{"id":254,"date":"2025-08-16T00:26:13","date_gmt":"2025-08-16T00:26:13","guid":{"rendered":"https:\/\/bioskinetics.com.au\/?page_id=254"},"modified":"2025-08-16T00:56:40","modified_gmt":"2025-08-16T00:56:40","slug":"chain-of-chaos","status":"publish","type":"page","link":"https:\/\/bioskinetics.com.au\/?page_id=254","title":{"rendered":"Chain of Chaos"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\" \/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/>\n  <title>Chain of Chos<\/title>\n  <style>\n    :root {\n      --bg: #0e0f14;\n      --panel: #141726;\n      --muted: #8a8fa3;\n      --text: #eef0f7;\n      --accent: #8b5cf6; \/* purple *\/\n      --accent-2: #22d3ee; \/* cyan *\/\n      --good: #22c55e;\n      --bad: #ef4444;\n      --warn: #f59e0b;\n      --card: #1a1f35;\n      --shadow: 0 10px 30px rgba(0,0,0,.35);\n      --radius: 16px;\n    }\n    * { box-sizing: border-box; }\n    html, body { height: 100%; }\n    body {\n      margin: 0;\n      font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, \"Helvetica Neue\", Arial, \"Noto Sans\", \"Apple Color Emoji\", \"Segoe UI Emoji\";\n      background: radial-gradient(1200px 800px at 80% -10%, rgba(139,92,246,.16), transparent),\n                  radial-gradient(1200px 800px at -10% 110%, rgba(34,211,238,.12), transparent),\n                  var(--bg);\n      color: var(--text);\n    }\n    a { color: var(--accent-2); text-decoration: none; }\n    .app { max-width: 1100px; margin: 0 auto; padding: 20px; }\n    header.header { display: flex; align-items: center; gap: 16px; }\n    .logo {\n      width: 48px; height: 48px; border-radius: 12px;\n      display: grid; place-items: center;\n      background: linear-gradient(135deg, var(--accent), var(--accent-2));\n      box-shadow: var(--shadow);\n      font-size: 28px;\n    }\n    .title h1 { margin: 0; font-size: 28px; letter-spacing: .3px; }\n    .title p { margin: 2px 0 0; color: var(--muted); font-size: 14px; }\n\n    .topbar { margin: 14px 0 18px; display: grid; grid-template-columns: 1fr auto auto; gap: 10px; align-items: center; }\n    .scorebox { background: var(--card); padding: 12px 14px; border-radius: var(--radius); display:flex; gap:14px; align-items:center; box-shadow: var(--shadow); }\n    .scorebox .item { font-size: 14px; }\n    .scorebox .label { color: var(--muted); font-size: 12px; }\n    .score { font-weight: 700; font-size: 18px; }\n    .btn { border: 0; background: linear-gradient(135deg, var(--accent), var(--accent-2)); color: #0b0e18; padding: 10px 16px; border-radius: 12px; font-weight: 700; cursor: pointer; box-shadow: var(--shadow); }\n    .btn.secondary { background: #222744; color: var(--text); }\n    .btn.ghost { background: transparent; border: 1px solid #2d3358; color: var(--text); }\n    .btn:disabled { opacity: .6; cursor: not-allowed; }\n\n    .layout { display: grid; grid-template-columns: 260px 1fr; gap: 16px; }\n    @media (max-width: 980px) { .layout { grid-template-columns: 1fr; } }\n\n    .panel { background: var(--panel); border: 1px solid #1f2442; border-radius: var(--radius); box-shadow: var(--shadow); }\n    .panel h2 { margin: 0; padding: 14px 16px; border-bottom:1px solid #22284a; font-size: 16px; letter-spacing:.3px; }\n    .panel .body { padding: 12px; }\n\n    \/* Arena *\/\n    .arena { position: relative; height: 520px; border-radius: var(--radius); overflow: hidden; background:\n      radial-gradient(1200px 600px at 50% -400px, rgba(139,92,246,.16), transparent),\n      linear-gradient(180deg, #0f1326, #101329 40%, #0e1124); border:1px solid #20264a; }\n    .arena::after { content:\"\"; position:absolute; inset:0; pointer-events:none; background: linear-gradient(180deg, transparent, rgba(0,0,0,.3)); }\n\n    \/* Hint bar *\/\n    .hintbar { display:flex; align-items:center; gap:10px; background:#121738; border:1px solid #1e2550; border-radius: 12px; padding: 10px 12px; }\n    .hinticon { width: 28px; height: 28px; border-radius: 8px; display:grid; place-items:center; background:#1b2148; font-size:18px; }\n    .hinttext { font-size: 14px; color: #cfd5f6; }\n    .timer { height: 6px; border-radius: 6px; background:#1c2246; overflow:hidden; }\n    .timer .bar { height: 100%; width:0%; background: linear-gradient(90deg, var(--accent), var(--accent-2)); transition: width linear; }\n\n    \/* Predictions grid *\/\n    .grid { display:grid; grid-template-columns: repeat(5, 1fr); gap:10px; }\n    @media (max-width:720px) { .grid { grid-template-columns: repeat(4, 1fr);} }\n    .tile { background:#10142c; border:1px solid #1c2246; border-radius: 12px; padding: 10px; display:flex; gap:10px; align-items:center; cursor:pointer; position:relative; overflow:hidden; min-height:60px; }\n    .tile .ico { font-size: 22px; width: 28px; display:grid; place-items:center; }\n    .tile .nm { font-size: 12px; color: #c7cdf0; line-height: 1.2; }\n    .tile:hover { border-color:#2a3270; background:#12183a; }\n    .tile.selected { outline:2px solid var(--accent-2); background:#12183a; }\n    .tile.correct { box-shadow: inset 0 0 0 2px var(--good); }\n    .tile.wrong { box-shadow: inset 0 0 0 2px var(--bad); }\n\n    \/* Progress *\/\n    .progress { display:flex; gap:4px; }\n    .dot { width: 12px; height: 12px; border-radius: 50%; background: #242b54; border: 1px solid #1d234a; opacity:.8; }\n    .dot.done { background: linear-gradient(135deg, var(--accent), var(--accent-2)); opacity:1; }\n    .dot.current { box-shadow: 0 0 0 2px #2b336c; }\n\n    \/* Overlays *\/\n    .overlay { position:absolute; inset:0; display:none; align-items:center; justify-content:center; background: rgba(12,14,24,.72); z-index: 30; }\n    .overlay.show { display:flex; }\n    .card { background: var(--card); border:1px solid #242a52; border-radius: 16px; padding: 18px; width:min(560px, 92vw); box-shadow: var(--shadow); }\n    .card h3 { margin:0 0 8px; font-size: 22px; }\n    .card p { color: var(--muted); margin:0 0 12px; }\n\n    \/* Arena FX particles *\/\n    .particle { position:absolute; will-change: transform, opacity; user-select:none; }\n    @keyframes fall { from { transform: translateY(-120%) rotate(0deg);} to { transform: translateY(120%) rotate(360deg);} }\n    @keyframes rise { from { transform: translateY(120%) scale(.8);} to { transform: translateY(-120%) scale(1);} }\n    @keyframes fly { from { transform: translateX(-20%) rotate(-10deg);} to { transform: translateX(120%) rotate(10deg);} }\n    @keyframes dropBounce { 0%{ transform: translateY(-120%);} 80%{ transform: translateY(0);} 90%{ transform: translateY(-20%);} 100%{ transform: translateY(0);} }\n    @keyframes slide { from{ transform: translateX(-120%);} to { transform: translateX(120%);} }\n    @keyframes spin { from{ transform: rotate(0);} to{ transform: rotate(-360deg);} }\n    @keyframes flash { 0%, 100% { opacity: 0; } 10%, 20% { opacity: 1; } 30%, 60%{ opacity: .2;} }\n    @keyframes shake { 0%, 100%{ transform: translate(0,0);} 20%{ transform: translate(-8px, 2px);} 40%{ transform: translate(6px, -4px);} 60%{ transform: translate(-4px, 4px);} 80%{ transform: translate(4px, -2px);} }\n    .shake { animation: shake .8s ease-in-out; }\n\n    .laser { position:absolute; left: -100%; right:auto; height: 10px; background: linear-gradient(90deg, rgba(255,0,95,.0), rgba(255,0,95,.9) 30%, rgba(255,0,95,.0)); box-shadow: 0 0 18px rgba(255,0,95,.8); }\n    .beam { position:absolute; left: 50%; transform: translateX(-50%); width: 120px; height: 0; bottom: 80px; background: linear-gradient(180deg, rgba(34,211,238,.0), rgba(34,211,238,.45)); filter: blur(4px); border-radius: 0 0 80px 80px; }\n    .ufo { position:absolute; top: 10%; left: -20%; font-size: 40px; filter: drop-shadow(0 12px 20px rgba(0,0,0,.4)); }\n\n    .glitch { position:absolute; inset:0; background: repeating-linear-gradient(0deg, rgba(255,255,255,.04) 0 2px, transparent 2px 4px); mix-blend-mode: overlay; animation: flash .7s ease-in-out; pointer-events:none; }\n\n    .rainbow { position:absolute; width: 120%; height: 120%; left: -10%; top: 40%; background: conic-gradient(from 180deg at 50% 100%, red, orange, yellow, green, cyan, blue, violet, red); border-radius: 50% 50% 0 0; filter: blur(6px) brightness(1.1); opacity:.85; clip-path: ellipse(50% 45% at 50% 100%); }\n    .tornado { position:absolute; left:50%; top: 40px; width: 18px; height: 240px; transform: translateX(-50%); background: conic-gradient(from 180deg, rgba(255,255,255,.08), rgba(255,255,255,.01)); border-radius: 50% 50% 8px 8px; filter: blur(2px); animation: spin 1.2s linear infinite; }\n\n    .controls { display:flex; gap:8px; justify-content:flex-end; }\n    .small { font-size: 12px; color: var(--muted); }\n    .kbd { background:#0f1430; border:1px solid #232a58; padding: 2px 6px; border-radius:8px; font-size: 12px; }\n\n    .settings { display:grid; gap:10px; }\n    .row { display:flex; gap:10px; align-items:center; justify-content:space-between; }\n    input[type=\"range\"]{ width: 130px; }\n    select, input[type=\"range\"] { accent-color: var(--accent); }\n  <\/style>\n<\/head>\n<body>\n<div class=\"app\" id=\"app\">\n  <header class=\"header\">\n    <div class=\"logo\" aria-hidden=\"true\">\u26d3\ufe0f<\/div>\n    <div class=\"title\">\n      <h1>Chain of Chos<\/h1>\n      <p>Predict the next bizarre event before it happens. Stack streaks, chase multipliers, and dare your friends.<\/p>\n    <\/div>\n  <\/header>\n\n  <div class=\"topbar\">\n    <div class=\"scorebox\" role=\"status\" aria-live=\"polite\">\n      <div class=\"item\"><div class=\"label\">Score<\/div><div class=\"score\" id=\"score\">0<\/div><\/div>\n      <div class=\"item\"><div class=\"label\">Streak<\/div><div class=\"score\" id=\"streak\">0<\/div><\/div>\n      <div class=\"item\"><div class=\"label\">Multiplier<\/div><div class=\"score\" id=\"mult\">x1<\/div><\/div>\n      <div class=\"item\"><div class=\"label\">High Score<\/div><div class=\"score\" id=\"hi\">0<\/div><\/div>\n    <\/div>\n    <div class=\"controls\">\n      <button class=\"btn ghost\" id=\"btnHow\">How to Play<\/button>\n      <button class=\"btn secondary\" id=\"btnReset\" disabled>Reset<\/button>\n      <button class=\"btn\" id=\"btnStart\">Start<\/button>\n    <\/div>\n  <\/div>\n\n  <div class=\"layout\">\n    <aside class=\"panel\">\n      <h2>Round<\/h2>\n      <div class=\"body\">\n        <div class=\"hintbar\" style=\"margin-bottom:10px;\">\n          <div class=\"hinticon\" id=\"hintIcon\">\u2753<\/div>\n          <div class=\"hinttext\" id=\"hintText\">Press <span class=\"kbd\">Start<\/span> to begin. Make a prediction when you see the clue.<\/div>\n        <\/div>\n        <div class=\"timer\" aria-hidden=\"true\"><div class=\"bar\" id=\"timerBar\"><\/div><\/div>\n        <div class=\"small\" style=\"margin-top:6px;\">Chain progress<\/div>\n        <div id=\"progress\" class=\"progress\" style=\"margin-top:6px;\"><\/div>\n      <\/div>\n\n      <h2>Settings<\/h2>\n      <div class=\"body settings\">\n        <div class=\"row\"><label for=\"len\">Chain length<\/label><input type=\"range\" id=\"len\" min=\"6\" max=\"20\" step=\"1\" value=\"10\"><span id=\"lenVal\">10<\/span><\/div>\n        <div class=\"row\"><label for=\"diff\">Difficulty<\/label>\n          <select id=\"diff\">\n            <option value=\"normal\">Normal<\/option>\n            <option value=\"hard\">Epic<\/option>\n          <\/select>\n        <\/div>\n        <p class=\"small\">Scoring: <strong>+3<\/strong> for correct before reveal, <strong>+1<\/strong> during early event. Streak adds a multiplier every 3 in a row.<\/p>\n      <\/div>\n\n      <h2>Viral<\/h2>\n      <div class=\"body\">\n        <button class=\"btn\" id=\"btnShare\" style=\"width:100%;\">Share your score<\/button>\n        <p class=\"small\" style=\"margin-top:8px;\">Challenge friends. The text includes your score and a link.<\/p>\n      <\/div>\n    <\/aside>\n\n    <main class=\"panel\">\n      <h2>Arena<\/h2>\n      <div class=\"body\" style=\"display:grid; gap:12px;\">\n        <div id=\"arena\" class=\"arena\" aria-live=\"polite\" aria-label=\"Game arena\"><\/div>\n\n        <div class=\"panel\" style=\"border:none; background:#0f1430;\">\n          <h2>Predict the next event<\/h2>\n          <div class=\"body\">\n            <div id=\"grid\" class=\"grid\" aria-label=\"Prediction options\"><\/div>\n            <p class=\"small\" style=\"margin-top:8px;\">Tip: You can also use keys <span class=\"kbd\">1-0<\/span> then <span class=\"kbd\">Q-W-E-R-T<\/span> to quickly select.<\/p>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/main>\n  <\/div>\n\n  <!-- Overlays -->\n  <div id=\"overlay\" class=\"overlay\" role=\"dialog\" aria-modal=\"true\" aria-hidden=\"true\">\n    <div class=\"card\" id=\"overlayCard\">\n      <h3 id=\"ovTitle\">Welcome to Chain of Chos<\/h3>\n      <p id=\"ovText\">Predict the next event from the clue to score big. Ready?<\/p>\n      <div style=\"display:flex; gap:8px; justify-content:flex-end; margin-top:8px;\">\n        <button class=\"btn secondary\" id=\"ovClose\">Close<\/button>\n        <button class=\"btn\" id=\"ovStart\">Start<\/button>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  \/\/ ======= Game Constants & State =======\n  const arena = document.getElementById('arena');\n  const grid = document.getElementById('grid');\n  const scoreEl = document.getElementById('score');\n  const streakEl = document.getElementById('streak');\n  const multEl = document.getElementById('mult');\n  const hiEl = document.getElementById('hi');\n  const hintIcon = document.getElementById('hintIcon');\n  const hintText = document.getElementById('hintText');\n  const timerBar = document.getElementById('timerBar');\n  const progress = document.getElementById('progress');\n  const lenInput = document.getElementById('len');\n  const lenVal = document.getElementById('lenVal');\n  const diffSelect = document.getElementById('diff');\n\n  const btnStart = document.getElementById('btnStart');\n  const btnReset = document.getElementById('btnReset');\n  const btnHow = document.getElementById('btnHow');\n  const btnShare = document.getElementById('btnShare');\n\n  const overlay = document.getElementById('overlay');\n  const ovTitle = document.getElementById('ovTitle');\n  const ovText = document.getElementById('ovText');\n  const ovStart = document.getElementById('ovStart');\n  const ovClose = document.getElementById('ovClose');\n\n  const HIGH_KEY = 'chain_of_chos_highscore_v2';\n  let highScore = Number(localStorage.getItem(HIGH_KEY) || 0);\n  hiEl.textContent = highScore;\n\n  \/\/ Each event lasts 20 seconds\n  const EVENT_MS = 20000;\n  let HINT_MS = 3000; \/\/ adjustable by difficulty\n  let EARLY_WINDOW_MS = 900; \/\/ adjustable by difficulty\n\n  let chain = []; \/\/ array of event ids\n  let index = 0;\n  let score = 0;\n  let streak = 0;\n  let selected = null; \/\/ id of selected prediction\n  let lockInput = false;\n  let timers = [];\n\n  \/\/ Utility to manage timers and clear on reset\n  function setT(cb, ms){ const t = setTimeout(cb, ms); timers.push(t); return t; }\n  function clearTimers(){ timers.forEach(clearTimeout); timers = []; }\n\n  \/\/ ======= Event & Clue Catalog (20 events) =======\n  \/\/ Each event has: id, name, icon, clue{icon,text}, run(arena)->Promise\n  const Events = [\n    {\n      id:'cats', name:'Cats Falling', icon:'\ud83d\udc31',\n      clue:{ icon:'\ud83d\udc3e', text:'Soft thuds from above. A faint purr on the wind\u2026' },\n      run: () => spawnEmoji({chars:['\ud83d\udc31','\ud83d\udc08\u200d\u2b1b'], count:60, anim:'fall'})\n    },\n    {\n      id:'laser', name:'Laser Sweep', icon:'\ud83d\udd34',\n      clue:{ icon:'\ud83d\udd26', text:'A crimson line charges with a high\u2011pitched hum\u2026' },\n      run: () => laserSweep()\n    },\n    {\n      id:'fruit', name:'Bouncing Fruit', icon:'\ud83c\udf49',\n      clue:{ icon:'\ud83c\udf4c', text:'A sweet scent and a suspicious wobble underfoot\u2026' },\n      run: () => spawnEmoji({chars:['\ud83c\udf4e','\ud83c\udf4c','\ud83c\udf4a','\ud83c\udf53','\ud83c\udf49'], count:70, anim:'dropBounce'})\n    },\n    {\n      id:'ufo', name:'UFO Abduction', icon:'\ud83d\udef8',\n      clue:{ icon:'\ud83d\udfe6', text:'A cone of cold light searches the ground\u2026' },\n      run: () => ufoBeam()\n    },\n    {\n      id:'confetti', name:'Disco Confetti', icon:'\ud83c\udf89',\n      clue:{ icon:'\ud83c\udfb6', text:'Bass hits. The air tastes like celebration\u2026' },\n      run: () => confettiBurst()\n    },\n    {\n      id:'quake', name:'Earthquake', icon:'\ud83c\udf0b',\n      clue:{ icon:'\ud83d\udcc9', text:'A low rumble, then a jitter in your knees\u2026' },\n      run: () => quakeShake()\n    },\n    {\n      id:'lightning', name:'Lightning', icon:'\u26a1',\n      clue:{ icon:'\u26c5', text:'Air crackles. The hair on your arms rises\u2026' },\n      run: () => lightning()\n    },\n    {\n      id:'bubbles', name:'Bubbles Rising', icon:'\ud83e\udee7',\n      clue:{ icon:'\ud83e\udee5', text:'Everything feels lighter\u2026 almost buoyant.' },\n      run: () => spawnEmoji({chars:['\ud83e\udee7'], count:80, anim:'rise'})\n    },\n    {\n      id:'portal', name:'Portal', icon:'\ud83c\udf00',\n      clue:{ icon:'\ud83d\udfe3', text:'Space folds. A ring of light whispers open\u2026' },\n      run: () => portalOpen()\n    },\n    {\n      id:'ghosts', name:'Ghost Parade', icon:'\ud83d\udc7b',\n      clue:{ icon:'\ud83d\udd6f\ufe0f', text:'A chill drifts sideways. Someone giggles\u2026' },\n      run: () => spawnEmoji({chars:['\ud83d\udc7b','\ud83e\udddf\u200d\u2642\ufe0f'], count:40, anim:'fly'})\n    },\n    {\n      id:'bananas', name:'Banana Slide', icon:'\ud83e\udd56',\n      clue:{ icon:'\ud83e\udd7e', text:'Watch your step. Slick mischief ahead\u2026' },\n      run: () => spawnEmoji({chars:['\ud83c\udf4c','\ud83c\udf4c','\ud83c\udf4c'], count:64, anim:'slide'})\n    },\n    {\n      id:'volcano', name:'Volcano Erupts', icon:'\ud83c\udf0b',\n      clue:{ icon:'\ud83d\udd25', text:'Heat tremors, then a glow from below\u2026' },\n      run: () => volcano()\n    },\n    {\n      id:'snow', name:'Snow Flurry', icon:'\u2744\ufe0f',\n      clue:{ icon:'\ud83e\udde3', text:'Breath fogs. Silence gets heavier\u2026' },\n      run: () => spawnEmoji({chars:['\u2744\ufe0f','\u2745','\u2746'], count:90, anim:'fall'})\n    },\n    {\n      id:'flip', name:'Gravity Flip', icon:'\ud83d\udd04',\n      clue:{ icon:'\ud83e\uddf2', text:'Up feels down. Your pockets float\u2026' },\n      run: () => gravityFlip()\n    },\n    {\n      id:'rewind', name:'Clock Rewind', icon:'\u23ea',\n      clue:{ icon:'\u23f0', text:'The ticks run backward. D\u00e9j\u00e0 vu doubles\u2026' },\n      run: () => spawnEmoji({chars:['\u23f0','\ud83d\udd52','\u23f1\ufe0f'], count:50, anim:'spin'})\n    },\n    {\n      id:'glitch', name:'Pixel Glitch', icon:'\ud83e\udde9',\n      clue:{ icon:'\ud83d\udcfa', text:'Reality stutters. Colors misalign\u2026' },\n      run: () => glitch()\n    },\n    {\n      id:'fireworks', name:'Fireworks', icon:'\ud83c\udf86',\n      clue:{ icon:'\ud83c\udf87', text:'Whistles rise. Night prepares to bloom\u2026' },\n      run: () => fireworks()\n    },\n    {\n      id:'rainbow', name:'Rainbow Arc', icon:'\ud83c\udf08',\n      clue:{ icon:'\ud83c\udf26\ufe0f', text:'Rain stops. The air smiles\u2026' },\n      run: () => rainbowArc()\n    },\n    {\n      id:'tornado', name:'Tornado', icon:'\ud83c\udf2a\ufe0f',\n      clue:{ icon:'\ud83c\udf43', text:'Leaves begin to circle in a jealous spiral\u2026' },\n      run: () => tornado()\n    },\n    {\n      id:'meteors', name:'Meteor Shower', icon:'\u2604\ufe0f',\n      clue:{ icon:'\ud83c\udf0c', text:'A streak on the horizon becomes many\u2026' },\n      run: () => meteors()\n    }\n  ];\n\n  \/\/ Build prediction grid\n  const keyHints = ['1','2','3','4','5','6','7','8','9','0','Q','W','E','R','T','Y','U','I','O','P'];\n  const idToIndex = new Map();\n  function buildGrid(){\n    grid.innerHTML = '';\n    Events.forEach((ev, i)=>{\n      const tile = document.createElement('button');\n      tile.className = 'tile';\n      tile.setAttribute('data-id', ev.id);\n      tile.setAttribute('title', ev.name + ' (' + keyHints[i] + ')');\n      tile.innerHTML = `<div class=\"ico\">${ev.icon}<\/div><div class=\"nm\"><strong>${ev.name}<\/strong><br><span class=\"small\">Key: ${keyHints[i]}<\/span><\/div>`;\n      tile.addEventListener('click', ()=> selectPrediction(ev.id));\n      grid.appendChild(tile);\n      idToIndex.set(ev.id, i);\n    });\n  }\n  buildGrid();\n\n  \/\/ ======= Utility FX & Spawners =======\n  function spawnEmoji({chars, count, anim}){\n    return new Promise(resolve => {\n      const duration = EVENT_MS;\n      const items = [];\n      for(let i=0;i<count;i++){\n        const el = document.createElement('div');\n        el.className = 'particle';\n        el.textContent = chars[Math.floor(Math.random()*chars.length)];\n        el.style.left = Math.random()*92 + '%';\n        el.style.top = (anim==='rise'? (80+Math.random()*30) : (-20 - Math.random()*30)) + '%';\n        el.style.fontSize = (16 + Math.random()*32) + 'px';\n        el.style.animation = `${anim} ${1.2 + Math.random()*1.2}s ${Math.random()*0.8}s both ease-in-out`;\n        arena.appendChild(el);\n        items.push(el);\n      }\n      setT(()=>{ items.forEach(n=>n.remove()); resolve(); }, duration);\n    });\n  }\n\n  function laserSweep(){\n    return new Promise(resolve=>{\n      const y = 50 + Math.random()*60;\n      const l = document.createElement('div');\n      l.className = 'laser';\n      l.style.top = y + 'px';\n      l.style.height = (6 + Math.random()*10) + 'px';\n      arena.appendChild(l);\n      l.animate([\n        { left: '-100%' },\n        { left: '120%' }\n      ], { duration: EVENT_MS, easing:'cubic-bezier(.25,.8,.25,1)' });\n      setT(()=>{ l.remove(); resolve(); }, EVENT_MS);\n    });\n  }\n\n  function ufoBeam(){\n    return new Promise(resolve=>{\n      const u = document.createElement('div'); u.className='ufo'; u.textContent='\ud83d\udef8'; arena.appendChild(u);\n      const beam = document.createElement('div'); beam.className='beam'; arena.appendChild(beam);\n      u.animate([{ transform:'translateX(0)' }, { transform:'translateX(160%)' }], { duration: EVENT_MS, easing:'ease-in-out' });\n      beam.animate([{ height:'0px', opacity:.2 }, { height:'140px', opacity:.6 }, { height:'0px', opacity:.2 }], { duration: EVENT_MS, easing:'ease-in-out' });\n      setT(()=>{ u.remove(); beam.remove(); resolve(); }, EVENT_MS);\n    });\n  }\n\n  function confettiBurst(){\n    return new Promise(resolve=>{\n      const n = 120;\n      const pieces = [];\n      for(let i=0;i<n;i++){\n        const p = document.createElement('div');\n        p.className='particle';\n        p.style.left = '50%'; p.style.top = '40%';\n        p.style.width = p.style.height = (4 + Math.random()*6) + 'px';\n        p.style.background = `hsl(${Math.random()*360}, 90%, 60%)`;\n        p.style.borderRadius = '2px';\n        p.style.opacity = .9;\n        arena.appendChild(p);\n        const dx = (Math.random()-.5)*300; const dy = (Math.random()-.2)*260;\n        p.animate([{ transform: 'translate(0,0) rotate(0deg)' }, { transform: `translate(${dx}px, ${dy}px) rotate(${Math.random()*720-360}deg)` , opacity:.1}], { duration: EVENT_MS, easing:'ease-out' });\n        pieces.push(p);\n      }\n      setT(()=>{ pieces.forEach(e=>e.remove()); resolve(); }, EVENT_MS);\n    });\n  }\n\n  function quakeShake(){\n    return new Promise(resolve=>{\n      arena.classList.add('shake');\n      setT(()=>{ arena.classList.remove('shake'); resolve(); }, Math.min(EVENT_MS, 1400));\n    });\n  }\n\n  function lightning(){\n    return new Promise(resolve=>{\n      const flashes = [];\n      for(let i=0;i<10;i++){\n        const f = document.createElement('div'); f.className='particle'; f.style.inset='0'; f.style.background='radial-gradient(circle at 60% 20%, rgba(255,255,100,.7), rgba(255,255,100,.0) 30%), radial-gradient(circle at 40% 0%, rgba(255,255,200,.5), transparent 25%)'; f.style.animation='flash .7s ease-in-out'; f.style.zIndex=10; arena.appendChild(f);\n        flashes.push(f);\n      }\n      setT(()=>{ flashes.forEach(x=>x.remove()); resolve(); }, EVENT_MS);\n    });\n  }\n\n  function portalOpen(){\n    return new Promise(resolve=>{\n      const ring = document.createElement('div'); ring.className='particle'; ring.style.left='50%'; ring.style.top='50%'; ring.style.transform='translate(-50%,-50%)'; ring.style.width='140px'; ring.style.height='140px'; ring.style.border='3px solid rgba(139,92,246,.8)'; ring.style.borderRadius='50%'; ring.style.boxShadow='0 0 30px rgba(139,92,246,.6) inset, 0 0 30px rgba(34,211,238,.3)'; arena.appendChild(ring);\n      ring.animate([{ transform:'translate(-50%,-50%) scale(.6)', opacity:.6 }, { transform:'translate(-50%,-50%) scale(1.15)', opacity:1 }, { transform:'translate(-50%,-50%) scale(.8)', opacity:.0 }], { duration: EVENT_MS, easing:'ease-in-out' });\n      setT(()=>{ ring.remove(); resolve(); }, EVENT_MS);\n    });\n  }\n\n  function volcano(){\n    return new Promise(resolve=>{\n      const crater = document.createElement('div'); crater.className='particle'; crater.style.left='50%'; crater.style.bottom='6px'; crater.style.transform='translateX(-50%)'; crater.style.fontSize='40px'; crater.textContent='\ud83c\udf0b'; arena.appendChild(crater);\n      const flames = [];\n      for(let i=0;i<64;i++){\n        const f = document.createElement('div'); f.className='particle'; f.textContent = Math.random()<.5 ? '\ud83d\udd25' : '\u2728';\n        f.style.left=(46+Math.random()*8)+'%'; f.style.bottom='30px'; f.style.fontSize=(12+Math.random()*18)+'px';\n        arena.appendChild(f);\n        f.animate([{ transform:'translateY(0) scale(.8)', opacity:1 }, { transform:`translate(${(Math.random()-.5)*80}px, -160px) scale(1.2)`, opacity:.1 }], { duration: EVENT_MS, easing:'ease-out' });\n        flames.push(f);\n      }\n      setT(()=>{ crater.remove(); flames.forEach(e=>e.remove()); resolve(); }, EVENT_MS);\n    });\n  }\n\n  function gravityFlip(){\n    return new Promise(resolve=>{\n      const a = arena.animate([{ transform:'rotate(0deg)' }, { transform:'rotate(180deg)' }, { transform:'rotate(0deg)' }], { duration: EVENT_MS, easing:'ease-in-out' });\n      a.addEventListener('finish', ()=> resolve());\n    });\n  }\n\n  function glitch(){\n    return new Promise(resolve=>{\n      const gl = [];\n      for(let i=0;i<20;i++){\n        const g = document.createElement('div'); g.className='glitch'; arena.appendChild(g); gl.push(g);\n      }\n      setT(()=>{ gl.forEach(e=>e.remove()); resolve(); }, EVENT_MS);\n    });\n  }\n\n  function fireworks(){\n    return new Promise(resolve=>{\n      const rockets = [];\n      for(let i=0;i<22;i++){\n        const r = document.createElement('div'); r.className='particle'; r.textContent='\ud83c\udf87'; r.style.left=(10+Math.random()*80)+'%'; r.style.bottom='0'; r.style.fontSize=(18+Math.random()*10)+'px'; arena.appendChild(r);\n        const height = 140 + Math.random()*160;\n        r.animate([{ transform:'translateY(0) scale(.8)', opacity:1 }, { transform:`translateY(-${height}px) scale(1.2)`, opacity:0.8 }], { duration: EVENT_MS*0.7, easing:'cubic-bezier(.3,.66,.35,1)' });\n        rockets.push(r);\n      }\n      setT(()=>{ rockets.forEach(e=>e.remove()); resolve(); }, EVENT_MS);\n    });\n  }\n\n  function rainbowArc(){\n    return new Promise(resolve=>{\n      const rb = document.createElement('div'); rb.className='rainbow'; arena.appendChild(rb);\n      rb.animate([{ opacity:0, transform:'translateY(20px)' }, { opacity:.85, transform:'translateY(0)' }, { opacity:0, transform:'translateY(-10px)' }], { duration: EVENT_MS, easing:'ease-in-out' });\n      setT(()=>{ rb.remove(); resolve(); }, EVENT_MS);\n    });\n  }\n\n  function tornado(){\n    return new Promise(resolve=>{\n      const t = document.createElement('div'); t.className='tornado'; arena.appendChild(t);\n      setT(()=>{ t.remove(); resolve(); }, EVENT_MS);\n    });\n  }\n\n  function meteors(){\n    return new Promise(resolve=>{\n      const ms = [];\n      for(let i=0;i<24;i++){\n        const m = document.createElement('div'); m.className='particle'; m.textContent='\u2604\ufe0f'; m.style.left=(-10 - Math.random()*10)+'%'; m.style.top=(10 + Math.random()*40)+'%'; m.style.fontSize=(18+Math.random()*16)+'px'; arena.appendChild(m);\n        const dx = 140 + Math.random()*220; const dy = 160 + Math.random()*180;\n        m.animate([{ transform:'translate(0,0) rotate(0deg)', opacity:1 }, { transform:`translate(${dx}%, ${dy}%) rotate(${Math.random()*90}deg)`, opacity:.1 }], { duration: EVENT_MS, easing:'linear' });\n        ms.push(m);\n      }\n      setT(()=>{ ms.forEach(e=>e.remove()); resolve(); }, EVENT_MS);\n    });\n  }\n\n  \/\/ ======= Game Flow =======\n  function setDifficulty(){\n    const d = diffSelect.value;\n    if(d==='hard'){ HINT_MS = 2000; EARLY_WINDOW_MS = 700; }\n    else { HINT_MS = 3000; EARLY_WINDOW_MS = 900; }\n  }\n\n  function buildProgress(len){\n    progress.innerHTML = '';\n    for(let i=0;i<len;i++){\n      const d = document.createElement('div'); d.className='dot'; progress.appendChild(d);\n    }\n  }\n\n  function updateProgress(){\n    [...progress.children].forEach((d, i)=>{\n      d.classList.toggle('done', i < index);\n      d.classList.toggle('current', i === index);\n    });\n  }\n\n  function resetState(){\n    clearTimers();\n    score = 0; streak = 0; index = 0; selected = null; lockInput = false;\n    scoreEl.textContent = '0'; streakEl.textContent = '0'; multEl.textContent = 'x1';\n    hintIcon.textContent = '\u2753'; hintText.textContent = 'Make a prediction when you see the clue.';\n    timerBar.style.transitionDuration = '0ms'; timerBar.style.width = '0%';\n    arena.innerHTML = '';\n    [...grid.children].forEach(tile=> tile.classList.remove('selected','correct','wrong'));\n  }\n\n  function multiplier(){ return 1 + Math.floor(streak\/3); }\n\n  function generateChain(len){\n    chain = []; let prev = null;\n    for(let i=0;i<len;i++){\n      let id; do { id = Events[Math.floor(Math.random()*Events.length)].id; } while(id===prev);\n      prev = id; chain.push(id);\n    }\n  }\n\n  function selectPrediction(id){\n    if(lockInput) return; \/\/ cannot change once event starts\n    selected = id;\n    [...grid.children].forEach(t => t.classList.remove('selected'));\n    const tile = grid.querySelector(`[data-id=\"${id}\"]`);\n    if(tile) tile.classList.add('selected');\n  }\n\n  function roundHint(ev){\n    hintIcon.textContent = ev.clue.icon; hintText.textContent = ev.clue.text;\n    timerBar.style.transitionDuration = '0ms'; timerBar.style.width = '0%';\n    \/\/ start fill\n    requestAnimationFrame(()=>{\n      timerBar.style.transitionDuration = HINT_MS + 'ms';\n      timerBar.style.width = '100%';\n    });\n  }\n\n  async function playNext(){\n    if(index >= chain.length){ return finish(); }\n    updateProgress();\n    const ev = Events.find(e=> e.id === chain[index]);\n    roundHint(ev);\n    lockInput = false; \/\/ can change prediction during hint phase\n\n    \/\/ At reveal time, lock input, score early if correct\n    setT(()=>{\n      lockInput = true; \/\/ lock tile changes as event starts\n      const wasCorrectEarly = selected === ev.id;\n      let awarded = 0;\n      if(wasCorrectEarly){ streak++; awarded = 3 * multiplier(); score += awarded; toast(`Perfect! +${awarded}`,'good'); }\n      else { streak = 0; }\n      updateHUD();\n\n      \/\/ mark tiles\n      markTiles(ev.id, wasCorrectEarly ? 'correct' : null);\n\n      \/\/ During early event, allow late guess for +1 if still wrong\n      let earlyWindowOpen = true;\n      const lateGuard = (e)=>{\n        if(!earlyWindowOpen) return; if(lockInput && selected && selected === ev.id && !wasCorrectEarly){\n          streak = 1; const pts = 1 * multiplier(); score += pts; earlyWindowOpen = false; toast(`Quick catch! +${pts}`,'warn'); updateHUD(); markTiles(ev.id, 'correct'); }\n      };\n      const keyLateListener = (e)=>{ lateGuard(); };\n      grid.addEventListener('click', lateGuard, { once:true });\n      window.addEventListener('keydown', keyLateListener, { once:true });\n\n      \/\/ Play the event\n      ev.run().then(()=>{\n        earlyWindowOpen = false;\n        window.removeEventListener('keydown', keyLateListener, { once:true });\n        \/\/ If wrong at end, mark wrong\n        if(selected !== ev.id){ markTiles(ev.id, 'wrong'); }\n        \/\/ Move to next after a short breather\n        setT(()=>{ index++; selected = null; [...grid.children].forEach(t=>t.classList.remove('selected')); playNext(); }, 350);\n      });\n\n    }, HINT_MS);\n  }\n\n  function markTiles(correctId, mode){\n    [...grid.children].forEach(t=>{\n      const id = t.getAttribute('data-id');\n      t.classList.remove('correct','wrong');\n      if(id === correctId){ if(mode==='correct') t.classList.add('correct'); }\n    });\n    if(mode==='wrong'){\n      const chosen = selected ? grid.querySelector(`[data-id=\"${selected}\"]`) : null;\n      if(chosen) chosen.classList.add('wrong');\n    }\n  }\n\n  function updateHUD(){\n    scoreEl.textContent = String(score);\n    streakEl.textContent = String(streak);\n    multEl.textContent = 'x' + String(multiplier());\n  }\n\n  function finish(){\n    clearTimers();\n    if(score > highScore){ highScore = score; localStorage.setItem(HIGH_KEY, String(highScore)); hiEl.textContent = highScore; }\n    showOverlay('Chain Complete!', `You scored <strong>${score}<\/strong> points with a max streak of <strong>${streak}<\/strong>. Challenge a friend?`);\n    btnReset.disabled = false; btnStart.disabled = false;\n  }\n\n  function toast(msg, type){\n    const t = document.createElement('div'); t.className='particle'; t.style.left='50%'; t.style.top='18px'; t.style.transform='translateX(-50%)'; t.style.padding='8px 10px'; t.style.borderRadius='10px'; t.style.fontWeight='700'; t.style.background = type==='good'? 'rgba(34,197,94,.15)' : (type==='warn'? 'rgba(245,158,11,.15)' : 'rgba(239,68,68,.2)'); t.style.border='1px solid rgba(255,255,255,.12)'; t.textContent = msg; arena.appendChild(t);\n    t.animate([{ opacity:0, transform:'translate(-50%, -6px)' }, { opacity:1, transform:'translate(-50%, 0)' }, { opacity:0, transform:'translate(-50%, -10px)' }], { duration: 1200, easing:'ease-in-out' });\n    setT(()=> t.remove(), 1200);\n  }\n\n  function showOverlay(title, html){\n    ovTitle.innerHTML = title; ovText.innerHTML = html; overlay.classList.add('show'); overlay.setAttribute('aria-hidden','false');\n  }\n  function hideOverlay(){ overlay.classList.remove('show'); overlay.setAttribute('aria-hidden','true'); }\n\n  function startGame(){\n    resetState();\n    setDifficulty();\n    const len = Number(lenInput.value);\n    buildProgress(len);\n    generateChain(len);\n    btnStart.disabled = true; btnReset.disabled = false;\n    hideOverlay();\n    playNext();\n  }\n\n  function resetGame(){ resetState(); buildProgress(Number(lenInput.value)); }\n\n  \/\/ ======= Keyboard shortcuts =======\n  window.addEventListener('keydown', (e)=>{\n    const k = e.key.toUpperCase();\n    const idx = keyHints.indexOf(k);\n    if(idx>-1){ const ev = Events[idx]; selectPrediction(ev.id); }\n  });\n\n  \/\/ ======= Share =======\n  async function share(){\n    const text = `I scored ${score} in Chain of Chos! Can you beat me?`;\n    const url = (location && location.href) ? location.href : '';\n    const full = `${text} ${url}`.trim();\n    try{\n      if(navigator.share){ await navigator.share({ title:'Chain of Chos', text, url }); }\n      else { await navigator.clipboard.writeText(full); showOverlay('Copied!', 'Your challenge text is on the clipboard. Paste it anywhere.'); }\n    } catch(err){ await navigator.clipboard.writeText(full); showOverlay('Copied!', 'Your challenge text is on the clipboard. Paste it anywhere.'); }\n  }\n\n  \/\/ ======= UI Wire =======\n  lenInput.addEventListener('input', ()=>{ lenVal.textContent = lenInput.value; buildProgress(Number(lenInput.value)); });\n  diffSelect.addEventListener('change', setDifficulty);\n  btnStart.addEventListener('click', startGame);\n  btnReset.addEventListener('click', resetGame);\n  btnHow.addEventListener('click', ()=> showOverlay('How to Play', `\n    <ul style=\"margin:0; padding-left:18px;\">\n      <li>Press <strong>Start<\/strong>. You will see a <em>clue<\/em> for the next event and a short timer.<\/li>\n      <li>Click the matching tile (or press its key). Change your mind until the event starts.<\/li>\n      <li><strong>+3 points<\/strong> if correct <em>before<\/em> the event. <strong>+1<\/strong> if you pick correctly right after it starts.<\/li>\n      <li>Every <strong>3 correct in a row<\/strong> boosts your multiplier. Longer streaks = bigger scores.<\/li>\n      <li>Finish the chain to get a share card and challenge friends.<\/li>\n    <\/ul>\n  `));\n  btnShare.addEventListener('click', share);\n  ovStart.addEventListener('click', startGame);\n  ovClose.addEventListener('click', hideOverlay);\n\n  \/\/ Init\n  buildProgress(Number(lenInput.value));\n})();\n<\/script>\n<\/body>\n<\/html>\n\n","protected":false},"excerpt":{"rendered":"<p>Chain of Chos \u26d3\ufe0f Chain of Chos Predict the next bizarre event before it happens. Stack streaks, chase multipliers, and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"_glsr_average":0,"_glsr_ranking":0,"_glsr_reviews":0,"footnotes":""},"class_list":["post-254","page","type-page","status-publish","hentry"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=\/wp\/v2\/pages\/254","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=254"}],"version-history":[{"count":2,"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=\/wp\/v2\/pages\/254\/revisions"}],"predecessor-version":[{"id":257,"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=\/wp\/v2\/pages\/254\/revisions\/257"}],"wp:attachment":[{"href":"https:\/\/bioskinetics.com.au\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=254"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}