<!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>