<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>English-Like Fake Word Generator with Dynamic Colors</title>
<style>
html, body {
height: 100%;
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
transition: background-color 0.5s ease;
}
#word {
font-size: 48pt;
user-select: none;
cursor: pointer;
transition: color 0.5s ease;
padding: 0 10px;
text-align: center;
max-width: 90vw;
word-break: break-word;
}
#word:hover {
opacity: 0.8;
}
#refreshBtn {
margin-top: 20px;
font-size: 18pt;
padding: 8px 24px;
cursor: pointer;
border: none;
border-radius: 6px;
background-color: #007acc;
color: white;
transition: background-color 0.3s ease;
}
#refreshBtn:hover {
background-color: #005f99;
}
</style>
</head>
<body>
<div id="word" title="Click to generate a new word"></div>
<button id="refreshBtn" aria-label="Generate new word">Refresh</button>
<script>
// Most common English consonant clusters (onsets & codas)
const consonantClusters = [
'bl', 'br', 'cl', 'cr', 'dr', 'fl', 'fr', 'gl', 'gr',
'pl', 'pr', 'sl', 'sm', 'sn', 'sp', 'st', 'tr', 'tw',
'sh', 'th', 'ch', 'wh', 'ph', 'qu'
];
// Most common single consonants in English
const consonants = 'b c d f g h j k l m n p r s t v w y z'.split(' ');
// Common single vowels in English
const vowels = ['a', 'e', 'i', 'o', 'u'];
// Most frequent vowel clusters/diphthongs in English
const vowelClusters = [
'ai', 'au', 'ea', 'ee', 'ei', 'ie', 'oa', 'oi', 'oo', 'ou', 'ue', 'ui'
];
// Utility: pick random item from array
function pickRandom(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
// Pick consonant or consonant cluster (clusters about 25% chance)
function pickConsonant() {
if (Math.random() < 0.25) {
return pickRandom(consonantClusters);
} else {
return pickRandom(consonants);
}
}
// Pick vowel or vowel cluster (diphthong) (20% chance diphthong)
function pickVowel() {
if (Math.random() < 0.2) {
return pickRandom(vowelClusters);
} else {
return pickRandom(vowels);
}
}
// Check if string ends with vowel (single or cluster)
function endsWithVowel(str) {
for (const vc of vowelClusters) {
if (str.endsWith(vc)) return true;
}
for (const v of vowels) {
if (str.endsWith(v)) return true;
}
return false;
}
// Check if string starts with vowel (single or cluster)
function startsWithVowel(str) {
for (const vc of vowelClusters) {
if (str.startsWith(vc)) return true;
}
for (const v of vowels) {
if (str.startsWith(v)) return true;
}
return false;
}
// Generate a syllable from simplified, common English patterns only
// Patterns: CVC, CV, VC, CCV (start clusters), no vowel-only syllables
function generateSyllable(allowStartVowel = false) {
const patterns = allowStartVowel
? ['CVC', 'CV', 'VC', 'CCV']
: ['CVC', 'CV', 'VC', 'CCV'];
let pattern = pickRandom(patterns);
let syllable = '';
for (const ch of pattern) {
if (ch === 'C') {
syllable += pickConsonant();
} else if (ch === 'V') {
syllable += pickVowel();
}
}
return syllable;
}
// Generate a word by concatenating 2 or 3 syllables
// Prevent vowel-to-vowel junctions between syllables
function generateWord() {
const syllableCount = 2 + Math.floor(Math.random() * 2); // 2 or 3 syllables
let word = '';
for (let i = 0; i < syllableCount; i++) {
const allowStartVowel = (i === 0);
let syllable = generateSyllable(allowStartVowel);
// Avoid vowel-to-vowel syllable junctions
let attempts = 0;
while (
word.length > 0 &&
endsWithVowel(word) &&
startsWithVowel(syllable) &&
attempts < 10
) {
syllable = generateSyllable(false);
attempts++;
}
word += syllable;
}
return word.charAt(0).toUpperCase() + word.slice(1);
}
// Generate a random color in HSL space for good variation
function randomHslColor() {
// Hue: 0-360, Saturation: 60-90%, Lightness: 40-70%
const h = Math.floor(Math.random() * 360);
const s = 60 + Math.random() * 30;
const l = 40 + Math.random() * 30;
return `hsl(${h}, ${s.toFixed(0)}%, ${l.toFixed(0)}%)`;
}
// Compute luminance of an RGB color (0 to 1)
function luminance(r, g, b) {
const a = [r, g, b].map(v => {
v /= 255;
return v <= 0.03928
? v / 12.92
: Math.pow((v + 0.055) / 1.055, 2.4);
});
return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];
}
// Convert HSL string to RGB object {r,g,b}
function hslToRgb(h, s, l) {
s /= 100;
l /= 100;
const k = n => (n + h / 30) % 12;
const a = s * Math.min(l, 1 - l);
const f = n =>
l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
return {
r: Math.round(255 * f(0)),
g: Math.round(255 * f(8)),
b: Math.round(255 * f(4))
};
}
// Compute contrast ratio between two luminances per WCAG
// 1 is minimum (same color), 21 is max contrast (black-white)
function contrastRatio(l1, l2) {
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
// Given bg HSL, find a font color (black or white) with sufficient contrast
// fallback is white if black too low contrast
function pickFontColor(bgHsl) {
const [h, s, l] = bgHsl.match(/\d+/g).map(Number);
const bgRgb = hslToRgb(h, s, l);
const bgLum = luminance(bgRgb.r, bgRgb.g, bgRgb.b);
// Black luminance = 0, white luminance = 1
const blackContrast = contrastRatio(bgLum, 0);
const whiteContrast = contrastRatio(bgLum, 1);
// WCAG recommends minimum 4.5 contrast for normal text
if (blackContrast >= 4.5) return 'black';
if (whiteContrast >= 4.5) return 'white';
// If neither black nor white meets contrast, pick the one with higher contrast
return blackContrast > whiteContrast ? 'black' : 'white';
}
const wordEl = document.getElementById('word');
const refreshBtn = document.getElementById('refreshBtn');
function refreshWord() {
const word = generateWord();
// Pick a random background color
const bgColor = randomHslColor();
document.body.style.backgroundColor = bgColor;
// Pick font color with sufficient contrast
const fontColor = pickFontColor(bgColor);
wordEl.style.color = fontColor;
wordEl.textContent = word;
}
refreshWord();
wordEl.addEventListener('click', refreshWord);
refreshBtn.addEventListener('click', refreshWord);
</script>
</body>
</html>