[{"content":"This is my new Hugo site using the PaperMod theme and hosted on Firebase!\n","permalink":"https://steelwhitetable.ca/posts/hello-world/","summary":"\u003cp\u003eThis is my new Hugo site using the PaperMod theme and hosted on Firebase!\u003c/p\u003e","title":"Welcome to Steel White Table"},{"content":"The Return to the Table If you’ve been around the web for a while, the name Steel White Table might ring a bell.\nFor many years, steelwhitetable.org was my digital home: a place where I shared random observations and other interesting websites. It was a \u0026ldquo;digital scrapbook\u0026rdquo; of the mid-2000s internet, a hub for niche interests.\nEventually, the original site went quiet, preserved only in the Wayback Machine.\nA New Chapter Steel White Table is the revival of that spirit. While the tools and technologies have changed, the goal remains the same: a clean, minimalist space to document what I’m working on, what I’m learning, and what I’m thinking about.\nExpect to find:\nCode \u0026amp; Tech: Insights from my latest projects and experiments. Personal Log: Thoughts on development, motorcycles, and daily life. Archives: Occasional nods to the content that made the original site what it was. Social Media (Or Lack Thereof) I don\u0026rsquo;t do social media. It turns out that having a life is much more interesting than scrolling through someone else\u0026rsquo;s highlight reel. But if you really must \u0026ldquo;connect\u0026rdquo;, I may have accounts here:\nTwitter: I prefer to shout my opinions into a literal void in the forest. The squirrels provide much better engagement and fewer bot replies. LinkedIn: I have endorsed myself for \u0026ldquo;Extreme Privacy\u0026rdquo; and \u0026ldquo;Avoiding Synergy.\u0026rdquo; Please don\u0026rsquo;t reach out to \u0026ldquo;pick my brain\u0026rdquo; as it\u0026rsquo;s currently in use. Instagram: 0 posts. I find it\u0026rsquo;s much more efficient to just describe my avocado toast to strangers on the bus in excruciating detail. Facebook: Unless we shared a locker in 1984 or are related by blood, I am legally obligated to ignore your friend request. Thanks for stopping by. It’s good to be back.\n","permalink":"https://steelwhitetable.ca/about/","summary":"\u003ch3 id=\"the-return-to-the-table\"\u003eThe Return to the Table\u003c/h3\u003e\n\u003cp\u003eIf you’ve been around the web for a while, the name \u003cstrong\u003eSteel White Table\u003c/strong\u003e might ring a bell.\u003c/p\u003e\n\u003cp\u003eFor many years, \u003ccode\u003esteelwhitetable.org\u003c/code\u003e was my digital home: a place where I shared random observations and other interesting websites.   It was a \u0026ldquo;digital scrapbook\u0026rdquo; of the mid-2000s internet, a hub for niche interests.\u003c/p\u003e\n\u003cp\u003eEventually, the original site went quiet, preserved only in the \u003ca href=\"https://web.archive.org/details/steelwhitetable.org\"\u003eWayback Machine\u003c/a\u003e.\u003c/p\u003e\n\u003ch3 id=\"a-new-chapter\"\u003eA New Chapter\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003eSteel White Table\u003c/strong\u003e is the revival of that spirit. While the tools and technologies have changed, the goal remains the same: a clean, minimalist space to document what I’m working on, what I’m learning, and what I’m thinking about.\u003c/p\u003e","title":"About"},{"content":"\r–\r–\r–\r–\r–\r–\rPick Numbers\r","permalink":"https://steelwhitetable.ca/apps/6-49/","summary":"\u003cstyle\u003e\r\n  .n649-wrap {\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    gap: 2rem;\r\n    margin: 2.5rem 0;\r\n  }\r\n\r\n  .n649-row {\r\n    display: flex;\r\n    flex-wrap: wrap;\r\n    justify-content: center;\r\n    gap: 0.75rem;\r\n  }\r\n\r\n  .n649-ball {\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    width: 3.5rem;\r\n    height: 3.5rem;\r\n    border: 2px solid var(--border);\r\n    border-radius: 10px;\r\n    font-size: 1.5rem;\r\n    font-weight: 700;\r\n    color: var(--primary);\r\n    background: var(--entry);\r\n    transition: opacity 0.15s ease;\r\n  }\r\n\r\n  .n649-ball.n649-fade {\r\n    opacity: 0;\r\n  }\r\n\r\n  .n649-btn {\r\n    padding: 0.6rem 1.6rem;\r\n    font-size: 1rem;\r\n    font-weight: 600;\r\n    border: 2px solid var(--primary);\r\n    border-radius: 8px;\r\n    background: transparent;\r\n    color: var(--primary);\r\n    cursor: pointer;\r\n    transition: background 0.15s ease, color 0.15s ease;\r\n  }\r\n\r\n  .n649-btn:hover {\r\n    background: var(--primary);\r\n    color: var(--theme);\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"n649-wrap\"\u003e\r\n  \u003cdiv class=\"n649-row\" id=\"n649-row\"\u003e\r\n    \u003cdiv class=\"n649-ball\"\u003e–\u003c/div\u003e\r\n    \u003cdiv class=\"n649-ball\"\u003e–\u003c/div\u003e\r\n    \u003cdiv class=\"n649-ball\"\u003e–\u003c/div\u003e\r\n    \u003cdiv class=\"n649-ball\"\u003e–\u003c/div\u003e\r\n    \u003cdiv class=\"n649-ball\"\u003e–\u003c/div\u003e\r\n    \u003cdiv class=\"n649-ball\"\u003e–\u003c/div\u003e\r\n  \u003c/div\u003e\r\n  \u003cbutton class=\"n649-btn\" id=\"n649-btn\"\u003ePick Numbers\u003c/button\u003e\r\n\u003c/div\u003e\r\n\u003cscript\u003e\r\n  (function () {\r\n    var row = document.getElementById('n649-row');\r\n    var btn = document.getElementById('n649-btn');\r\n    var balls = Array.from(row.querySelectorAll('.n649-ball'));\r\n\r\n    function pick() {\r\n      var pool = [];\r\n      for (var i = 1; i \u003c= 49; i++) pool.push(i);\r\n      var result = [];\r\n      while (result.length \u003c 6) {\r\n        var idx = Math.floor(Math.random() * pool.length);\r\n        result.push(pool.splice(idx, 1)[0]);\r\n      }\r\n      return result.sort(function (a, b) { return a - b; });\r\n    }\r\n\r\n    function render() {\r\n      balls.forEach(function (ball) { ball.classList.add('n649-fade'); });\r\n      setTimeout(function () {\r\n        var nums = pick();\r\n        balls.forEach(function (ball, i) {\r\n          ball.textContent = nums[i];\r\n          ball.classList.remove('n649-fade');\r\n        });\r\n      }, 150);\r\n    }\r\n\r\n    btn.addEventListener('click', render);\r\n    render();\r\n  })();\r\n\u003c/script\u003e","title":"6/49 Number Generator"},{"content":"\rDrag to fling. Nothing is resolved.\n","permalink":"https://steelwhitetable.ca/apps/ball-pit-of-regrets/","summary":"\u003cstyle\u003e\r\n  .bpr-outer {\r\n    margin: 1.5rem 0;\r\n  }\r\n\r\n  .bpr-stage {\r\n    position: relative;\r\n    border-radius: 8px;\r\n    overflow: hidden;\r\n    line-height: 0;\r\n  }\r\n\r\n  #bpr-canvas {\r\n    display: block;\r\n    width: 100%;\r\n    cursor: grab;\r\n  }\r\n\r\n  #bpr-canvas:active {\r\n    cursor: grabbing;\r\n  }\r\n\r\n  .bpr-badge {\r\n    position: absolute;\r\n    top: 10px;\r\n    right: 10px;\r\n    z-index: 10;\r\n    font-size: 0.6rem;\r\n    letter-spacing: 0.1em;\r\n    text-transform: uppercase;\r\n    padding: 0.25em 0.6em;\r\n    border-radius: 3px;\r\n    font-family: 'Courier New', Courier, monospace;\r\n    pointer-events: none;\r\n  }\r\n\r\n  .bpr-hint {\r\n    font-size: 0.72rem;\r\n    color: #3a3428;\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    margin-top: 0.7rem;\r\n    font-family: inherit;\r\n  }\r\n\u003c/style\u003e\r\n\u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js\"\u003e\u003c/script\u003e\r\n\u003cdiv class=\"bpr-outer\"\u003e\r\n  \u003cdiv class=\"bpr-stage\" id=\"bpr-stage\"\u003e\r\n    \u003cspan class=\"bpr-badge\" id=\"bpr-badge\"\u003e\u003c/span\u003e\r\n    \u003ccanvas id=\"bpr-canvas\"\u003e\u003c/canvas\u003e\r\n  \u003c/div\u003e\r\n  \u003cp class=\"bpr-hint\"\u003eDrag to fling. Nothing is resolved.\u003c/p\u003e","title":"Ball Pit of Regrets"},{"content":"\rShake\r","permalink":"https://steelwhitetable.ca/apps/browser-window-earthquake-simulator/","summary":"\u003cstyle\u003e\r\n  @keyframes bwes-shake {\r\n    0%, 100% { transform: translate(0, 0) rotate(0deg); }\r\n    10%  { transform: translate(-9px, -6px) rotate(-1.5deg); }\r\n    20%  { transform: translate(8px,   5px) rotate( 1.0deg); }\r\n    30%  { transform: translate(-11px,  4px) rotate( 0.5deg); }\r\n    40%  { transform: translate(5px,  -8px) rotate(-1.0deg); }\r\n    50%  { transform: translate(-6px,   7px) rotate( 1.5deg); }\r\n    60%  { transform: translate(10px,  -5px) rotate(-0.5deg); }\r\n    70%  { transform: translate(-7px,   9px) rotate( 1.0deg); }\r\n    80%  { transform: translate(4px,   -7px) rotate(-1.5deg); }\r\n    90%  { transform: translate(-9px,   3px) rotate( 0.5deg); }\r\n  }\r\n\r\n  body.bwes-shaking {\r\n    animation-name: bwes-shake;\r\n    animation-timing-function: linear;\r\n    animation-iteration-count: 1;\r\n    animation-fill-mode: none;\r\n    overflow: hidden;\r\n  }\r\n\r\n  html.bwes-shaking {\r\n    overflow: hidden;\r\n  }\r\n\r\n  .bwes-container {\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    justify-content: center;\r\n    padding: 5rem 1rem;\r\n    gap: 1.75rem;\r\n  }\r\n\r\n  .bwes-btn {\r\n    font-size: 1.5rem;\r\n    font-weight: 700;\r\n    padding: 1.25rem 3.5rem;\r\n    background: #c00;\r\n    color: #fff;\r\n    border: none;\r\n    border-radius: 8px;\r\n    cursor: pointer;\r\n    letter-spacing: 0.06em;\r\n    text-transform: uppercase;\r\n    transition: background 0.15s, transform 0.1s;\r\n    user-select: none;\r\n    -webkit-user-select: none;\r\n  }\r\n\r\n  .bwes-btn:hover:not(:disabled) {\r\n    background: #e00;\r\n    transform: scale(1.04);\r\n  }\r\n\r\n  .bwes-btn:active:not(:disabled) {\r\n    transform: scale(0.96);\r\n  }\r\n\r\n  .bwes-btn:disabled {\r\n    background: #888;\r\n    cursor: not-allowed;\r\n    transform: none;\r\n  }\r\n\r\n  .bwes-status {\r\n    font-size: 0.85rem;\r\n    color: rgba(128, 128, 128, 0.7);\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    height: 1.2em;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"bwes-container\"\u003e\r\n  \u003cbutton class=\"bwes-btn\" id=\"bwes-btn\"\u003eShake\u003c/button\u003e\r\n  \u003cdiv class=\"bwes-status\" id=\"bwes-status\"\u003e\u003c/div\u003e\r\n\u003c/div\u003e\r\n\u003cscript\u003e\r\n  (function () {\r\n    var btn      = document.getElementById('bwes-btn');\r\n    var statusEl = document.getElementById('bwes-status');\r\n    var timer    = null;\r\n\r\n    function shake() {\r\n      var duration = 1000 + Math.random() * 2000; // 1000–3000 ms\r\n      var seconds  = (duration / 1000).toFixed(1);\r\n\r\n      btn.disabled = true;\r\n      statusEl.textContent = seconds + 's';\r\n\r\n      document.body.style.animationDuration = duration + 'ms';\r\n      document.body.classList.add('bwes-shaking');\r\n      document.documentElement.classList.add('bwes-shaking');\r\n\r\n      clearTimeout(timer);\r\n      timer = setTimeout(function () {\r\n        document.body.classList.remove('bwes-shaking');\r\n        document.documentElement.classList.remove('bwes-shaking');\r\n        document.body.style.animationDuration = '';\r\n        btn.disabled = false;\r\n        statusEl.textContent = '';\r\n      }, duration);\r\n    }\r\n\r\n    btn.addEventListener('click', function () {\r\n      if (!btn.disabled) shake();\r\n    });\r\n  })();\r\n\u003c/script\u003e","title":"Browser Window Earthquake Simulator"},{"content":" 0% 00:00:00 Leave it.\u0026nbsp;\u0026nbsp;Or blow. SPACE / swipe / mic\n◈\nDust Sand Ash Pollen Glitter Pause dust Clear all ","permalink":"https://steelwhitetable.ca/apps/digital-dust/","summary":"\u003cstyle\u003e\n  .dd-wrap {\n    position: relative;\n    background: #0e0d0c;\n    border-radius: 8px;\n    margin: 1.5rem 0;\n    overflow: hidden;\n    height: 420px;\n    user-select: none;\n  }\n\n  .dd-wrap canvas {\n    position: absolute;\n    top: 0;\n    left: 0;\n    display: block;\n  }\n\n  #dd-settled { z-index: 1; }\n\n  #dd-air {\n    z-index: 2;\n    cursor: crosshair;\n  }\n\n  .dd-hud {\n    position: absolute;\n    bottom: 14px;\n    right: 16px;\n    z-index: 10;\n    text-align: right;\n    pointer-events: none;\n    line-height: 1.3;\n  }\n\n  .dd-pct {\n    display: block;\n    font-family: 'Courier New', Courier, monospace;\n    font-size: 1.05rem;\n    color: #fff;\n    transition: color 1s ease;\n  }\n\n  .dd-timer {\n    display: block;\n    font-family: 'Courier New', Courier, monospace;\n    font-size: 0.65rem;\n    color: #2e2c2a;\n    letter-spacing: 0.04em;\n    margin-top: 2px;\n  }\n\n  .dd-hint {\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    z-index: 10;\n    color: #3a3028;\n    font-family: 'Georgia', 'Times New Roman', serif;\n    font-style: italic;\n    font-size: 0.85rem;\n    letter-spacing: 0.07em;\n    pointer-events: none;\n    white-space: nowrap;\n    opacity: 1;\n    transition: opacity 1.6s ease;\n  }\n\n  .dd-cog {\n    position: absolute;\n    top: 10px;\n    right: 12px;\n    z-index: 20;\n    background: none;\n    border: none;\n    color: #2e2c2a;\n    font-size: 1.1rem;\n    cursor: pointer;\n    padding: 4px 6px;\n    line-height: 1;\n    transition: color 0.2s;\n  }\n\n  .dd-cog:hover { color: #7a6a58; }\n\n  .dd-panel {\n    position: absolute;\n    top: 36px;\n    right: 8px;\n    z-index: 30;\n    background: #141210;\n    border: 1px solid #252018;\n    border-radius: 6px;\n    padding: 8px;\n    display: none;\n    flex-direction: column;\n    gap: 4px;\n    min-width: 120px;\n  }\n\n  .dd-panel.dd-open { display: flex; }\n\n  .dd-sw {\n    font-family: inherit;\n    font-size: 0.72rem;\n    letter-spacing: 0.07em;\n    color: #6a6050;\n    background: none;\n    border: 1px solid #252018;\n    border-radius: 3px;\n    padding: 0.38em 0.7em 0.38em 0.55em;\n    cursor: pointer;\n    text-align: left;\n    transition: border-color 0.15s, color 0.15s;\n    display: flex;\n    align-items: center;\n    gap: 7px;\n  }\n\n  .dd-sw:hover { border-color: #4a4030; color: #c0b8a8; }\n\n  .dd-sw::before {\n    content: '';\n    display: inline-block;\n    width: 7px;\n    height: 7px;\n    border-radius: 50%;\n    flex-shrink: 0;\n  }\n\n  .dd-sw-dust::before   { background: #aaa; }\n  .dd-sw-golden::before { background: #d4b060; }\n  .dd-sw-ash::before    { background: #555; }\n  .dd-sw-pollen::before { background: #c8e850; }\n  .dd-sw-glitter::before {\n    background: conic-gradient(#f06, #90f, #06f, #0c6, #ff0, #f06);\n  }\n  .dd-sw-clear::before  { background: #3a3028; }\n\n  .dd-sw-clear { color: #5a4a3a; }\n  .dd-sw-clear:hover { color: #c0a080; border-color: #5a4030; }\n\n  .dd-hr {\n    border: none;\n    border-top: 1px solid #252018;\n    margin: 2px 0;\n  }\n\n  .dd-kbd-hint {\n    position: absolute;\n    bottom: 14px;\n    left: 16px;\n    z-index: 10;\n    font-family: 'Courier New', Courier, monospace;\n    font-size: 0.58rem;\n    color: #1e1c1a;\n    letter-spacing: 0.06em;\n    pointer-events: none;\n  }\n\u003c/style\u003e\n\u003cdiv class=\"dd-wrap\" id=\"dd-wrap\"\u003e\n  \u003ccanvas id=\"dd-settled\"\u003e\u003c/canvas\u003e\n  \u003ccanvas id=\"dd-air\"\u003e\u003c/canvas\u003e\n  \u003cdiv class=\"dd-hud\"\u003e\n    \u003cspan class=\"dd-pct\" id=\"dd-pct\"\u003e0%\u003c/span\u003e\n    \u003cspan class=\"dd-timer\" id=\"dd-timer\"\u003e00:00:00\u003c/span\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"dd-hint\" id=\"dd-hint\"\u003eLeave it.\u0026nbsp;\u0026nbsp;Or blow.\u003c/div\u003e\n\u003cp\u003e\u003cspan class=\"dd-kbd-hint\" id=\"dd-kbd-hint\"\u003eSPACE / swipe / mic\u003c/span\u003e\u003c/p\u003e","title":"Digital Dust"},{"content":"\rWrite your first sentence.\nThese will be the only words available to you today.\nLock In\rOnly words from your first sentence are permitted.\n","permalink":"https://steelwhitetable.ca/apps/echo-chamber/","summary":"\u003cstyle\u003e\r\n  .ec-stage {\r\n    background: #0d0c0a;\r\n    border-radius: 8px;\r\n    padding: 2.5rem 2rem;\r\n    margin: 1.5rem 0;\r\n    display: flex;\r\n    flex-direction: column;\r\n    gap: 1.2rem;\r\n  }\r\n\r\n  .ec-label {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: 1rem;\r\n    color: #c8c0b0;\r\n    margin: 0;\r\n    line-height: 1.5;\r\n  }\r\n\r\n  .ec-sublabel {\r\n    font-size: 0.78rem;\r\n    color: #4a4438;\r\n    letter-spacing: 0.06em;\r\n    text-transform: uppercase;\r\n    margin: 0;\r\n  }\r\n\r\n  .ec-textarea {\r\n    width: 100%;\r\n    background: #141210;\r\n    border: 1px solid #2a2418;\r\n    border-radius: 4px;\r\n    color: #c8c0b0;\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: 0.95rem;\r\n    line-height: 1.75;\r\n    padding: 0.9rem 1rem;\r\n    resize: vertical;\r\n    outline: none;\r\n    box-sizing: border-box;\r\n    transition: border-color 0.2s ease;\r\n  }\r\n\r\n  .ec-textarea:focus {\r\n    border-color: #3a3428;\r\n  }\r\n\r\n  .ec-textarea.ec-rejected {\r\n    border-color: #8a3030;\r\n    background: #1a1010;\r\n    transition: none;\r\n  }\r\n\r\n  .ec-lock-btn {\r\n    align-self: flex-start;\r\n    font-family: inherit;\r\n    font-size: 0.78rem;\r\n    letter-spacing: 0.1em;\r\n    text-transform: uppercase;\r\n    color: #5a8a7a;\r\n    background: none;\r\n    border: 1px solid rgba(90, 138, 122, 0.4);\r\n    border-radius: 4px;\r\n    padding: 0.5em 1.3em;\r\n    cursor: pointer;\r\n    transition: background 0.2s ease, border-color 0.2s ease;\r\n  }\r\n\r\n  .ec-lock-btn:hover {\r\n    background: rgba(90, 138, 122, 0.08);\r\n    border-color: rgba(90, 138, 122, 0.7);\r\n  }\r\n\r\n  .ec-seed-block {\r\n    border-left: 3px solid #2a2418;\r\n    padding: 0.7rem 1rem;\r\n    background: #111008;\r\n  }\r\n\r\n  .ec-seed-text {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: 0.88rem;\r\n    color: #4a4438;\r\n    font-style: italic;\r\n    margin: 0;\r\n    line-height: 1.6;\r\n  }\r\n\r\n  .ec-meta {\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 1rem;\r\n  }\r\n\r\n  .ec-badge {\r\n    font-size: 0.68rem;\r\n    letter-spacing: 0.1em;\r\n    text-transform: uppercase;\r\n    font-family: 'Courier New', Courier, monospace;\r\n    color: #5a8a7a;\r\n    background: rgba(90, 138, 122, 0.1);\r\n    border: 1px solid rgba(90, 138, 122, 0.2);\r\n    border-radius: 3px;\r\n    padding: 0.25em 0.65em;\r\n  }\r\n\r\n  .ec-hint {\r\n    font-size: 0.72rem;\r\n    color: #2a2420;\r\n    letter-spacing: 0.06em;\r\n    margin: 0;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"ec-stage\" id=\"ec-stage\"\u003e\r\n  \u003c!-- Phase 1: Seed --\u003e\r\n  \u003cdiv id=\"ec-seed-phase\"\u003e\r\n    \u003cp class=\"ec-label\"\u003eWrite your first sentence.\u003c/p\u003e","title":"Echo Chamber"},{"content":"\r0.0%\rInitializing.\rEstimated completion: unknown.\r","permalink":"https://steelwhitetable.ca/apps/existential-loading-bar/","summary":"\u003cstyle\u003e\r\n  .elb-wrap {\r\n    background: #111;\r\n    border-radius: 8px;\r\n    padding: 3.5rem 2.5rem 2.5rem;\r\n    margin: 1.5rem 0;\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    min-height: 280px;\r\n    justify-content: center;\r\n  }\r\n\r\n  .elb-concept {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: clamp(1rem, 2.5vw, 1.4rem);\r\n    color: #e8e0d0;\r\n    text-align: center;\r\n    margin-bottom: 1.8rem;\r\n    letter-spacing: 0.02em;\r\n  }\r\n\r\n  .elb-pct {\r\n    font-family: 'Courier New', Courier, monospace;\r\n    font-size: clamp(2.8rem, 6vw, 4.2rem);\r\n    color: #c9913a;\r\n    font-weight: normal;\r\n    margin-bottom: 1.2rem;\r\n    min-width: 7ch;\r\n    text-align: center;\r\n  }\r\n\r\n  .elb-track {\r\n    width: 100%;\r\n    max-width: 480px;\r\n    height: 5px;\r\n    background: #2a2a2a;\r\n    border-radius: 3px;\r\n    overflow: hidden;\r\n    margin-bottom: 1.4rem;\r\n  }\r\n\r\n  .elb-bar {\r\n    height: 100%;\r\n    background: #c9913a;\r\n    border-radius: 3px;\r\n    width: 0%;\r\n    transition: width 0.7s ease;\r\n  }\r\n\r\n  .elb-status {\r\n    font-size: 0.78rem;\r\n    color: #6a6050;\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    min-height: 1.2em;\r\n    text-align: center;\r\n    margin-bottom: 2.8rem;\r\n  }\r\n\r\n  .elb-footer {\r\n    font-size: 0.68rem;\r\n    color: rgba(255, 255, 255, 0.1);\r\n    letter-spacing: 0.1em;\r\n    text-transform: uppercase;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"elb-wrap\"\u003e\r\n  \u003cdiv class=\"elb-concept\" id=\"elb-concept\"\u003e\u003c/div\u003e\r\n  \u003cdiv class=\"elb-pct\" id=\"elb-pct\"\u003e0.0%\u003c/div\u003e\r\n  \u003cdiv class=\"elb-track\"\u003e\r\n    \u003cdiv class=\"elb-bar\" id=\"elb-bar\"\u003e\u003c/div\u003e\r\n  \u003c/div\u003e\r\n  \u003cdiv class=\"elb-status\" id=\"elb-status\"\u003eInitializing.\u003c/div\u003e\r\n  \u003cdiv class=\"elb-footer\"\u003eEstimated completion: unknown.\u003c/div\u003e\r\n\u003c/div\u003e\r\n\u003cscript\u003e\r\n  (function () {\r\n    var CONCEPTS = [\r\n      \"Understanding Your Purpose\",\r\n      \"Achieving Inner Peace\",\r\n      \"Accepting the Inevitable\",\r\n      \"Resolving Existential Uncertainty\",\r\n      \"Locating the Self\",\r\n      \"Processing the Impermanence of Existence\",\r\n      \"Optimizing for Contentment\",\r\n      \"Completing Unfinished Thoughts\",\r\n      \"Reconciling Past Decisions\",\r\n      \"Determining Life's Meaning\"\r\n    ];\r\n\r\n    var STATUS_RANGES = [\r\n      { max: 15,  msgs: [\"Initializing.\", \"Locating subject matter.\", \"Establishing baseline.\"] },\r\n      { max: 40,  msgs: [\"Consulting internal records.\", \"Cross-referencing known frameworks.\", \"Reviewing available data.\"] },\r\n      { max: 65,  msgs: [\"Narrowing possibilities.\", \"Eliminating unlikely outcomes.\", \"Refining parameters.\"] },\r\n      { max: 85,  msgs: [\"Progress detected.\", \"Trajectory appears promising.\", \"Positive indicators noted.\"] },\r\n      { max: 100, msgs: [\"Approaching resolution.\", \"Final calibrations underway.\", \"One moment.\", \"Nearly complete.\"] }\r\n    ];\r\n\r\n    var BACKWARD_MSG = \"Re-evaluating prior assumptions.\";\r\n\r\n    var pct = 0;\r\n    var conceptIdx = -1;\r\n    var lastRangeIdx = -1;\r\n    var stuckSince = null;\r\n\r\n    var conceptEl = document.getElementById(\"elb-concept\");\r\n    var pctEl     = document.getElementById(\"elb-pct\");\r\n    var barEl     = document.getElementById(\"elb-bar\");\r\n    var statusEl  = document.getElementById(\"elb-status\");\r\n\r\n    function rand(min, max) {\r\n      return min + Math.random() * (max - min);\r\n    }\r\n\r\n    function pickConcept() {\r\n      var next;\r\n      do { next = Math.floor(Math.random() * CONCEPTS.length); }\r\n      while (next === conceptIdx \u0026\u0026 CONCEPTS.length \u003e 1);\r\n      conceptIdx = next;\r\n      pct = 0;\r\n      lastRangeIdx = -1;\r\n      stuckSince = null;\r\n      conceptEl.textContent = CONCEPTS[conceptIdx];\r\n      barEl.style.width = \"0%\";\r\n      pctEl.textContent = \"0.0%\";\r\n      statusEl.textContent = \"Initializing.\";\r\n    }\r\n\r\n    function currentRangeIdx() {\r\n      for (var i = 0; i \u003c STATUS_RANGES.length; i++) {\r\n        if (pct \u003c= STATUS_RANGES[i].max) return i;\r\n      }\r\n      return STATUS_RANGES.length - 1;\r\n    }\r\n\r\n    function pickMsg(idx) {\r\n      var msgs = STATUS_RANGES[idx].msgs;\r\n      return msgs[Math.floor(Math.random() * msgs.length)];\r\n    }\r\n\r\n    function tick() {\r\n      var wentBack = Math.random() \u003c 0.12;\r\n\r\n      if (wentBack \u0026\u0026 pct \u003e 1) {\r\n        pct = Math.max(0, pct - rand(0.3, 2.0));\r\n        statusEl.textContent = BACKWARD_MSG;\r\n        // Reset stuck timer — backward motion means we're not stuck\r\n        stuckSince = null;\r\n        lastRangeIdx = currentRangeIdx();\r\n      } else {\r\n        var increment;\r\n        if (pct \u003e 90) {\r\n          increment = (100 - pct) * 0.018;\r\n        } else {\r\n          increment = rand(0.1, 0.8);\r\n        }\r\n        pct = Math.min(99.9, pct + increment);\r\n\r\n        var ri = currentRangeIdx();\r\n        if (ri !== lastRangeIdx) {\r\n          lastRangeIdx = ri;\r\n          statusEl.textContent = pickMsg(ri);\r\n        }\r\n      }\r\n\r\n      barEl.style.width = pct.toFixed(1) + \"%\";\r\n      pctEl.textContent  = pct.toFixed(1) + \"%\";\r\n\r\n      // Stuck detection: reset after 30s above 97%\r\n      if (pct \u003e 97) {\r\n        if (!stuckSince) stuckSince = Date.now();\r\n        else if (Date.now() - stuckSince \u003e 30000) {\r\n          pickConcept();\r\n          setTimeout(tick, rand(1000, 1600));\r\n          return;\r\n        }\r\n      }\r\n\r\n      setTimeout(tick, rand(800, 1200));\r\n    }\r\n\r\n    pickConcept();\r\n    setTimeout(tick, 1200);\r\n  })();\r\n\u003c/script\u003e","title":"Existential Loading Bar"},{"content":"\rHit the button to generate your order.\rOrder #—\rMobile Order\r—\r—\r0\rBasic\rGenerate Order\n","permalink":"https://steelwhitetable.ca/apps/fake-starbucks-drink-order/","summary":"\u003cstyle\u003e\r\n  .fsdo-wrap {\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    gap: 1.5rem;\r\n    margin: 2rem 0;\r\n  }\r\n\r\n  .fsdo-cup {\r\n    width: 100%;\r\n    max-width: 380px;\r\n    border-radius: 12px;\r\n    overflow: hidden;\r\n    box-shadow: 0 4px 24px rgba(0,0,0,0.18);\r\n    font-family: Arial, Helvetica, sans-serif;\r\n    background: #fff;\r\n    color: #1a1a1a;\r\n  }\r\n\r\n  .fsdo-cup-header {\r\n    background: #00704A;\r\n    color: #fff;\r\n    padding: 0.6rem 1rem 0.4rem;\r\n    display: flex;\r\n    justify-content: space-between;\r\n    align-items: center;\r\n  }\r\n\r\n  .fsdo-order-num {\r\n    font-size: 0.7rem;\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    opacity: 0.85;\r\n  }\r\n\r\n  .fsdo-order-label {\r\n    font-size: 0.65rem;\r\n    letter-spacing: 0.1em;\r\n    text-transform: uppercase;\r\n    opacity: 0.7;\r\n  }\r\n\r\n  .fsdo-cup-body {\r\n    padding: 1rem 1rem 0.75rem;\r\n  }\r\n\r\n  .fsdo-customer-name {\r\n    font-size: 2rem;\r\n    font-weight: 900;\r\n    text-transform: uppercase;\r\n    letter-spacing: 0.04em;\r\n    color: #1a1a1a;\r\n    margin-bottom: 0.75rem;\r\n    line-height: 1;\r\n  }\r\n\r\n  .fsdo-checks {\r\n    display: grid;\r\n    grid-template-columns: 1fr 1fr;\r\n    gap: 0.3rem 1rem;\r\n    margin-bottom: 0.9rem;\r\n  }\r\n\r\n  .fsdo-check-item {\r\n    display: flex;\r\n    align-items: center;\r\n    gap: 0.35rem;\r\n    font-size: 0.72rem;\r\n    color: #444;\r\n    white-space: nowrap;\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n  }\r\n\r\n  .fsdo-tick {\r\n    flex-shrink: 0;\r\n    width: 13px;\r\n    height: 13px;\r\n    border: 1.5px solid #888;\r\n    border-radius: 2px;\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n  }\r\n\r\n  .fsdo-tick.fsdo-checked {\r\n    background: #00704A;\r\n    border-color: #00704A;\r\n  }\r\n\r\n  .fsdo-tick.fsdo-checked::after {\r\n    content: '';\r\n    display: block;\r\n    width: 5px;\r\n    height: 3px;\r\n    border-left: 1.5px solid #fff;\r\n    border-bottom: 1.5px solid #fff;\r\n    transform: rotate(-45deg) translateY(-1px);\r\n  }\r\n\r\n  .fsdo-tick.fsdo-unchecked {\r\n    opacity: 0.3;\r\n  }\r\n\r\n  .fsdo-check-label {\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n  }\r\n\r\n  .fsdo-order-text {\r\n    font-size: 0.78rem;\r\n    color: #333;\r\n    line-height: 1.5;\r\n    border-top: 1px dashed #ccc;\r\n    padding-top: 0.65rem;\r\n    margin-top: 0.1rem;\r\n  }\r\n\r\n  .fsdo-cup-footer {\r\n    background: #00704A;\r\n    color: #fff;\r\n    padding: 0.55rem 1rem;\r\n  }\r\n\r\n  .fsdo-complexity-row {\r\n    display: flex;\r\n    justify-content: space-between;\r\n    align-items: center;\r\n    margin-bottom: 0.4rem;\r\n  }\r\n\r\n  .fsdo-complexity-score {\r\n    font-size: 1.4rem;\r\n    font-weight: 900;\r\n    line-height: 1;\r\n  }\r\n\r\n  .fsdo-complexity-label {\r\n    font-size: 0.7rem;\r\n    font-weight: 700;\r\n    text-transform: uppercase;\r\n    letter-spacing: 0.06em;\r\n    opacity: 0.9;\r\n    text-align: right;\r\n  }\r\n\r\n  .fsdo-meter-ticks {\r\n    display: flex;\r\n    gap: 4px;\r\n  }\r\n\r\n  .fsdo-meter-tick {\r\n    width: 22px;\r\n    height: 10px;\r\n    border: 1.5px solid rgba(255,255,255,0.5);\r\n    border-radius: 2px;\r\n    background: transparent;\r\n  }\r\n\r\n  .fsdo-meter-tick.fsdo-filled {\r\n    background: rgba(255,255,255,0.9);\r\n    border-color: rgba(255,255,255,0.9);\r\n  }\r\n\r\n  .fsdo-empty {\r\n    width: 100%;\r\n    max-width: 380px;\r\n    border: 2px dashed var(--border, #ccc);\r\n    border-radius: 12px;\r\n    padding: 3rem 1rem;\r\n    text-align: center;\r\n    color: var(--secondary, #888);\r\n    font-size: 0.9rem;\r\n  }\r\n\r\n  .fsdo-btn {\r\n    padding: 0.65rem 2rem;\r\n    font-size: 1rem;\r\n    font-weight: 700;\r\n    border: 2px solid #00704A;\r\n    border-radius: 8px;\r\n    background: transparent;\r\n    color: #00704A;\r\n    cursor: pointer;\r\n    letter-spacing: 0.04em;\r\n    transition: background 0.15s, color 0.15s;\r\n  }\r\n\r\n  .fsdo-btn:hover {\r\n    background: #00704A;\r\n    color: #fff;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"fsdo-wrap\"\u003e\r\n  \u003cdiv id=\"fsdo-empty\" class=\"fsdo-empty\"\u003e\r\n    Hit the button to generate your order.\r\n  \u003c/div\u003e\r\n  \u003cdiv id=\"fsdo-cup\" class=\"fsdo-cup\" style=\"display:none\"\u003e\r\n    \u003cdiv class=\"fsdo-cup-header\"\u003e\r\n      \u003cspan class=\"fsdo-order-num\" id=\"fsdo-order-num\"\u003eOrder #—\u003c/span\u003e\r\n      \u003cspan class=\"fsdo-order-label\"\u003eMobile Order\u003c/span\u003e\r\n    \u003c/div\u003e\r\n    \u003cdiv class=\"fsdo-cup-body\"\u003e\r\n      \u003cdiv class=\"fsdo-customer-name\" id=\"fsdo-name\"\u003e—\u003c/div\u003e\r\n      \u003cdiv class=\"fsdo-checks\" id=\"fsdo-checks\"\u003e\u003c/div\u003e\r\n      \u003cdiv class=\"fsdo-order-text\" id=\"fsdo-order-text\"\u003e—\u003c/div\u003e\r\n    \u003c/div\u003e\r\n    \u003cdiv class=\"fsdo-cup-footer\"\u003e\r\n      \u003cdiv class=\"fsdo-complexity-row\"\u003e\r\n        \u003cspan class=\"fsdo-complexity-score\" id=\"fsdo-score\"\u003e0\u003c/span\u003e\r\n        \u003cspan class=\"fsdo-complexity-label\" id=\"fsdo-desc\"\u003eBasic\u003c/span\u003e\r\n      \u003c/div\u003e\r\n      \u003cdiv class=\"fsdo-meter-ticks\" id=\"fsdo-ticks\"\u003e\u003c/div\u003e\r\n    \u003c/div\u003e\r\n  \u003c/div\u003e\r\n\u003cp\u003e\u003cbutton class=\"fsdo-btn\" id=\"fsdo-btn\"\u003eGenerate Order\u003c/button\u003e\u003c/p\u003e","title":"Fake Starbucks Drink Order"},{"content":"\rFind the face that is quietly judging you.\rPreparing the collection…\rNext Exhibit →\r","permalink":"https://steelwhitetable.ca/apps/museum-of-accidental-eye-contact/","summary":"\u003cstyle\u003e\r\n  .mae-stage {\r\n    background: #0d0c0a;\r\n    border-radius: 8px;\r\n    padding: 2.5rem 2rem 2rem;\r\n    margin: 1.5rem 0;\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    min-height: 340px;\r\n  }\r\n\r\n  .mae-instruction {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: 0.9rem;\r\n    color: #7a7060;\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    margin-bottom: 1.5rem;\r\n    text-align: center;\r\n    transition: opacity 0.4s ease;\r\n  }\r\n\r\n  .mae-instruction.mae-hidden {\r\n    opacity: 0;\r\n    pointer-events: none;\r\n  }\r\n\r\n  .mae-frame {\r\n    position: relative;\r\n    max-width: 700px;\r\n    width: 100%;\r\n    box-shadow: 0 8px 40px rgba(0,0,0,0.7);\r\n    cursor: crosshair;\r\n  }\r\n\r\n  .mae-frame.mae-clicked {\r\n    cursor: default;\r\n  }\r\n\r\n  .mae-img {\r\n    display: block;\r\n    width: 100%;\r\n    height: auto;\r\n    border: 1px solid #2a2418;\r\n    opacity: 0;\r\n    transition: opacity 0.6s ease;\r\n  }\r\n\r\n  .mae-img.mae-loaded {\r\n    opacity: 1;\r\n  }\r\n\r\n  .mae-loading {\r\n    position: absolute;\r\n    inset: 0;\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    font-family: 'Georgia', serif;\r\n    font-size: 0.82rem;\r\n    color: #4a4438;\r\n    letter-spacing: 0.1em;\r\n    text-transform: uppercase;\r\n  }\r\n\r\n  .mae-dot {\r\n    position: absolute;\r\n    width: 22px;\r\n    height: 22px;\r\n    border-radius: 50%;\r\n    border: 2px solid #c9913a;\r\n    transform: translate(-50%, -50%);\r\n    pointer-events: none;\r\n    display: none;\r\n    box-shadow: 0 0 0 4px rgba(201,145,58,0.15);\r\n  }\r\n\r\n  .mae-placard {\r\n    max-width: 700px;\r\n    width: 100%;\r\n    margin-top: 1.4rem;\r\n    border-left: 3px solid #3a3020;\r\n    padding: 1rem 1.4rem;\r\n    background: #141210;\r\n    opacity: 0;\r\n    transition: opacity 0.5s ease;\r\n  }\r\n\r\n  .mae-placard.mae-visible {\r\n    opacity: 1;\r\n  }\r\n\r\n  .mae-placard-title {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: 1rem;\r\n    color: #e0d8c8;\r\n    margin: 0 0 0.2rem;\r\n    line-height: 1.3;\r\n  }\r\n\r\n  .mae-placard-meta {\r\n    font-size: 0.78rem;\r\n    color: #5a5040;\r\n    letter-spacing: 0.04em;\r\n    margin: 0 0 0.9rem;\r\n  }\r\n\r\n  .mae-placard-judgment {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: 0.92rem;\r\n    color: #9a8f7a;\r\n    font-style: italic;\r\n    margin: 0;\r\n    line-height: 1.6;\r\n  }\r\n\r\n  .mae-next {\r\n    margin-top: 1.4rem;\r\n    font-family: inherit;\r\n    font-size: 0.78rem;\r\n    letter-spacing: 0.1em;\r\n    text-transform: uppercase;\r\n    color: #5a5040;\r\n    background: none;\r\n    border: 1px solid #2a2418;\r\n    border-radius: 4px;\r\n    padding: 0.5em 1.2em;\r\n    cursor: pointer;\r\n    opacity: 0;\r\n    transition: opacity 0.4s ease, color 0.2s ease, border-color 0.2s ease;\r\n    display: block;\r\n  }\r\n\r\n  .mae-next.mae-visible {\r\n    opacity: 1;\r\n  }\r\n\r\n  .mae-next:hover {\r\n    color: #9a8f7a;\r\n    border-color: #4a4030;\r\n  }\r\n\r\n  .mae-error {\r\n    font-size: 0.82rem;\r\n    color: #5a5040;\r\n    text-align: center;\r\n    padding: 2rem;\r\n    font-style: italic;\r\n    font-family: 'Georgia', serif;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"mae-stage\" id=\"mae-stage\"\u003e\r\n  \u003cdiv class=\"mae-instruction\" id=\"mae-instruction\"\u003eFind the face that is quietly judging you.\u003c/div\u003e\r\n  \u003cdiv class=\"mae-frame\" id=\"mae-frame\"\u003e\r\n    \u003cdiv class=\"mae-loading\" id=\"mae-loading\"\u003ePreparing the collection…\u003c/div\u003e\r\n    \u003cimg class=\"mae-img\" id=\"mae-img\" alt=\"\" draggable=\"false\" /\u003e\r\n    \u003cdiv class=\"mae-dot\" id=\"mae-dot\"\u003e\u003c/div\u003e\r\n  \u003c/div\u003e\r\n  \u003cdiv class=\"mae-placard\" id=\"mae-placard\"\u003e\r\n    \u003cp class=\"mae-placard-title\" id=\"mae-title\"\u003e\u003c/p\u003e","title":"Museum of Accidental Eye Contact"},{"content":"\rThe internet is vast. Most of it is this.\nSpin\rRead on Wikipedia →\rSpin Again\rWikipedia appears to be momentarily unavailable. Or possibly this article was too obscure even for them.\n","permalink":"https://steelwhitetable.ca/apps/obscure-wikipedia-article-roulette/","summary":"\u003cstyle\u003e\r\n  .wr-stage {\r\n    background: #0d0c0a;\r\n    border-radius: 8px;\r\n    padding: 3rem 2rem;\r\n    margin: 1.5rem 0;\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    min-height: 320px;\r\n    justify-content: center;\r\n    gap: 0;\r\n  }\r\n\r\n  .wr-tagline {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: 0.88rem;\r\n    color: #4a4438;\r\n    letter-spacing: 0.06em;\r\n    margin-bottom: 2rem;\r\n    text-align: center;\r\n    font-style: italic;\r\n  }\r\n\r\n  .wr-spin-btn {\r\n    font-family: inherit;\r\n    font-size: 1rem;\r\n    letter-spacing: 0.12em;\r\n    text-transform: uppercase;\r\n    padding: 0.8em 2.6em;\r\n    border: 2px solid #5b8dd9;\r\n    border-radius: 6px;\r\n    background: transparent;\r\n    color: #5b8dd9;\r\n    cursor: pointer;\r\n    outline: none;\r\n    transition: background 0.2s ease, color 0.2s ease;\r\n  }\r\n\r\n  .wr-spin-btn:hover {\r\n    background: rgba(91, 141, 217, 0.1);\r\n  }\r\n\r\n  .wr-spin-btn:disabled {\r\n    opacity: 0.4;\r\n    cursor: default;\r\n  }\r\n\r\n  /* Flicker area */\r\n  .wr-flicker {\r\n    display: none;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    width: 100%;\r\n    max-width: 680px;\r\n  }\r\n\r\n  .wr-flicker.wr-active {\r\n    display: flex;\r\n  }\r\n\r\n  .wr-title {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: clamp(1.2rem, 3vw, 1.9rem);\r\n    color: #c8c0b0;\r\n    text-align: center;\r\n    min-height: 2.5em;\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    line-height: 1.3;\r\n    padding: 0 1rem;\r\n    transition: color 0.15s ease;\r\n  }\r\n\r\n  .wr-title.wr-landed {\r\n    color: #c9913a;\r\n  }\r\n\r\n  .wr-summary-wrap {\r\n    width: 100%;\r\n    max-width: 600px;\r\n    margin-top: 1.8rem;\r\n    opacity: 0;\r\n    transition: opacity 0.6s ease;\r\n  }\r\n\r\n  .wr-summary-wrap.wr-visible {\r\n    opacity: 1;\r\n  }\r\n\r\n  .wr-summary-card {\r\n    border-left: 3px solid #2a2418;\r\n    padding: 1rem 1.4rem;\r\n    background: #141210;\r\n  }\r\n\r\n  .wr-summary-text {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: 0.92rem;\r\n    color: #8a8070;\r\n    line-height: 1.75;\r\n    margin: 0 0 1.1rem;\r\n  }\r\n\r\n  .wr-actions {\r\n    display: flex;\r\n    gap: 1rem;\r\n    align-items: center;\r\n    flex-wrap: wrap;\r\n  }\r\n\r\n  .wr-read-link {\r\n    font-size: 0.78rem;\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    color: #5b8dd9;\r\n    text-decoration: none;\r\n    border-bottom: 1px solid rgba(91,141,217,0.3);\r\n    padding-bottom: 1px;\r\n    transition: border-color 0.2s ease;\r\n  }\r\n\r\n  .wr-read-link:hover {\r\n    border-color: #5b8dd9;\r\n  }\r\n\r\n  .wr-again-btn {\r\n    font-family: inherit;\r\n    font-size: 0.78rem;\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    color: #4a4438;\r\n    background: none;\r\n    border: 1px solid #2a2418;\r\n    border-radius: 4px;\r\n    padding: 0.4em 1em;\r\n    cursor: pointer;\r\n    transition: color 0.2s ease, border-color 0.2s ease;\r\n  }\r\n\r\n  .wr-again-btn:hover {\r\n    color: #8a8070;\r\n    border-color: #4a4030;\r\n  }\r\n\r\n  .wr-error {\r\n    font-size: 0.82rem;\r\n    color: #4a4438;\r\n    font-style: italic;\r\n    font-family: 'Georgia', serif;\r\n    text-align: center;\r\n    margin-top: 1.5rem;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"wr-stage\" id=\"wr-stage\"\u003e\r\n  \u003cp class=\"wr-tagline\"\u003eThe internet is vast. Most of it is this.\u003c/p\u003e","title":"Obscure Wikipedia Article Roulette"},{"content":"\rescapes\r0\r⚠ Do Not Click\rapproach the button\r","permalink":"https://steelwhitetable.ca/apps/randomized-button-runaway/","summary":"\u003cstyle\u003e\r\n  .rbr-stage {\r\n    position: relative;\r\n    width: 100%;\r\n    height: 520px;\r\n    background: #111;\r\n    border-radius: 8px;\r\n    overflow: hidden;\r\n    margin: 1.5rem 0;\r\n    user-select: none;\r\n    -webkit-user-select: none;\r\n  }\r\n\r\n  .rbr-counter-wrap {\r\n    position: absolute;\r\n    top: 1.25rem;\r\n    right: 1.5rem;\r\n    text-align: right;\r\n    pointer-events: none;\r\n  }\r\n\r\n  .rbr-counter-label {\r\n    display: block;\r\n    font-size: 10px;\r\n    letter-spacing: 0.1em;\r\n    text-transform: uppercase;\r\n    color: rgba(255, 255, 255, 0.3);\r\n    font-family: inherit;\r\n  }\r\n\r\n  .rbr-counter-num {\r\n    display: block;\r\n    font-size: 2rem;\r\n    font-weight: 700;\r\n    color: rgba(255, 255, 255, 0.15);\r\n    line-height: 1;\r\n    font-variant-numeric: tabular-nums;\r\n  }\r\n\r\n  .rbr-btn {\r\n    position: absolute;\r\n    padding: 0.9rem 1.75rem;\r\n    background: #c00;\r\n    color: #fff;\r\n    border: none;\r\n    border-radius: 6px;\r\n    font-size: 1rem;\r\n    font-weight: 700;\r\n    letter-spacing: 0.05em;\r\n    text-transform: uppercase;\r\n    cursor: pointer;\r\n    white-space: nowrap;\r\n    box-shadow: 0 0 0 2px rgba(255, 0, 0, 0.3), 0 4px 18px rgba(0, 0, 0, 0.5);\r\n    transition: box-shadow 0.1s;\r\n  }\r\n\r\n  .rbr-btn:hover {\r\n    box-shadow: 0 0 0 2px rgba(255, 0, 0, 0.5), 0 4px 18px rgba(0, 0, 0, 0.5);\r\n  }\r\n\r\n  .rbr-msg {\r\n    position: absolute;\r\n    inset: 0;\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    font-size: 2.5rem;\r\n    font-weight: 700;\r\n    color: #fff;\r\n    pointer-events: none;\r\n    opacity: 0;\r\n    transition: opacity 0.2s;\r\n  }\r\n\r\n  .rbr-msg.rbr-visible {\r\n    opacity: 1;\r\n  }\r\n\r\n  .rbr-hint {\r\n    position: absolute;\r\n    bottom: 1.25rem;\r\n    left: 0;\r\n    right: 0;\r\n    text-align: center;\r\n    font-size: 11px;\r\n    color: rgba(255, 255, 255, 0.18);\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    pointer-events: none;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"rbr-stage\" id=\"rbr-stage\"\u003e\r\n  \u003cdiv class=\"rbr-counter-wrap\"\u003e\r\n    \u003cspan class=\"rbr-counter-label\"\u003eescapes\u003c/span\u003e\r\n    \u003cspan class=\"rbr-counter-num\" id=\"rbr-counter\"\u003e0\u003c/span\u003e\r\n  \u003c/div\u003e\r\n  \u003cbutton class=\"rbr-btn\" id=\"rbr-btn\"\u003e⚠ Do Not Click\u003c/button\u003e\r\n  \u003cdiv class=\"rbr-msg\" id=\"rbr-msg\"\u003e\u003c/div\u003e\r\n  \u003cspan class=\"rbr-hint\" id=\"rbr-hint\"\u003eapproach the button\u003c/span\u003e\r\n\u003c/div\u003e\r\n\u003cscript\u003e\r\n  (function () {\r\n    var stage      = document.getElementById('rbr-stage');\r\n    var btn        = document.getElementById('rbr-btn');\r\n    var counterEl  = document.getElementById('rbr-counter');\r\n    var msgEl      = document.getElementById('rbr-msg');\r\n    var hintEl     = document.getElementById('rbr-hint');\r\n\r\n    var escapes    = 0;\r\n    var caught     = false;\r\n    var msgTimer   = null;\r\n    var THRESHOLD  = 130; // px from button centre\r\n\r\n    function btnRect() {\r\n      return {\r\n        w: btn.offsetWidth,\r\n        h: btn.offsetHeight,\r\n        cx: btn.offsetLeft + btn.offsetWidth  / 2,\r\n        cy: btn.offsetTop  + btn.offsetHeight / 2\r\n      };\r\n    }\r\n\r\n    function teleportAway(cursorX, cursorY) {\r\n      var pad    = 20;\r\n      var sw     = stage.offsetWidth;\r\n      var sh     = stage.offsetHeight;\r\n      var bw     = btn.offsetWidth;\r\n      var bh     = btn.offsetHeight;\r\n      var maxX   = sw - bw - pad;\r\n      var maxY   = sh - bh - pad;\r\n\r\n      // Pick the candidate farthest from the cursor out of 12 attempts\r\n      var best     = null;\r\n      var bestDist = -1;\r\n\r\n      for (var i = 0; i \u003c 12; i++) {\r\n        var x   = pad + Math.random() * (maxX - pad);\r\n        var y   = pad + Math.random() * (maxY - pad);\r\n        var bcx = x + bw / 2;\r\n        var bcy = y + bh / 2;\r\n        var d   = Math.sqrt(Math.pow(cursorX - bcx, 2) + Math.pow(cursorY - bcy, 2));\r\n        if (d \u003e bestDist) { bestDist = d; best = { x: x, y: y }; }\r\n      }\r\n\r\n      btn.style.left = best.x + 'px';\r\n      btn.style.top  = best.y + 'px';\r\n      escapes++;\r\n      counterEl.textContent = escapes;\r\n\r\n      if (hintEl.style.opacity !== '0') hintEl.style.opacity = '0';\r\n    }\r\n\r\n    function center() {\r\n      var sw = stage.offsetWidth;\r\n      var sh = stage.offsetHeight;\r\n      btn.style.left = ((sw - btn.offsetWidth)  / 2) + 'px';\r\n      btn.style.top  = ((sh - btn.offsetHeight) / 2) + 'px';\r\n    }\r\n\r\n    function showMsg(text) {\r\n      clearTimeout(msgTimer);\r\n      msgEl.textContent = text;\r\n      msgEl.classList.add('rbr-visible');\r\n      btn.style.display = 'none';\r\n      msgTimer = setTimeout(function () {\r\n        msgEl.classList.remove('rbr-visible');\r\n        setTimeout(function () {\r\n          caught   = false;\r\n          escapes  = 0;\r\n          counterEl.textContent = '0';\r\n          btn.style.display = '';\r\n          center();\r\n        }, 250);\r\n      }, 2200);\r\n    }\r\n\r\n    stage.addEventListener('mousemove', function (e) {\r\n      if (caught) return;\r\n      var sr  = stage.getBoundingClientRect();\r\n      var cx  = e.clientX - sr.left;\r\n      var cy  = e.clientY - sr.top;\r\n      var b   = btnRect();\r\n      var dist = Math.sqrt(Math.pow(cx - b.cx, 2) + Math.pow(cy - b.cy, 2));\r\n      if (dist \u003c THRESHOLD) teleportAway(cx, cy);\r\n    });\r\n\r\n    btn.addEventListener('touchstart', function (e) {\r\n      e.preventDefault();\r\n      if (caught) return;\r\n      var sr  = stage.getBoundingClientRect();\r\n      var t   = e.touches[0];\r\n      teleportAway(t.clientX - sr.left, t.clientY - sr.top);\r\n    }, { passive: false });\r\n\r\n    btn.addEventListener('click', function () {\r\n      if (caught) return;\r\n      caught = true;\r\n      var msg = escapes === 0\r\n        ? 'You clicked it immediately?!'\r\n        : escapes \u003c 5\r\n          ? 'Lucky catch! (' + escapes + ' escapes)'\r\n          : 'Finally! (' + escapes + ' escapes)';\r\n      showMsg(msg);\r\n    });\r\n\r\n    center();\r\n  })();\r\n\u003c/script\u003e","title":"Randomized Button Runaway"},{"content":"\rWhat's the matter.\nRelease\rAgain.\r","permalink":"https://steelwhitetable.ca/apps/scream-into-gradient/","summary":"\u003cstyle\u003e\r\n  .sg-stage {\r\n    position: relative;\r\n    background: #000;\r\n    border-radius: 8px;\r\n    margin: 1.5rem 0;\r\n    min-height: 480px;\r\n    overflow: hidden;\r\n  }\r\n\r\n  .sg-phase {\r\n    position: absolute;\r\n    inset: 0;\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    justify-content: center;\r\n    padding: 2.5rem 2rem;\r\n    opacity: 0;\r\n    transition: opacity 0.6s ease;\r\n    pointer-events: none;\r\n  }\r\n\r\n  .sg-phase.sg-visible {\r\n    opacity: 1;\r\n    pointer-events: auto;\r\n  }\r\n\r\n  .sg-prompt {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: 0.9rem;\r\n    color: #4a4438;\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    margin: 0 0 1.2rem;\r\n  }\r\n\r\n  .sg-input {\r\n    width: 100%;\r\n    max-width: 520px;\r\n    background: #0d0c0a;\r\n    border: 1px solid #2a2418;\r\n    border-radius: 4px;\r\n    color: #e0d8c8;\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: 1rem;\r\n    line-height: 1.7;\r\n    padding: 0.9rem 1rem;\r\n    resize: none;\r\n    outline: none;\r\n    box-sizing: border-box;\r\n    transition: border-color 0.2s ease;\r\n  }\r\n\r\n  .sg-input:focus {\r\n    border-color: #4a3a28;\r\n  }\r\n\r\n  .sg-release-btn {\r\n    margin-top: 1rem;\r\n    font-family: inherit;\r\n    font-size: 0.78rem;\r\n    letter-spacing: 0.12em;\r\n    text-transform: uppercase;\r\n    color: #c04040;\r\n    background: none;\r\n    border: 1px solid rgba(192, 64, 64, 0.4);\r\n    border-radius: 4px;\r\n    padding: 0.5em 1.6em;\r\n    cursor: pointer;\r\n    transition: background 0.2s ease, border-color 0.2s ease;\r\n  }\r\n\r\n  .sg-release-btn:hover {\r\n    background: rgba(192, 64, 64, 0.08);\r\n    border-color: rgba(192, 64, 64, 0.7);\r\n  }\r\n\r\n  .sg-big-text {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: clamp(1.3rem, 4vw, 2.2rem);\r\n    color: #fff;\r\n    text-align: center;\r\n    line-height: 1.5;\r\n    max-width: 700px;\r\n    word-break: break-word;\r\n    margin: 0;\r\n  }\r\n\r\n  #sg-canvas {\r\n    position: absolute;\r\n    inset: -20%;\r\n    width: 140%;\r\n    height: 140%;\r\n    background: #000;\r\n    z-index: 1;\r\n    pointer-events: none;\r\n  }\r\n\r\n  .sg-again-btn {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: 1rem;\r\n    font-style: italic;\r\n    color: #3a3428;\r\n    background: none;\r\n    border: none;\r\n    cursor: pointer;\r\n    letter-spacing: 0.04em;\r\n    transition: color 0.3s ease;\r\n    padding: 0;\r\n  }\r\n\r\n  .sg-again-btn:hover {\r\n    color: #7a6a58;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"sg-stage\" id=\"sg-stage\"\u003e\r\n  \u003cdiv class=\"sg-phase sg-visible\" id=\"sg-input-phase\"\u003e\r\n    \u003cp class=\"sg-prompt\"\u003eWhat's the matter.\u003c/p\u003e","title":"Scream Into Gradient"},{"content":"\rMove your mouse.\rMouse only.\r","permalink":"https://steelwhitetable.ca/apps/the-cursors-shadow/","summary":"\u003cstyle\u003e\r\n  .cs-stage {\r\n    position: relative;\r\n    background: #0a0a0c;\r\n    border-radius: 8px;\r\n    margin: 1.5rem 0;\r\n    height: 480px;\r\n    cursor: none;\r\n    overflow: hidden;\r\n  }\r\n\r\n  #cs-canvas {\r\n    display: block;\r\n    width: 100%;\r\n    height: 100%;\r\n  }\r\n\r\n  .cs-hint {\r\n    position: absolute;\r\n    bottom: 1.2rem;\r\n    width: 100%;\r\n    text-align: center;\r\n    font-size: 0.68rem;\r\n    color: rgba(255, 255, 255, 0.1);\r\n    letter-spacing: 0.1em;\r\n    text-transform: uppercase;\r\n    pointer-events: none;\r\n    transition: opacity 1.5s ease;\r\n  }\r\n\r\n  .cs-hint.cs-gone {\r\n    opacity: 0;\r\n  }\r\n\r\n  .cs-touch {\r\n    position: absolute;\r\n    inset: 0;\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    font-family: 'Georgia', serif;\r\n    font-size: 0.85rem;\r\n    font-style: italic;\r\n    color: rgba(255, 255, 255, 0.12);\r\n    pointer-events: none;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"cs-stage\" id=\"cs-stage\"\u003e\r\n  \u003ccanvas id=\"cs-canvas\"\u003e\u003c/canvas\u003e\r\n  \u003cdiv class=\"cs-hint\" id=\"cs-hint\"\u003eMove your mouse.\u003c/div\u003e\r\n  \u003cdiv class=\"cs-touch\" id=\"cs-touch\" style=\"display:none\"\u003eMouse only.\u003c/div\u003e\r\n\u003c/div\u003e\r\n\u003cscript\u003e\r\n  (function () {\r\n    var stage  = document.getElementById('cs-stage');\r\n    var canvas = document.getElementById('cs-canvas');\r\n    var hint   = document.getElementById('cs-hint');\r\n    var touch  = document.getElementById('cs-touch');\r\n    var ctx    = canvas.getContext('2d');\r\n\r\n    var W = 0, H = 0;\r\n    var mx = 0, my = 0;   // cursor position\r\n    var sx = 0, sy = 0;   // shadow position\r\n    var vx = 0, vy = 0;   // shadow velocity\r\n    var hasInteracted = false;\r\n    var cursorInside = false;\r\n\r\n    var STIFFNESS = 0.06;\r\n    var DAMPING   = 0.72;\r\n\r\n    // Audio\r\n    var audioCtx   = null;\r\n    var osc        = null;\r\n    var gainNode   = null;\r\n    var smoothFreq = 40;\r\n    var smoothGain = 0;\r\n\r\n    // ── Sizing ────────────────────────────────────────────────────────────────\r\n\r\n    function resize() {\r\n      W = stage.offsetWidth;\r\n      H = stage.offsetHeight;\r\n      canvas.width  = W;\r\n      canvas.height = H;\r\n      if (!hasInteracted) {\r\n        sx = W / 2; sy = H / 2;\r\n        mx = W / 2; my = H / 2;\r\n      }\r\n    }\r\n\r\n    resize();\r\n    window.addEventListener('resize', resize);\r\n\r\n    // ── Audio ─────────────────────────────────────────────────────────────────\r\n\r\n    function initAudio() {\r\n      if (audioCtx) return;\r\n      try {\r\n        audioCtx  = new (window.AudioContext || window.webkitAudioContext)();\r\n        osc       = audioCtx.createOscillator();\r\n        gainNode  = audioCtx.createGain();\r\n        osc.type  = 'sine';\r\n        osc.frequency.value = 40;\r\n        gainNode.gain.value = 0;\r\n        osc.connect(gainNode);\r\n        gainNode.connect(audioCtx.destination);\r\n        osc.start();\r\n      } catch (e) {}\r\n    }\r\n\r\n    function updateAudio(spd) {\r\n      if (!audioCtx) return;\r\n      var targetFreq = 40 + Math.min(spd * 2.8, 70);\r\n      var targetGain = cursorInside ? Math.min(spd * 0.012, 0.18) : 0;\r\n      smoothFreq += (targetFreq - smoothFreq) * 0.05;\r\n      smoothGain += (targetGain - smoothGain) * 0.08;\r\n      try {\r\n        osc.frequency.value = smoothFreq;\r\n        gainNode.gain.value = smoothGain;\r\n      } catch (e) {}\r\n    }\r\n\r\n    // ── Input ─────────────────────────────────────────────────────────────────\r\n\r\n    // Detect touch-only devices and show note\r\n    if (!window.matchMedia('(pointer: fine)').matches) {\r\n      hint.style.display  = 'none';\r\n      touch.style.display = 'flex';\r\n    }\r\n\r\n    stage.addEventListener('mouseenter', function () { cursorInside = true; });\r\n    stage.addEventListener('mouseleave', function () { cursorInside = false; });\r\n\r\n    stage.addEventListener('mousemove', function (e) {\r\n      var rect = canvas.getBoundingClientRect();\r\n      mx = e.clientX - rect.left;\r\n      my = e.clientY - rect.top;\r\n\r\n      if (!hasInteracted) {\r\n        hasInteracted = true;\r\n        sx = mx; sy = my;   // start shadow under cursor\r\n        hint.classList.add('cs-gone');\r\n        initAudio();\r\n      }\r\n    });\r\n\r\n    // ── Physics ───────────────────────────────────────────────────────────────\r\n\r\n    function updatePhysics() {\r\n      vx += (mx - sx) * STIFFNESS;\r\n      vy += (my - sy) * STIFFNESS;\r\n      vx *= DAMPING;\r\n      vy *= DAMPING;\r\n      sx += vx;\r\n      sy += vy;\r\n    }\r\n\r\n    function shadowSpeed() {\r\n      return Math.sqrt(vx * vx + vy * vy);\r\n    }\r\n\r\n    // ── Draw ──────────────────────────────────────────────────────────────────\r\n\r\n    function draw() {\r\n      ctx.clearRect(0, 0, W, H);\r\n\r\n      var spd  = shadowSpeed();\r\n      var dist = Math.sqrt((mx - sx) * (mx - sx) + (my - sy) * (my - sy));\r\n\r\n      // Connecting thread\r\n      if (dist \u003e 10) {\r\n        var lineAlpha = Math.min(dist / 280, 0.12);\r\n        ctx.beginPath();\r\n        ctx.moveTo(mx, my);\r\n        ctx.lineTo(sx, sy);\r\n        ctx.strokeStyle = 'rgba(200,180,140,' + lineAlpha.toFixed(3) + ')';\r\n        ctx.lineWidth = 1;\r\n        ctx.stroke();\r\n      }\r\n\r\n      // Shadow blob — radial gradient (amber centre, transparent edge)\r\n      var radius  = 26 + Math.min(spd * 0.65, 14);\r\n      var cAlpha  = 0.3  + Math.min(spd * 0.025, 0.5);\r\n      var grad    = ctx.createRadialGradient(sx, sy, 0, sx, sy, radius);\r\n      grad.addColorStop(0,   'rgba(180,145,80,'  + cAlpha.toFixed(3) + ')');\r\n      grad.addColorStop(0.5, 'rgba(140,110,55,'  + (cAlpha * 0.5).toFixed(3) + ')');\r\n      grad.addColorStop(1,   'rgba(100, 70,20,0)');\r\n      ctx.beginPath();\r\n      ctx.arc(sx, sy, radius, 0, Math.PI * 2);\r\n      ctx.fillStyle = grad;\r\n      ctx.fill();\r\n\r\n      // Outer glow ring when moving\r\n      if (spd \u003e 2) {\r\n        var ringAlpha = Math.min(spd * 0.007, 0.12);\r\n        ctx.beginPath();\r\n        ctx.arc(sx, sy, radius + 10, 0, Math.PI * 2);\r\n        ctx.strokeStyle = 'rgba(180,140,60,' + ringAlpha.toFixed(3) + ')';\r\n        ctx.lineWidth = 2;\r\n        ctx.stroke();\r\n      }\r\n\r\n      // Real cursor — small white dot (only when inside stage)\r\n      if (cursorInside) {\r\n        ctx.beginPath();\r\n        ctx.arc(mx, my, 4, 0, Math.PI * 2);\r\n        ctx.fillStyle = 'rgba(255,255,255,0.75)';\r\n        ctx.fill();\r\n        ctx.beginPath();\r\n        ctx.arc(mx, my, 1.5, 0, Math.PI * 2);\r\n        ctx.fillStyle = 'rgba(255,255,255,1)';\r\n        ctx.fill();\r\n      }\r\n    }\r\n\r\n    // ── Loop ──────────────────────────────────────────────────────────────────\r\n\r\n    function loop() {\r\n      updatePhysics();\r\n      draw();\r\n      updateAudio(shadowSpeed());\r\n      requestAnimationFrame(loop);\r\n    }\r\n\r\n    loop();\r\n  })();\r\n\u003c/script\u003e","title":"The Cursor's Shadow"},{"content":"\rClick me\r","permalink":"https://steelwhitetable.ca/apps/the-desperate-button/","summary":"\u003cstyle\u003e\r\n  .db-stage {\r\n    background: #111;\r\n    border-radius: 8px;\r\n    min-height: 320px;\r\n    margin: 1.5rem 0;\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    justify-content: center;\r\n    gap: 1.8rem;\r\n    padding: 3rem 2rem;\r\n    user-select: none;\r\n    -webkit-user-select: none;\r\n  }\r\n\r\n  .db-plea {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: clamp(1rem, 2.5vw, 1.3rem);\r\n    color: #c8c0b0;\r\n    text-align: center;\r\n    min-height: 1.6em;\r\n    letter-spacing: 0.01em;\r\n    transition: opacity 0.3s ease;\r\n  }\r\n\r\n  .db-plea.db-hidden {\r\n    opacity: 0;\r\n  }\r\n\r\n  .db-btn {\r\n    font-family: inherit;\r\n    font-size: 1.1rem;\r\n    letter-spacing: 0.06em;\r\n    text-transform: uppercase;\r\n    padding: 0.85em 2.4em;\r\n    border: 2px solid #e05050;\r\n    border-radius: 6px;\r\n    background: transparent;\r\n    color: #e05050;\r\n    cursor: pointer;\r\n    outline: none;\r\n    position: relative;\r\n    transition: background 0.4s ease, color 0.4s ease, border-color 0.4s ease, opacity 0.4s ease;\r\n  }\r\n\r\n  .db-btn:hover:not(.db-btn--held) {\r\n    background: rgba(224, 80, 80, 0.08);\r\n  }\r\n\r\n  /* Animation stages */\r\n  .db-btn--concern {\r\n    animation: db-tremble 0.15s ease-in-out infinite;\r\n  }\r\n\r\n  .db-btn--pleading {\r\n    animation: db-shake 0.1s ease-in-out infinite;\r\n  }\r\n\r\n  .db-btn--desperate {\r\n    animation: db-shake 0.07s ease-in-out infinite;\r\n    border-color: #a03030;\r\n    color: #a03030;\r\n  }\r\n\r\n  .db-btn--panic {\r\n    animation: db-panic 0.05s linear infinite;\r\n    border-color: #702020;\r\n    color: #702020;\r\n  }\r\n\r\n  /* Release outcomes */\r\n  .db-btn--devastated {\r\n    animation: none;\r\n    border-color: #444;\r\n    color: #444;\r\n    opacity: 0.5;\r\n    transform: translateY(4px);\r\n    cursor: default;\r\n  }\r\n\r\n  .db-btn--relieved {\r\n    animation: none;\r\n    border-color: #50a060;\r\n    color: #50a060;\r\n    transition: border-color 0.5s ease, color 0.5s ease;\r\n  }\r\n\r\n  @keyframes db-tremble {\r\n    0%, 100% { transform: translate(0, 0); }\r\n    25%       { transform: translate(-1px, 1px); }\r\n    75%       { transform: translate(1px, -1px); }\r\n  }\r\n\r\n  @keyframes db-shake {\r\n    0%, 100% { transform: translate(0, 0); }\r\n    20%       { transform: translate(-2px, 1px); }\r\n    40%       { transform: translate(2px, -1px); }\r\n    60%       { transform: translate(-2px, -1px); }\r\n    80%       { transform: translate(2px, 1px); }\r\n  }\r\n\r\n  @keyframes db-panic {\r\n    0%   { transform: translate(-3px, 1px) rotate(-0.5deg); }\r\n    20%  { transform: translate(3px, -2px) rotate(0.5deg); }\r\n    40%  { transform: translate(-4px, 2px) rotate(-1deg); }\r\n    60%  { transform: translate(4px, -1px) rotate(0.8deg); }\r\n    80%  { transform: translate(-2px, 3px) rotate(-0.3deg); }\r\n    100% { transform: translate(2px, -3px) rotate(0.6deg); }\r\n  }\r\n\r\n  .db-timer {\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: clamp(1.4rem, 3.5vw, 2.2rem);\r\n    color: #c9913a;\r\n    letter-spacing: 0.04em;\r\n    min-height: 1.4em;\r\n    text-align: center;\r\n    opacity: 0;\r\n    transition: opacity 0.2s ease;\r\n  }\r\n\r\n  .db-timer.db-active {\r\n    opacity: 1;\r\n  }\r\n\r\n  .db-ending {\r\n    font-size: 0.82rem;\r\n    color: #5a5248;\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    min-height: 1.2em;\r\n    text-align: center;\r\n    transition: opacity 0.5s ease;\r\n  }\r\n\r\n  .db-ending.db-hidden {\r\n    opacity: 0;\r\n  }\r\n\r\n  .db-best {\r\n    font-size: 0.75rem;\r\n    color: #3a3530;\r\n    letter-spacing: 0.1em;\r\n    text-transform: uppercase;\r\n    text-align: center;\r\n    min-height: 1.2em;\r\n    transition: color 0.4s ease;\r\n  }\r\n\r\n  .db-best.db-best--new {\r\n    color: #c9913a;\r\n  }\r\n\r\n  .db-ending-sub {\r\n    font-size: 0.85em;\r\n    opacity: 0.7;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"db-stage\" id=\"db-stage\"\u003e\r\n  \u003cdiv class=\"db-plea db-hidden\" id=\"db-plea\"\u003e\u003c/div\u003e\r\n  \u003cdiv class=\"db-timer\" id=\"db-timer\"\u003e\u003c/div\u003e\r\n  \u003cbutton class=\"db-btn\" id=\"db-btn\"\u003eClick me\u003c/button\u003e\r\n  \u003cdiv class=\"db-ending db-hidden\" id=\"db-ending\"\u003e\u003c/div\u003e\r\n  \u003cdiv class=\"db-best\" id=\"db-best\"\u003e\u003c/div\u003e\r\n\u003c/div\u003e\r\n\u003cscript\u003e\r\n  (function () {\r\n    var btn      = document.getElementById('db-btn');\r\n    var pleaEl   = document.getElementById('db-plea');\r\n    var endEl    = document.getElementById('db-ending');\r\n    var timerEl  = document.getElementById('db-timer');\r\n    var bestEl   = document.getElementById('db-best');\r\n\r\n    var STORAGE_KEY = 'db-best-ms';\r\n\r\n    function fmtMs(ms) {\r\n      return (ms / 1000).toFixed(2) + 's';\r\n    }\r\n\r\n    function loadBest() {\r\n      try { return parseInt(localStorage.getItem(STORAGE_KEY), 10) || 0; } catch (e) { return 0; }\r\n    }\r\n\r\n    function saveBest(ms) {\r\n      try { localStorage.setItem(STORAGE_KEY, ms); } catch (e) {}\r\n    }\r\n\r\n    function renderBest() {\r\n      var best = loadBest();\r\n      bestEl.textContent = best \u003e 0 ? 'Best: ' + fmtMs(best) : '';\r\n    }\r\n\r\n    renderBest();\r\n\r\n    var PLEAS = {\r\n      concern:    [\"Please don't.\", \"Wait.\", \"Hold on.\"],\r\n      pleading:   [\"I'm asking nicely.\", \"Please.\", \"Don't do this.\"],\r\n      desperate:  [\"I need this. Please.\", \"You don't have to do this.\", \"I'm begging you.\"],\r\n      panic:      [\"I have a family.\", \"DON'T.\", \"PLEASE.\", \"NOT LIKE THIS.\"]\r\n    };\r\n\r\n    var ENDINGS = {\r\n      devastation: [\"I knew you would.\", \"Of course.\", \"Fine.\"],\r\n      relief:      [\"Thank you.\", \"You came back to me.\", \"I knew you wouldn't.\"],\r\n      nothing:     []\r\n    };\r\n\r\n    var STAGES = ['concern', 'pleading', 'desperate', 'panic'];\r\n    var STAGE_CLASSES = {\r\n      concern:   'db-btn--concern',\r\n      pleading:  'db-btn--pleading',\r\n      desperate: 'db-btn--desperate',\r\n      panic:     'db-btn--panic'\r\n    };\r\n    var STAGE_DELAYS = [0, 2000, 5000, 10000];\r\n\r\n    var held = false;\r\n    var timers = [];\r\n    var currentStage = -1;\r\n    var resetTimer = null;\r\n    var pressStart = 0;\r\n    var tickInterval = null;\r\n\r\n    function pick(arr) {\r\n      return arr[Math.floor(Math.random() * arr.length)];\r\n    }\r\n\r\n    function clearTimers() {\r\n      for (var i = 0; i \u003c timers.length; i++) clearTimeout(timers[i]);\r\n      timers = [];\r\n    }\r\n\r\n    function setStage(name) {\r\n      STAGES.forEach(function (s) {\r\n        btn.classList.remove(STAGE_CLASSES[s]);\r\n      });\r\n      if (name) {\r\n        btn.classList.add(STAGE_CLASSES[name]);\r\n        pleaEl.textContent = pick(PLEAS[name]);\r\n        pleaEl.classList.remove('db-hidden');\r\n      }\r\n    }\r\n\r\n    function onPress() {\r\n      if (held) return;\r\n      held = true;\r\n      currentStage = -1;\r\n      pressStart = Date.now();\r\n\r\n      btn.classList.remove('db-btn--devastated', 'db-btn--relieved');\r\n      pleaEl.classList.add('db-hidden');\r\n      endEl.classList.add('db-hidden');\r\n      endEl.textContent = '';\r\n      bestEl.classList.remove('db-best--new');\r\n      clearTimeout(resetTimer);\r\n\r\n      timerEl.textContent = '0.00s';\r\n      timerEl.classList.add('db-active');\r\n      clearInterval(tickInterval);\r\n      tickInterval = setInterval(function () {\r\n        timerEl.textContent = fmtMs(Date.now() - pressStart);\r\n      }, 100);\r\n\r\n      var immediate = Math.random() \u003c 0.5;\r\n\r\n      if (immediate) {\r\n        setStage('panic');\r\n        currentStage = 3;\r\n      } else {\r\n        // Schedule escalating stages\r\n        STAGE_DELAYS.forEach(function (delay, i) {\r\n          timers.push(setTimeout(function () {\r\n            if (!held) return;\r\n            setStage(STAGES[i]);\r\n            currentStage = i;\r\n          }, delay));\r\n        });\r\n      }\r\n    }\r\n\r\n    function onRelease() {\r\n      if (!held) return;\r\n      held = false;\r\n      clearTimers();\r\n      clearInterval(tickInterval);\r\n\r\n      var elapsed = Date.now() - pressStart;\r\n      timerEl.classList.remove('db-active');\r\n\r\n      var prevBest = loadBest();\r\n      var isRecord = elapsed \u003e prevBest;\r\n      if (isRecord) saveBest(elapsed);\r\n      renderBest();\r\n\r\n      STAGES.forEach(function (s) {\r\n        btn.classList.remove(STAGE_CLASSES[s]);\r\n      });\r\n\r\n      var roll = Math.random();\r\n      var ending = roll \u003c 0.33 ? 'devastation' : roll \u003c 0.66 ? 'relief' : 'nothing';\r\n\r\n      pleaEl.classList.add('db-hidden');\r\n\r\n      if (ending === 'devastation' || ending === 'relief') {\r\n        var recordLine = isRecord ? 'New record!' : 'Your best: ' + fmtMs(prevBest);\r\n        var endingKey = ending === 'devastation' ? 'devastation' : 'relief';\r\n        if (ending === 'devastation') {\r\n          btn.classList.add('db-btn--devastated');\r\n        } else {\r\n          btn.classList.add('db-btn--relieved');\r\n        }\r\n        var sub = document.createElement('span');\r\n        sub.className = 'db-ending-sub';\r\n        sub.textContent = recordLine;\r\n        endEl.textContent = '';\r\n        endEl.appendChild(document.createTextNode(pick(ENDINGS[endingKey])));\r\n        endEl.appendChild(document.createElement('br'));\r\n        endEl.appendChild(sub);\r\n        endEl.classList.remove('db-hidden');\r\n        if (isRecord) bestEl.classList.add('db-best--new');\r\n        resetTimer = setTimeout(reset, 3000);\r\n      } else {\r\n        // Nothing — silent immediate reset\r\n        reset();\r\n      }\r\n    }\r\n\r\n    function reset() {\r\n      btn.classList.remove('db-btn--devastated', 'db-btn--relieved');\r\n      STAGES.forEach(function (s) { btn.classList.remove(STAGE_CLASSES[s]); });\r\n      pleaEl.classList.add('db-hidden');\r\n      endEl.classList.add('db-hidden');\r\n      bestEl.classList.remove('db-best--new');\r\n      pleaEl.textContent = '';\r\n      endEl.textContent = '';\r\n      timerEl.textContent = '';\r\n      timerEl.classList.remove('db-active');\r\n    }\r\n\r\n    // Mouse\r\n    btn.addEventListener('mousedown', function (e) { e.preventDefault(); onPress(); });\r\n    document.addEventListener('mouseup', function () { if (held) onRelease(); });\r\n    btn.addEventListener('mouseleave', function () { if (held) onRelease(); });\r\n\r\n    // Touch\r\n    btn.addEventListener('touchstart', function (e) { e.preventDefault(); onPress(); }, { passive: false });\r\n    document.addEventListener('touchend', function () { if (held) onRelease(); });\r\n  })();\r\n\u003c/script\u003e","title":"The Desperate Button"},{"content":"\rDrag the corner to resize. Or try your browser window.\n","permalink":"https://steelwhitetable.ca/apps/the-window-compactor/","summary":"\u003cstyle\u003e\r\n  .wc-outer {\r\n    margin: 1.5rem 0;\r\n  }\r\n\r\n  .wc-container {\r\n    position: relative;\r\n    background: #0d0c0a;\r\n    border-radius: 8px;\r\n    overflow: hidden;\r\n    display: inline-block;\r\n    line-height: 0;\r\n  }\r\n\r\n  .wc-badge {\r\n    position: absolute;\r\n    top: 10px;\r\n    right: 10px;\r\n    z-index: 10;\r\n    font-size: 0.62rem;\r\n    letter-spacing: 0.1em;\r\n    text-transform: uppercase;\r\n    padding: 0.3em 0.65em;\r\n    border-radius: 3px;\r\n    font-family: 'Courier New', Courier, monospace;\r\n    pointer-events: none;\r\n  }\r\n\r\n  .wc-badge[data-mode=\"pressure\"] {\r\n    background: rgba(91, 141, 217, 0.12);\r\n    color: #5b8dd9;\r\n    border: 1px solid rgba(91, 141, 217, 0.25);\r\n  }\r\n\r\n  .wc-badge[data-mode=\"aggression\"] {\r\n    background: rgba(200, 70, 70, 0.12);\r\n    color: #d06060;\r\n    border: 1px solid rgba(200, 70, 70, 0.25);\r\n  }\r\n\r\n  .wc-badge[data-mode=\"resistance\"] {\r\n    background: rgba(201, 145, 58, 0.12);\r\n    color: #c9913a;\r\n    border: 1px solid rgba(201, 145, 58, 0.25);\r\n  }\r\n\r\n  .wc-handle {\r\n    position: absolute;\r\n    bottom: 3px;\r\n    right: 3px;\r\n    width: 18px;\r\n    height: 18px;\r\n    cursor: se-resize;\r\n    z-index: 10;\r\n    background-image: radial-gradient(circle, rgba(255,255,255,0.22) 1px, transparent 1px);\r\n    background-size: 5px 5px;\r\n    background-repeat: repeat;\r\n    background-position: 1px 1px;\r\n  }\r\n\r\n  @keyframes wc-shudder {\r\n    0%,100% { transform: translate(0,0); }\r\n    15%      { transform: translate(-3px, 2px); }\r\n    30%      { transform: translate(3px,-2px); }\r\n    45%      { transform: translate(-2px,-2px); }\r\n    60%      { transform: translate(2px, 3px); }\r\n    75%      { transform: translate(-3px,-1px); }\r\n    90%      { transform: translate(2px, 1px); }\r\n  }\r\n\r\n  .wc-shudder {\r\n    animation: wc-shudder 0.35s ease;\r\n  }\r\n\r\n  .wc-hint {\r\n    font-size: 0.72rem;\r\n    color: #3a3428;\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    margin-top: 0.8rem;\r\n    font-family: inherit;\r\n  }\r\n\u003c/style\u003e\r\n\u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js\"\u003e\u003c/script\u003e\r\n\u003cdiv class=\"wc-outer\" id=\"wc-outer\"\u003e\r\n  \u003cdiv class=\"wc-container\" id=\"wc-container\"\u003e\r\n    \u003cspan class=\"wc-badge\" id=\"wc-badge\"\u003e\u003c/span\u003e\r\n    \u003ccanvas id=\"wc-canvas\"\u003e\u003c/canvas\u003e\r\n    \u003cdiv class=\"wc-handle\" id=\"wc-handle\"\u003e\u003c/div\u003e\r\n  \u003c/div\u003e\r\n  \u003cp class=\"wc-hint\"\u003eDrag the corner to resize. Or try your browser window.\u003c/p\u003e","title":"The Window Compactor"},{"content":"\rHeight\u0026nbsp;0\u0026nbsp;units\rBest\u0026nbsp;0\u0026nbsp;units\rTower Balance\rStack shapes as high as you can.\rStart\rNext\r← → move\nZ X rotate\n↓ Space drop\rTap sides to move \u0026middot; Swipe down to drop\r","permalink":"https://steelwhitetable.ca/apps/tower-balance/","summary":"\u003cstyle\u003e\r\n  .tbc-wrap {\r\n    margin: 1.5rem 0;\r\n    user-select: none;\r\n    -webkit-user-select: none;\r\n  }\r\n\r\n  .tbc-stats {\r\n    display: flex;\r\n    gap: 2rem;\r\n    margin-bottom: 0.75rem;\r\n    font-size: 0.8rem;\r\n    letter-spacing: 0.09em;\r\n    text-transform: uppercase;\r\n    color: #555;\r\n  }\r\n\r\n  .tbc-stats-val {\r\n    color: #c9913a;\r\n  }\r\n\r\n  .tbc-game {\r\n    display: flex;\r\n    gap: 14px;\r\n    align-items: flex-start;\r\n  }\r\n\r\n  .tbc-canvas-wrap {\r\n    position: relative;\r\n    flex-shrink: 0;\r\n    border-radius: 6px;\r\n    overflow: hidden;\r\n    box-shadow: 0 4px 28px rgba(0,0,0,0.55);\r\n    line-height: 0;\r\n  }\r\n\r\n  #tbc-canvas {\r\n    display: block;\r\n    max-width: 100%;\r\n  }\r\n\r\n  .tbc-overlay {\r\n    position: absolute;\r\n    inset: 0;\r\n    background: rgba(8,8,8,0.90);\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    justify-content: center;\r\n    gap: 0.9rem;\r\n    text-align: center;\r\n    padding: 2.5rem 2rem;\r\n  }\r\n\r\n  .tbc-overlay.tbc-hidden {\r\n    display: none;\r\n  }\r\n\r\n  .tbc-ov-title {\r\n    font-size: 1.25rem;\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    color: #d8d0c0;\r\n  }\r\n\r\n  .tbc-ov-height {\r\n    font-size: 2.6rem;\r\n    color: #c9913a;\r\n    letter-spacing: 0.04em;\r\n    min-height: 1.2em;\r\n    line-height: 1;\r\n  }\r\n\r\n  .tbc-ov-msg {\r\n    font-size: 0.82rem;\r\n    color: #555;\r\n    letter-spacing: 0.06em;\r\n    min-height: 1.2em;\r\n  }\r\n\r\n  .tbc-ov-btn {\r\n    margin-top: 0.4rem;\r\n    font-family: inherit;\r\n    font-size: 0.85rem;\r\n    letter-spacing: 0.12em;\r\n    text-transform: uppercase;\r\n    padding: 0.65em 2.2em;\r\n    border: 1.5px solid #7eb8d4;\r\n    border-radius: 4px;\r\n    background: transparent;\r\n    color: #7eb8d4;\r\n    cursor: pointer;\r\n    transition: background 0.18s;\r\n  }\r\n\r\n  .tbc-ov-btn:hover {\r\n    background: rgba(126,184,212,0.13);\r\n  }\r\n\r\n  .tbc-sidebar {\r\n    display: flex;\r\n    flex-direction: column;\r\n    gap: 6px;\r\n  }\r\n\r\n  .tbc-sidebar-lbl {\r\n    font-size: 0.68rem;\r\n    letter-spacing: 0.12em;\r\n    text-transform: uppercase;\r\n    color: #3a3a3a;\r\n  }\r\n\r\n  .tbc-preview-wrap {\r\n    background: #1a1a1a;\r\n    border-radius: 4px;\r\n    border: 1px solid #252525;\r\n    overflow: hidden;\r\n    line-height: 0;\r\n  }\r\n\r\n  #tbc-preview {\r\n    display: block;\r\n  }\r\n\r\n  .tbc-hint {\r\n    margin-top: 0.6rem;\r\n    font-size: 0.66rem;\r\n    line-height: 1.9;\r\n    color: #383838;\r\n    letter-spacing: 0.03em;\r\n  }\r\n\r\n  .tbc-hint-key {\r\n    color: #4a4a4a;\r\n    font-style: normal;\r\n  }\r\n\r\n  .tbc-mobile-hint {\r\n    display: none;\r\n    margin-top: 0.65rem;\r\n    font-size: 0.7rem;\r\n    color: #3a3a3a;\r\n    letter-spacing: 0.05em;\r\n    text-align: center;\r\n  }\r\n\r\n  @media (max-width: 560px) {\r\n    .tbc-game {\r\n      flex-direction: column;\r\n      align-items: center;\r\n    }\r\n    .tbc-sidebar {\r\n      flex-direction: row;\r\n      align-items: center;\r\n      gap: 18px;\r\n      order: -1;\r\n      width: 100%;\r\n      justify-content: center;\r\n    }\r\n    .tbc-hint { display: none; }\r\n    .tbc-mobile-hint { display: block; }\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"tbc-wrap\"\u003e\r\n  \u003cdiv class=\"tbc-stats\"\u003e\r\n    \u003cdiv\u003eHeight\u0026nbsp;\u003cspan class=\"tbc-stats-val\" id=\"tbc-height\"\u003e0\u003c/span\u003e\u0026nbsp;units\u003c/div\u003e\r\n    \u003cdiv\u003eBest\u0026nbsp;\u003cspan class=\"tbc-stats-val\" id=\"tbc-best\"\u003e0\u003c/span\u003e\u0026nbsp;units\u003c/div\u003e\r\n  \u003c/div\u003e\r\n  \u003cdiv class=\"tbc-game\"\u003e\r\n    \u003cdiv class=\"tbc-canvas-wrap\"\u003e\r\n      \u003ccanvas id=\"tbc-canvas\" width=\"400\" height=\"600\"\u003e\u003c/canvas\u003e\r\n      \u003cdiv class=\"tbc-overlay\" id=\"tbc-overlay\"\u003e\r\n        \u003cdiv class=\"tbc-ov-title\" id=\"tbc-ov-title\"\u003eTower Balance\u003c/div\u003e\r\n        \u003cdiv class=\"tbc-ov-height\" id=\"tbc-ov-height\"\u003e\u003c/div\u003e\r\n        \u003cdiv class=\"tbc-ov-msg\" id=\"tbc-ov-msg\"\u003eStack shapes as high as you can.\u003c/div\u003e\r\n        \u003cbutton class=\"tbc-ov-btn\" id=\"tbc-ov-btn\"\u003eStart\u003c/button\u003e\r\n      \u003c/div\u003e\r\n    \u003c/div\u003e\u003cdiv class=\"tbc-sidebar\"\u003e\r\n\u003cdiv class=\"tbc-sidebar-lbl\"\u003eNext\u003c/div\u003e\r\n\u003cdiv class=\"tbc-preview-wrap\"\u003e\u003ccanvas id=\"tbc-preview\" width=\"80\" height=\"80\"\u003e\u003c/canvas\u003e\u003c/div\u003e\r\n\u003cdiv class=\"tbc-hint\"\u003e\u003cspan class=\"tbc-hint-key\"\u003e← →\u003c/span\u003e move\u003cbr\u003e\u003cspan class=\"tbc-hint-key\"\u003eZ X\u003c/span\u003e rotate\u003cbr\u003e\u003cspan class=\"tbc-hint-key\"\u003e↓ Space\u003c/span\u003e drop\u003c/div\u003e\r\n\u003c/div\u003e\r\n\u003c/div\u003e\r\n\u003cdiv class=\"tbc-mobile-hint\"\u003eTap sides to move \u0026middot; Swipe down to drop\u003c/div\u003e\r\n\u003c/div\u003e\r\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/poly-decomp@0.3.0/build/decomp.min.js\"\u003e\u003c/script\u003e\r\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/matter-js@0.19.0/build/matter.min.js\"\u003e\u003c/script\u003e\r\n\u003cscript\u003e\r\n(function () {\r\n  'use strict';\r\n\r\n  var Engine  = Matter.Engine;\r\n  var Runner  = Matter.Runner;\r\n  var Bodies  = Matter.Bodies;\r\n  var Body    = Matter.Body;\r\n  var World   = Matter.World;\r\n  var Common  = Matter.Common;\r\n\r\n  if (window.decomp) Common.setDecomp(window.decomp);\r\n\r\n  // ─── Constants ────────────────────────────────────────────────────────────\r\n  var CW       = 400;\r\n  var CH       = 600;\r\n  var GROUND_Y = 2000;\r\n  var UNIT     = 40;\r\n  var LS_KEY   = 'tbc-best';\r\n\r\n  // ─── Shape definitions (vertices centred around 0,0) ─────────────────────\r\n  // Convex shapes work without poly-decomp.\r\n  // L-Shape and T-Shape are concave and need poly-decomp.\r\n  var SHAPES = [\r\n    {\r\n      name: 'Rectangle', color: '#7eb8d4',\r\n      verts: [{x:-30,y:-14},{x:30,y:-14},{x:30,y:14},{x:-30,y:14}]\r\n    },\r\n    {\r\n      name: 'Trapezoid', color: '#c4a44a',\r\n      verts: [{x:-13,y:-20},{x:13,y:-20},{x:28,y:20},{x:-28,y:20}]\r\n    },\r\n    {\r\n      name: 'Triangle', color: '#c47070',\r\n      verts: [{x:-28,y:20},{x:28,y:20},{x:-28,y:-20}]\r\n    },\r\n    {\r\n      name: 'Parallelogram', color: '#6aad6a',\r\n      verts: [{x:-28,y:-15},{x:18,y:-15},{x:28,y:15},{x:-18,y:15}]\r\n    },\r\n    {\r\n      name: 'L-Shape', color: '#9a6ab0',\r\n      verts: [{x:-20,y:-25},{x:2,y:-25},{x:2,y:5},{x:22,y:5},{x:22,y:25},{x:-20,y:25}]\r\n    },\r\n    {\r\n      name: 'T-Shape', color: '#d4834a',\r\n      verts: [{x:-28,y:-25},{x:28,y:-25},{x:28,y:-5},{x:10,y:-5},{x:10,y:25},{x:-10,y:25},{x:-10,y:-5},{x:-28,y:-5}]\r\n    },\r\n    {\r\n      name: 'Pentagon', color: '#5ab0b0',\r\n      verts: [{x:-22,y:22},{x:-28,y:-5},{x:0,y:-28},{x:28,y:-10},{x:18,y:22}]\r\n    },\r\n    {\r\n      name: 'Kite', color: '#b0a040',\r\n      verts: [{x:0,y:-30},{x:22,y:8},{x:0,y:22},{x:-22,y:8}]\r\n    }\r\n  ];\r\n\r\n  // ─── State ────────────────────────────────────────────────────────────────\r\n  var S_START    = 0;\r\n  var S_PLAYING  = 1;\r\n  var S_SETTLING = 2;\r\n  var S_OVER     = 3;\r\n\r\n  var state          = S_START;\r\n  var engine, runner, world;\r\n  var settledBodies  = [];\r\n  var activePiece    = null;\r\n  var nextShapeIdx   = null;\r\n  var lastShapeIdx   = -1;\r\n  var camY           = GROUND_Y - CH;\r\n  var camYTarget     = GROUND_Y - CH;\r\n  var moveLeft       = false;\r\n  var moveRight      = false;\r\n  var fastDrop       = false;\r\n  var spawnTime      = 0;\r\n  var slowStart      = 0;\r\n  var transStart     = 0;\r\n  var curHeight      = 0;\r\n  var bestHeight     = 0;\r\n\r\n  // ─── DOM ──────────────────────────────────────────────────────────────────\r\n  var canvas     = document.getElementById('tbc-canvas');\r\n  var ctx        = canvas.getContext('2d');\r\n  var prevCvs    = document.getElementById('tbc-preview');\r\n  var pctx       = prevCvs.getContext('2d');\r\n  var heightEl   = document.getElementById('tbc-height');\r\n  var bestEl     = document.getElementById('tbc-best');\r\n  var overlayEl  = document.getElementById('tbc-overlay');\r\n  var ovTitle    = document.getElementById('tbc-ov-title');\r\n  var ovHeight   = document.getElementById('tbc-ov-height');\r\n  var ovMsg      = document.getElementById('tbc-ov-msg');\r\n  var ovBtn      = document.getElementById('tbc-ov-btn');\r\n\r\n  // ─── Persistence ──────────────────────────────────────────────────────────\r\n  function loadBest() {\r\n    try { return parseInt(localStorage.getItem(LS_KEY), 10) || 0; } catch (e) { return 0; }\r\n  }\r\n  function saveBest(h) {\r\n    try { localStorage.setItem(LS_KEY, h); } catch (e) {}\r\n  }\r\n  function updateHUD() {\r\n    heightEl.textContent = curHeight;\r\n    bestEl.textContent   = Math.max(bestHeight, curHeight);\r\n  }\r\n\r\n  // ─── Helpers ──────────────────────────────────────────────────────────────\r\n  function pickShape() {\r\n    var idx;\r\n    do { idx = Math.floor(Math.random() * SHAPES.length); }\r\n    while (SHAPES.length \u003e 1 \u0026\u0026 idx === lastShapeIdx);\r\n    lastShapeIdx = idx;\r\n    return idx;\r\n  }\r\n\r\n  function makeBody(si, x, y) {\r\n    var body = Bodies.fromVertices(x, y, SHAPES[si].verts, {\r\n      friction:       0.65,\r\n      frictionStatic: 0.5,\r\n      frictionAir:    0.02,\r\n      restitution:    0.07,\r\n      label:          'piece'\r\n    });\r\n    body.shapeIdx = si;\r\n    return body;\r\n  }\r\n\r\n  function computeHeight() {\r\n    var minY = GROUND_Y;\r\n    for (var i = 0; i \u003c settledBodies.length; i++) {\r\n      var by = settledBodies[i].bounds.min.y;\r\n      if (by \u003c minY) minY = by;\r\n    }\r\n    return Math.max(0, Math.round((GROUND_Y - minY) / UNIT));\r\n  }\r\n\r\n  function isOffWorld(body) {\r\n    return body.position.x \u003c -150\r\n        || body.position.x \u003e CW + 150\r\n        || body.position.y \u003e GROUND_Y + 350;\r\n  }\r\n\r\n  // ─── Physics ──────────────────────────────────────────────────────────────\r\n  function initPhysics() {\r\n    if (runner) Runner.stop(runner);\r\n    engine = Engine.create({ gravity: { x: 0, y: 0.8 } });\r\n    world  = engine.world;\r\n    runner = Runner.create();\r\n    var ground = Bodies.rectangle(CW / 2, GROUND_Y + 25, CW + 200, 50, {\r\n      isStatic: true, label: 'ground', friction: 0.8\r\n    });\r\n    World.add(world, ground);\r\n    Runner.run(runner, engine);\r\n  }\r\n\r\n  // ─── Game flow ────────────────────────────────────────────────────────────\r\n  function spawnPiece() {\r\n    var si      = (nextShapeIdx !== null) ? nextShapeIdx : pickShape();\r\n    nextShapeIdx = pickShape();\r\n    var spawnY  = camY + 55;\r\n    activePiece = makeBody(si, CW / 2, spawnY);\r\n    spawnTime   = performance.now();\r\n    slowStart   = 0;\r\n    World.add(world, activePiece);\r\n    drawPreview();\r\n    state = S_PLAYING;\r\n  }\r\n\r\n  function settlePiece(now) {\r\n    settledBodies.push(activePiece);\r\n    activePiece = null;\r\n    state       = S_SETTLING;\r\n    transStart  = now;\r\n    curHeight   = computeHeight();\r\n    if (curHeight \u003e bestHeight) { bestHeight = curHeight; saveBest(bestHeight); }\r\n    updateHUD();\r\n  }\r\n\r\n  function endGame() {\r\n    state = S_OVER;\r\n    if (activePiece) { World.remove(world, activePiece); activePiece = null; }\r\n    curHeight = computeHeight();\r\n    if (curHeight \u003e bestHeight) { bestHeight = curHeight; saveBest(bestHeight); }\r\n    updateHUD();\r\n    ovTitle.textContent  = 'Tower Fell';\r\n    ovHeight.textContent = curHeight + ' units';\r\n    ovMsg.textContent    = 'Best: ' + bestHeight + ' units';\r\n    ovBtn.textContent    = 'Try Again';\r\n    overlayEl.classList.remove('tbc-hidden');\r\n  }\r\n\r\n  function startGame() {\r\n    settledBodies = [];\r\n    activePiece   = null;\r\n    nextShapeIdx  = null;\r\n    lastShapeIdx  = -1;\r\n    curHeight     = 0;\r\n    camY          = GROUND_Y - CH;\r\n    camYTarget    = GROUND_Y - CH;\r\n    moveLeft = moveRight = fastDrop = false;\r\n    bestHeight = loadBest();\r\n    updateHUD();\r\n    initPhysics();\r\n    nextShapeIdx = pickShape();\r\n    drawPreview();\r\n    spawnPiece();\r\n    overlayEl.classList.add('tbc-hidden');\r\n  }\r\n\r\n  // ─── Rendering ────────────────────────────────────────────────────────────\r\n  function drawParts(context, body, fill, stroke) {\r\n    var parts = body.parts.length \u003e 1 ? body.parts.slice(1) : body.parts;\r\n    context.fillStyle   = fill;\r\n    context.strokeStyle = stroke;\r\n    context.lineWidth   = 1.5;\r\n    for (var p = 0; p \u003c parts.length; p++) {\r\n      var vs = parts[p].vertices;\r\n      context.beginPath();\r\n      context.moveTo(vs[0].x, vs[0].y);\r\n      for (var v = 1; v \u003c vs.length; v++) context.lineTo(vs[v].x, vs[v].y);\r\n      context.closePath();\r\n      context.fill();\r\n      context.stroke();\r\n    }\r\n  }\r\n\r\n  function drawPreview() {\r\n    pctx.clearRect(0, 0, 80, 80);\r\n    if (nextShapeIdx === null) return;\r\n    var verts = SHAPES[nextShapeIdx].verts;\r\n    var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\r\n    for (var i = 0; i \u003c verts.length; i++) {\r\n      if (verts[i].x \u003c minX) minX = verts[i].x;\r\n      if (verts[i].x \u003e maxX) maxX = verts[i].x;\r\n      if (verts[i].y \u003c minY) minY = verts[i].y;\r\n      if (verts[i].y \u003e maxY) maxY = verts[i].y;\r\n    }\r\n    var scale = Math.min(58 / (maxX - minX), 58 / (maxY - minY));\r\n    var ox = 40 - (minX + (maxX - minX) / 2) * scale;\r\n    var oy = 40 - (minY + (maxY - minY) / 2) * scale;\r\n    pctx.save();\r\n    pctx.translate(ox, oy);\r\n    pctx.scale(scale, scale);\r\n    pctx.fillStyle   = SHAPES[nextShapeIdx].color;\r\n    pctx.strokeStyle = 'rgba(255,255,255,0.2)';\r\n    pctx.lineWidth   = 1.5 / scale;\r\n    pctx.beginPath();\r\n    pctx.moveTo(verts[0].x, verts[0].y);\r\n    for (var j = 1; j \u003c verts.length; j++) pctx.lineTo(verts[j].x, verts[j].y);\r\n    pctx.closePath();\r\n    pctx.fill();\r\n    pctx.stroke();\r\n    pctx.restore();\r\n  }\r\n\r\n  // ─── Main loop ────────────────────────────────────────────────────────────\r\n  function tick(now) {\r\n    requestAnimationFrame(tick);\r\n\r\n    // Camera follow\r\n    var highY = GROUND_Y;\r\n    var i;\r\n    for (i = 0; i \u003c settledBodies.length; i++) {\r\n      if (settledBodies[i].bounds.min.y \u003c highY) highY = settledBodies[i].bounds.min.y;\r\n    }\r\n    if (activePiece \u0026\u0026 activePiece.bounds.min.y \u003c highY) highY = activePiece.bounds.min.y;\r\n    camYTarget = Math.max(GROUND_Y - CH, highY - 80);\r\n    camY += (camYTarget - camY) * 0.07;\r\n\r\n    // Control active piece\r\n    if (state === S_PLAYING \u0026\u0026 activePiece) {\r\n      var vx = activePiece.velocity.x;\r\n      var vy = activePiece.velocity.y;\r\n      if (moveLeft)                  vx = Math.max(vx - 0.7, -5);\r\n      if (moveRight)                 vx = Math.min(vx + 0.7,  5);\r\n      if (!moveLeft \u0026\u0026 !moveRight)   vx *= 0.82;\r\n      vy = Math.min(vy, fastDrop ? 14 : 3);\r\n      Body.setVelocity(activePiece, { x: vx, y: vy });\r\n\r\n      // Settle detection — only after piece has been alive 700ms\r\n      if (now - spawnTime \u003e 700) {\r\n        var speed = Math.sqrt(vx * vx + vy * vy);\r\n        if (speed \u003c 0.45) {\r\n          if (!slowStart) slowStart = now;\r\n          if (now - slowStart \u003e 1200) settlePiece(now);\r\n        } else {\r\n          slowStart = 0;\r\n        }\r\n      }\r\n\r\n      if (isOffWorld(activePiece)) endGame();\r\n    }\r\n\r\n    // Settling phase — wait, check for cascade falls, then spawn next\r\n    if (state === S_SETTLING) {\r\n      for (var j = 0; j \u003c settledBodies.length; j++) {\r\n        if (isOffWorld(settledBodies[j])) { endGame(); break; }\r\n      }\r\n      if (state === S_SETTLING \u0026\u0026 now - transStart \u003e 750) spawnPiece();\r\n    }\r\n\r\n    // Also check settled bodies during play (knock-off detection)\r\n    if (state === S_PLAYING) {\r\n      for (var k = 0; k \u003c settledBodies.length; k++) {\r\n        if (isOffWorld(settledBodies[k])) { endGame(); break; }\r\n      }\r\n    }\r\n\r\n    // ── Draw ──────────────────────────────────────────────────────────────\r\n    ctx.clearRect(0, 0, CW, CH);\r\n    ctx.fillStyle = '#111';\r\n    ctx.fillRect(0, 0, CW, CH);\r\n\r\n    ctx.save();\r\n    ctx.translate(0, -camY);\r\n\r\n    // Subtle height ruler lines (every UNIT)\r\n    ctx.strokeStyle = 'rgba(255,255,255,0.035)';\r\n    ctx.lineWidth   = 1;\r\n    var rulerStart = Math.ceil(camY / UNIT) * UNIT;\r\n    for (var u = rulerStart; u \u003c= camY + CH + UNIT; u += UNIT) {\r\n      ctx.beginPath();\r\n      ctx.moveTo(0, u);\r\n      ctx.lineTo(CW, u);\r\n      ctx.stroke();\r\n    }\r\n\r\n    // Ground\r\n    ctx.fillStyle = '#1c1c1c';\r\n    ctx.fillRect(-100, GROUND_Y, CW + 200, 100);\r\n    ctx.fillStyle = '#2e2e2e';\r\n    ctx.fillRect(-100, GROUND_Y, CW + 200, 3);\r\n\r\n    // Settled pieces\r\n    for (var s = 0; s \u003c settledBodies.length; s++) {\r\n      var sb = settledBodies[s];\r\n      drawParts(ctx, sb, SHAPES[sb.shapeIdx].color, 'rgba(255,255,255,0.09)');\r\n    }\r\n\r\n    // Active piece (slightly brighter outline)\r\n    if (activePiece) {\r\n      drawParts(ctx, activePiece, SHAPES[activePiece.shapeIdx].color, 'rgba(255,255,255,0.32)');\r\n    }\r\n\r\n    ctx.restore();\r\n  }\r\n\r\n  // ─── Keyboard ─────────────────────────────────────────────────────────────\r\n  document.addEventListener('keydown', function (e) {\r\n    if (state === S_START || state === S_OVER) {\r\n      if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); startGame(); }\r\n      return;\r\n    }\r\n    switch (e.key) {\r\n      case 'ArrowLeft':  case 'a': case 'A': moveLeft  = true; e.preventDefault(); break;\r\n      case 'ArrowRight': case 'd': case 'D': moveRight = true; e.preventDefault(); break;\r\n      case 'ArrowDown':  case ' ':           fastDrop  = true; e.preventDefault(); break;\r\n      case 'z': case 'Z':\r\n        if (activePiece) Body.setAngle(activePiece, activePiece.angle - 0.26);\r\n        break;\r\n      case 'x': case 'X':\r\n        if (activePiece) Body.setAngle(activePiece, activePiece.angle + 0.26);\r\n        break;\r\n    }\r\n  });\r\n\r\n  document.addEventListener('keyup', function (e) {\r\n    switch (e.key) {\r\n      case 'ArrowLeft':  case 'a': case 'A': moveLeft  = false; break;\r\n      case 'ArrowRight': case 'd': case 'D': moveRight = false; break;\r\n      case 'ArrowDown':  case ' ':           fastDrop  = false; break;\r\n    }\r\n  });\r\n\r\n  // ─── Touch ────────────────────────────────────────────────────────────────\r\n  var txStart = 0, tyStart = 0;\r\n  canvas.addEventListener('touchstart', function (e) {\r\n    e.preventDefault();\r\n    if (state === S_START || state === S_OVER) { startGame(); return; }\r\n    var t  = e.touches[0];\r\n    var r  = canvas.getBoundingClientRect();\r\n    txStart = t.clientX;\r\n    tyStart = t.clientY;\r\n    if ((t.clientX - r.left) / r.width \u003c 0.5) moveLeft  = true;\r\n    else                                        moveRight = true;\r\n  }, { passive: false });\r\n\r\n  canvas.addEventListener('touchend', function (e) {\r\n    e.preventDefault();\r\n    moveLeft = moveRight = false;\r\n    var t = e.changedTouches[0];\r\n    if (t.clientY - tyStart \u003e 38) {\r\n      fastDrop = true;\r\n      setTimeout(function () { fastDrop = false; }, 380);\r\n    }\r\n  }, { passive: false });\r\n\r\n  ovBtn.addEventListener('click', startGame);\r\n\r\n  // ─── Boot ─────────────────────────────────────────────────────────────────\r\n  bestHeight = loadBest();\r\n  updateHUD();\r\n  ovMsg.textContent = bestHeight \u003e 0\r\n    ? 'Best: ' + bestHeight + ' units'\r\n    : 'Stack shapes as high as you can.';\r\n\r\n  requestAnimationFrame(tick);\r\n})();\r\n\u003c/script\u003e","title":"Tower Balance Challenge"},{"content":" Utter Sign in to manage your quote collections and API keys.\nSign in with Google Dashboard Collections API Keys Sign out Loading… ","permalink":"https://steelwhitetable.ca/utter/","summary":"\u003c!-- Firebase SDK (compat layer — works with vanilla JS, no bundler required) --\u003e\n\u003cscript src=\"https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://www.gstatic.com/firebasejs/10.14.1/firebase-auth-compat.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://www.gstatic.com/firebasejs/10.14.1/firebase-firestore-compat.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://www.gstatic.com/firebasejs/10.14.1/firebase-functions-compat.js\"\u003e\u003c/script\u003e\n\u003cstyle\u003e\n/* ── utr- prefix to avoid PaperMod collisions ───────────────────────────── */\n\n.utr-app { margin: 0 0 3rem; }\n\n/* Auth gate ---------------------------------------------------------------- */\n.utr-auth-gate {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 1.5rem;\n  padding: 3rem 1rem;\n  text-align: center;\n}\n.utr-auth-gate h2 { margin: 0; font-size: 1.4rem; }\n.utr-auth-gate p  { margin: 0; color: var(--secondary); max-width: 36ch; }\n.utr-btn-google {\n  display: inline-flex;\n  align-items: center;\n  gap: 0.6rem;\n  padding: 0.65rem 1.4rem;\n  border: 1px solid var(--border);\n  border-radius: 6px;\n  background: var(--entry);\n  color: var(--primary);\n  font-size: 0.9rem;\n  cursor: pointer;\n  font-family: inherit;\n  transition: border-color 0.15s;\n}\n.utr-btn-google:hover { border-color: var(--primary); }\n.utr-btn-google svg { flex-shrink: 0; }\n\n/* Nav ---------------------------------------------------------------------- */\n.utr-nav {\n  display: flex;\n  align-items: center;\n  gap: 0.25rem;\n  padding: 0.5rem 0 1rem;\n  border-bottom: 1px solid var(--border);\n  margin-bottom: 1.5rem;\n  flex-wrap: wrap;\n}\n.utr-nav-link {\n  padding: 0.35rem 0.75rem;\n  border-radius: 5px;\n  border: none;\n  background: none;\n  color: var(--secondary);\n  font-size: 0.85rem;\n  cursor: pointer;\n  font-family: inherit;\n  transition: background 0.15s, color 0.15s;\n}\n.utr-nav-link:hover { background: var(--code-bg); color: var(--primary); }\n.utr-nav-link.active { background: var(--code-bg); color: var(--primary); font-weight: 600; }\n.utr-nav-spacer { flex: 1; }\n.utr-nav-user {\n  font-size: 0.78rem;\n  color: var(--secondary);\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n}\n.utr-nav-signout {\n  padding: 0.25rem 0.6rem;\n  border: 1px solid var(--border);\n  border-radius: 4px;\n  background: none;\n  color: var(--secondary);\n  font-size: 0.78rem;\n  cursor: pointer;\n  font-family: inherit;\n}\n.utr-nav-signout:hover { border-color: var(--primary); color: var(--primary); }\n\n/* Shared components -------------------------------------------------------- */\n.utr-btn {\n  display: inline-flex;\n  align-items: center;\n  gap: 0.4rem;\n  padding: 0.5rem 1rem;\n  border-radius: 5px;\n  border: 1px solid var(--border);\n  background: var(--entry);\n  color: var(--primary);\n  font-size: 0.85rem;\n  cursor: pointer;\n  font-family: inherit;\n  transition: border-color 0.15s;\n  text-decoration: none;\n}\n.utr-btn:hover { border-color: var(--primary); }\n.utr-btn-primary {\n  background: var(--primary);\n  color: var(--theme);\n  border-color: var(--primary);\n}\n.utr-btn-primary:hover { opacity: 0.85; }\n.utr-btn-danger { color: #c0392b; border-color: #c0392b33; }\n.utr-btn-danger:hover { border-color: #c0392b; background: #c0392b11; }\n.utr-btn-sm { padding: 0.3rem 0.65rem; font-size: 0.78rem; }\n.utr-btn:disabled { opacity: 0.45; cursor: not-allowed; pointer-events: none; }\n\n.utr-card {\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  padding: 1.2rem 1.4rem;\n  background: var(--entry);\n  margin-bottom: 1rem;\n}\n.utr-card-title {\n  font-size: 1rem;\n  font-weight: 600;\n  margin: 0 0 0.25rem;\n}\n.utr-card-sub {\n  font-size: 0.8rem;\n  color: var(--secondary);\n  margin: 0 0 0.75rem;\n}\n.utr-card-actions {\n  display: flex;\n  gap: 0.5rem;\n  margin-top: 0.85rem;\n  flex-wrap: wrap;\n}\n\n.utr-stat-row {\n  display: flex;\n  gap: 1.5rem;\n  flex-wrap: wrap;\n  margin-bottom: 1.5rem;\n}\n.utr-stat {\n  flex: 1;\n  min-width: 100px;\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  padding: 0.9rem 1.1rem;\n  background: var(--entry);\n}\n.utr-stat-n {\n  font-size: 1.8rem;\n  font-weight: 700;\n  line-height: 1;\n  margin-bottom: 0.2rem;\n}\n.utr-stat-label {\n  font-size: 0.75rem;\n  color: var(--secondary);\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n}\n\n/* Forms -------------------------------------------------------------------- */\n.utr-form { display: flex; flex-direction: column; gap: 1rem; }\n.utr-field { display: flex; flex-direction: column; gap: 0.35rem; }\n.utr-label {\n  font-size: 0.82rem;\n  font-weight: 600;\n  color: var(--secondary);\n  text-transform: uppercase;\n  letter-spacing: 0.04em;\n}\n.utr-input, .utr-textarea, .utr-select {\n  width: 100%;\n  padding: 0.55rem 0.8rem;\n  border: 1px solid var(--border);\n  border-radius: 5px;\n  background: var(--theme);\n  color: var(--primary);\n  font-size: 0.9rem;\n  font-family: inherit;\n  box-sizing: border-box;\n  transition: border-color 0.15s;\n}\n.utr-input:focus, .utr-textarea:focus, .utr-select:focus {\n  outline: none;\n  border-color: var(--primary);\n}\n.utr-textarea { resize: vertical; min-height: 7rem; line-height: 1.55; }\n.utr-form-row { display: flex; gap: 0.75rem; }\n.utr-form-row .utr-field { flex: 1; }\n.utr-form-actions { display: flex; gap: 0.75rem; align-items: center; }\n.utr-form-hint { font-size: 0.78rem; color: var(--secondary); }\n\n/* Quote list --------------------------------------------------------------- */\n.utr-quote-list { display: flex; flex-direction: column; gap: 0.6rem; }\n.utr-quote-item {\n  border: 1px solid var(--border);\n  border-radius: 6px;\n  padding: 0.9rem 1.1rem;\n  background: var(--entry);\n  display: flex;\n  gap: 1rem;\n  align-items: flex-start;\n}\n.utr-quote-pos {\n  font-size: 0.7rem;\n  color: var(--secondary);\n  font-family: monospace;\n  min-width: 2.5em;\n  padding-top: 0.15rem;\n  flex-shrink: 0;\n}\n.utr-quote-body { flex: 1; min-width: 0; }\n.utr-quote-text {\n  font-size: 0.9rem;\n  line-height: 1.5;\n  margin: 0 0 0.3rem;\n  word-break: break-word;\n}\n.utr-quote-meta { font-size: 0.78rem; color: var(--secondary); }\n.utr-quote-actions {\n  display: flex;\n  gap: 0.4rem;\n  flex-shrink: 0;\n  align-items: flex-start;\n}\n\n/* Section headers ---------------------------------------------------------- */\n.utr-section-header {\n  display: flex;\n  align-items: center;\n  gap: 1rem;\n  margin-bottom: 1.25rem;\n  flex-wrap: wrap;\n}\n.utr-section-header h2 {\n  margin: 0;\n  font-size: 1.2rem;\n}\n\n/* API key display ---------------------------------------------------------- */\n.utr-key-reveal {\n  border: 1px solid var(--border);\n  border-radius: 6px;\n  padding: 1rem 1.2rem;\n  background: var(--code-bg);\n  margin: 1rem 0;\n}\n.utr-key-reveal p {\n  font-size: 0.82rem;\n  color: var(--secondary);\n  margin: 0 0 0.5rem;\n}\n.utr-key-value {\n  font-family: monospace;\n  font-size: 0.88rem;\n  word-break: break-all;\n  margin: 0 0 0.75rem;\n  color: var(--primary);\n}\n.utr-badge {\n  display: inline-block;\n  font-size: 0.7rem;\n  font-weight: 600;\n  padding: 0.15em 0.5em;\n  border-radius: 3px;\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n}\n.utr-badge-active  { background: #2ecc7122; color: #27ae60; }\n.utr-badge-revoked { background: #e74c3c22; color: #c0392b; }\n\n/* Pagination --------------------------------------------------------------- */\n.utr-pagination {\n  display: flex;\n  justify-content: center;\n  gap: 0.5rem;\n  margin-top: 1.25rem;\n}\n\n/* Empty / loading states --------------------------------------------------- */\n.utr-empty {\n  text-align: center;\n  color: var(--secondary);\n  padding: 2.5rem 1rem;\n  border: 1px dashed var(--border);\n  border-radius: 8px;\n  font-size: 0.9rem;\n}\n.utr-spinner {\n  text-align: center;\n  color: var(--secondary);\n  padding: 2rem;\n  font-size: 0.9rem;\n}\n.utr-error {\n  border: 1px solid #c0392b44;\n  border-radius: 6px;\n  padding: 0.75rem 1rem;\n  background: #c0392b11;\n  color: #c0392b;\n  font-size: 0.85rem;\n  margin-bottom: 1rem;\n}\n.utr-success {\n  border: 1px solid #27ae6044;\n  border-radius: 6px;\n  padding: 0.75rem 1rem;\n  background: #27ae6011;\n  color: #27ae60;\n  font-size: 0.85rem;\n  margin-bottom: 1rem;\n}\n\n/* Back link ---------------------------------------------------------------- */\n.utr-back {\n  display: inline-flex;\n  align-items: center;\n  gap: 0.35rem;\n  font-size: 0.82rem;\n  color: var(--secondary);\n  cursor: pointer;\n  border: none;\n  background: none;\n  font-family: inherit;\n  padding: 0;\n  margin-bottom: 1.25rem;\n}\n.utr-back:hover { color: var(--primary); }\n\n/* Inline expandable form --------------------------------------------------- */\n.utr-inline-form {\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  padding: 1.2rem 1.4rem;\n  background: var(--entry);\n  margin-bottom: 1rem;\n}\n.utr-inline-form h3 { margin: 0 0 1rem; font-size: 1rem; }\n\u003c/style\u003e\n\u003cdiv class=\"utr-app\" id=\"utr-app\"\u003e\n  \u003c!-- Auth gate (shown when not signed in) --\u003e\n  \u003cdiv class=\"utr-auth-gate\" id=\"utr-auth-gate\" style=\"display:none\"\u003e\n    \u003csvg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"color:var(--secondary)\"\u003e\u003cpath d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/\u003e\u003c/svg\u003e\n    \u003ch2\u003eUtter\u003c/h2\u003e\n    \u003cp\u003eSign in to manage your quote collections and API keys.\u003c/p\u003e","title":"Utter"},{"content":"\r0 / — popped\r0:00.0\r","permalink":"https://steelwhitetable.ca/apps/virtual-bubble-wrap-popper/","summary":"\u003cstyle\u003e\r\n  .bwp-outer {\r\n    margin: 1.5rem 0;\r\n    border-radius: 10px;\r\n    overflow: hidden;\r\n    background: #d4e8f0;\r\n    border: 3px solid #b8d4e0;\r\n    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);\r\n  }\r\n\r\n  .bwp-grid {\r\n    display: grid;\r\n    grid-template-columns: repeat(auto-fill, minmax(46px, 1fr));\r\n    gap: 6px;\r\n    padding: 14px;\r\n  }\r\n\r\n  .bwp-bubble {\r\n    aspect-ratio: 1;\r\n    border-radius: 50%;\r\n    cursor: pointer;\r\n    background: radial-gradient(\r\n      circle at 35% 30%,\r\n      rgba(255, 255, 255, 0.95) 0%,\r\n      rgba(210, 235, 255, 0.75) 28%,\r\n      rgba(160, 210, 245, 0.55) 58%,\r\n      rgba(110, 175, 225, 0.45) 100%\r\n    );\r\n    box-shadow:\r\n      inset -2px -3px 6px rgba(0, 0, 0, 0.08),\r\n      inset 2px 2px 5px rgba(255, 255, 255, 0.85),\r\n      2px 3px 8px rgba(0, 0, 0, 0.14);\r\n    transition: transform 0.06s ease, background 0.15s ease, box-shadow 0.15s ease;\r\n    -webkit-tap-highlight-color: transparent;\r\n  }\r\n\r\n  .bwp-bubble:hover:not(.bwp-popped) {\r\n    transform: scale(1.06);\r\n  }\r\n\r\n  .bwp-bubble.bwp-popping {\r\n    animation: bwp-pop 0.12s ease-out forwards;\r\n  }\r\n\r\n  .bwp-bubble.bwp-popped {\r\n    background: radial-gradient(\r\n      circle at 50% 50%,\r\n      rgba(180, 205, 215, 0.35) 0%,\r\n      rgba(160, 190, 205, 0.25) 100%\r\n    );\r\n    box-shadow: inset 1px 2px 5px rgba(0, 0, 0, 0.18);\r\n    transform: scale(0.82);\r\n    cursor: default;\r\n  }\r\n\r\n  @keyframes bwp-pop {\r\n    0%   { transform: scale(1);    }\r\n    25%  { transform: scale(1.10); }\r\n    100% { transform: scale(0.82); }\r\n  }\r\n\r\n  .bwp-footer {\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: space-between;\r\n    padding: 10px 16px 12px;\r\n    background: #c4dce8;\r\n    border-top: 2px solid #b0ccd8;\r\n  }\r\n\r\n  .bwp-counter {\r\n    font-size: 0.82rem;\r\n    font-weight: 600;\r\n    letter-spacing: 0.05em;\r\n    text-transform: uppercase;\r\n    color: #4a7a90;\r\n  }\r\n\r\n  .bwp-footer-left {\r\n    display: flex;\r\n    flex-direction: column;\r\n    gap: 3px;\r\n  }\r\n\r\n  .bwp-timer-line {\r\n    font-size: 0.75rem;\r\n    color: #5a8fa5;\r\n    letter-spacing: 0.04em;\r\n    min-height: 1.1em;\r\n  }\r\n\r\n  .bwp-msg {\r\n    font-size: 0.78rem;\r\n    color: #5a8fa5;\r\n    font-style: italic;\r\n    text-align: right;\r\n    max-width: 55%;\r\n    min-height: 1.1em;\r\n    transition: opacity 0.4s ease;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"bwp-outer\"\u003e\r\n  \u003cdiv class=\"bwp-grid\" id=\"bwp-grid\"\u003e\u003c/div\u003e\r\n  \u003cdiv class=\"bwp-footer\"\u003e\r\n    \u003cdiv class=\"bwp-footer-left\"\u003e\r\n      \u003cspan class=\"bwp-counter\" id=\"bwp-counter\"\u003e0 / — popped\u003c/span\u003e\r\n      \u003cspan class=\"bwp-timer-line\" id=\"bwp-timer-line\"\u003e0:00.0\u003c/span\u003e\r\n    \u003c/div\u003e\r\n    \u003cspan class=\"bwp-msg\" id=\"bwp-msg\"\u003e\u003c/span\u003e\r\n  \u003c/div\u003e\r\n\u003c/div\u003e\r\n\u003cscript\u003e\r\n  (function () {\r\n    var TOTAL       = Math.floor(Math.random() * 61) + 60;\r\n    var grid        = document.getElementById('bwp-grid');\r\n    var counterEl   = document.getElementById('bwp-counter');\r\n    var timerLineEl = document.getElementById('bwp-timer-line');\r\n    var msgEl       = document.getElementById('bwp-msg');\r\n    var audioCtx    = null;\r\n    var popped      = 0;\r\n\r\n    var STORAGE_KEY  = 'bwp-best-ms';\r\n    var startTime    = Date.now();\r\n    var tickInterval = null;\r\n    var msgInterval  = null;\r\n\r\n    function fmtTime(ms) {\r\n      var totalSec = ms / 1000;\r\n      var min    = Math.floor(totalSec / 60);\r\n      var sec    = totalSec % 60;\r\n      var secStr = (sec \u003c 10 ? '0' : '') + sec.toFixed(1);\r\n      return min + ':' + secStr;\r\n    }\r\n\r\n    function loadBest() {\r\n      try { return parseInt(localStorage.getItem(STORAGE_KEY), 10) || 0; } catch (e) { return 0; }\r\n    }\r\n\r\n    function saveBest(ms) {\r\n      try { localStorage.setItem(STORAGE_KEY, ms); } catch (e) {}\r\n    }\r\n\r\n    function renderTimerLine(elapsed) {\r\n      var best = loadBest();\r\n      var line = fmtTime(elapsed);\r\n      if (best \u003e 0) line += '  \\u00b7  Best: ' + fmtTime(best);\r\n      timerLineEl.textContent = line;\r\n    }\r\n\r\n    function pick(arr) {\r\n      return arr[Math.floor(Math.random() * arr.length)];\r\n    }\r\n\r\n    function pickMsg() {\r\n      var elapsed = Date.now() - startTime;\r\n      var pct = popped / TOTAL;\r\n\r\n      if (pct \u003e 0.75) return pick([\r\n        'Almost there!', 'So close.', \"Don't quit now.\", 'Finish what you started.'\r\n      ]);\r\n      if (pct \u003e 0.50) return pick([\r\n        \"Halfway! Don't stop now.\", \"You're on a roll.\",\r\n        'The bubble wrap fears you.', 'Keep it up.'\r\n      ]);\r\n      if (pct \u003e 0.25) return pick([\r\n        'Keep going.', \"You're getting somewhere.\", 'Not bad.',\r\n        \"There's a rhythm here.\"\r\n      ]);\r\n      if (elapsed \u003e= 30000) return pick([\r\n        'Off to a slow start.', 'No rush. Seriously though.',\r\n        'Taking it easy, are we?', 'At this rate\\u2026'\r\n      ]);\r\n      if (elapsed \u003e= 10000) return pick([\r\n        'What are you waiting for?', \"Those bubbles won't pop themselves.\",\r\n        'Anytime now.', \"They're not going to pop themselves.\"\r\n      ]);\r\n      return '';\r\n    }\r\n\r\n    function getAudioCtx() {\r\n      if (!audioCtx) {\r\n        audioCtx = new (window.AudioContext || window.webkitAudioContext)();\r\n      }\r\n      return audioCtx;\r\n    }\r\n\r\n    function playPop(pitch) {\r\n      // pitch: 0.0 – 1.0, shapes the bandpass centre frequency\r\n      try {\r\n        var ctx      = getAudioCtx();\r\n        var now      = ctx.currentTime;\r\n        var duration = 0.07;\r\n\r\n        var bufLen = Math.floor(ctx.sampleRate * duration);\r\n        var buffer = ctx.createBuffer(1, bufLen, ctx.sampleRate);\r\n        var data   = buffer.getChannelData(0);\r\n        for (var i = 0; i \u003c bufLen; i++) {\r\n          data[i] = Math.random() * 2 - 1;\r\n        }\r\n\r\n        var src    = ctx.createBufferSource();\r\n        src.buffer = buffer;\r\n\r\n        var filter           = ctx.createBiquadFilter();\r\n        filter.type          = 'bandpass';\r\n        filter.frequency.value = 600 + pitch * 1400; // 600–2000 Hz\r\n        filter.Q.value       = 0.6;\r\n\r\n        var gain = ctx.createGain();\r\n        gain.gain.setValueAtTime(1.0, now);\r\n        gain.gain.exponentialRampToValueAtTime(0.001, now + duration);\r\n\r\n        src.connect(filter);\r\n        filter.connect(gain);\r\n        gain.connect(ctx.destination);\r\n        src.start(now);\r\n      } catch (e) {\r\n        // Audio unavailable — silent pop is fine\r\n      }\r\n    }\r\n\r\n    function clearGrid() {\r\n      while (grid.firstChild) {\r\n        grid.removeChild(grid.firstChild);\r\n      }\r\n    }\r\n\r\n    function buildSheet() {\r\n      clearGrid();\r\n      popped = 0;\r\n      updateCounter();\r\n      for (var i = 0; i \u003c TOTAL; i++) {\r\n        (function () {\r\n          var pitch  = Math.random(); // fixed per bubble at creation time\r\n          var bubble = document.createElement('div');\r\n          bubble.className = 'bwp-bubble';\r\n\r\n          function pop(e) {\r\n            e.preventDefault();\r\n            if (bubble.classList.contains('bwp-popped')) return;\r\n\r\n            bubble.removeEventListener('click',      pop);\r\n            bubble.removeEventListener('touchstart', pop);\r\n\r\n            bubble.classList.add('bwp-popping');\r\n            playPop(pitch);\r\n\r\n            bubble.addEventListener('animationend', function () {\r\n              bubble.classList.remove('bwp-popping');\r\n              bubble.classList.add('bwp-popped');\r\n              popped++;\r\n              updateCounter();\r\n              if (popped === TOTAL) onAllPopped();\r\n            }, { once: true });\r\n          }\r\n\r\n          bubble.addEventListener('click',      pop);\r\n          bubble.addEventListener('touchstart', pop, { passive: false });\r\n          grid.appendChild(bubble);\r\n        })();\r\n      }\r\n    }\r\n\r\n    function updateCounter() {\r\n      counterEl.textContent = popped + ' / ' + TOTAL + ' popped';\r\n    }\r\n\r\n    function onAllPopped() {\r\n      clearInterval(tickInterval);\r\n      clearInterval(msgInterval);\r\n\r\n      var elapsed  = Date.now() - startTime;\r\n      var prevBest = loadBest();\r\n      var isRecord = !prevBest || elapsed \u003c prevBest;\r\n      if (isRecord) saveBest(elapsed);\r\n\r\n      counterEl.textContent = 'All ' + TOTAL + ' popped! \\u2014 ' + fmtTime(elapsed);\r\n\r\n      if (isRecord) {\r\n        timerLineEl.textContent = 'New record!';\r\n      } else {\r\n        renderTimerLine(elapsed);\r\n      }\r\n\r\n      msgEl.textContent = 'Refresh the page to pop again.';\r\n    }\r\n\r\n    tickInterval = setInterval(function () {\r\n      renderTimerLine(Date.now() - startTime);\r\n    }, 100);\r\n\r\n    msgInterval = setInterval(function () {\r\n      msgEl.textContent = pickMsg();\r\n    }, 5000);\r\n\r\n    buildSheet();\r\n  })();\r\n\u003c/script\u003e","title":"Virtual Bubble Wrap Popper"},{"content":"\rclick anywhere to summon\r","permalink":"https://steelwhitetable.ca/apps/void-wisdom/","summary":"\u003cstyle\u003e\r\n  .vw-stage {\r\n    position: relative;\r\n    width: 100%;\r\n    height: 480px;\r\n    background: #000;\r\n    border-radius: 8px;\r\n    overflow: hidden;\r\n    cursor: default;\r\n    user-select: none;\r\n    -webkit-user-select: none;\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    margin: 1.5rem 0;\r\n  }\r\n\r\n  .vw-advice {\r\n    color: #fff;\r\n    font-family: 'Georgia', 'Times New Roman', serif;\r\n    font-size: clamp(1.1rem, 2.8vw, 1.6rem);\r\n    line-height: 1.7;\r\n    max-width: 52ch;\r\n    padding: 0 2rem;\r\n    text-align: center;\r\n    opacity: 0;\r\n    transition: opacity 2s ease;\r\n    letter-spacing: 0.01em;\r\n  }\r\n\r\n  .vw-advice.vw-visible {\r\n    opacity: 1;\r\n  }\r\n\r\n  .vw-ripple {\r\n    position: absolute;\r\n    border-radius: 50%;\r\n    border: 1px solid rgba(255, 255, 255, 0.06);\r\n    pointer-events: none;\r\n    animation: vw-ripple-expand 1.8s ease-out forwards;\r\n  }\r\n\r\n  @keyframes vw-ripple-expand {\r\n    0%   { width: 0; height: 0; opacity: 1; transform: translate(-50%, -50%); }\r\n    100% { width: 500px; height: 500px; opacity: 0; transform: translate(-50%, -50%); }\r\n  }\r\n\r\n  .vw-hint {\r\n    position: absolute;\r\n    bottom: 1.25rem;\r\n    font-size: 11px;\r\n    color: rgba(255, 255, 255, 0.18);\r\n    letter-spacing: 0.08em;\r\n    text-transform: uppercase;\r\n    font-family: inherit;\r\n    pointer-events: none;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"vw-stage\" id=\"vw-stage\"\u003e\r\n  \u003cdiv class=\"vw-advice\" id=\"vw-advice\"\u003e\u003c/div\u003e\r\n  \u003cspan class=\"vw-hint\" id=\"vw-hint\"\u003eclick anywhere to summon\u003c/span\u003e\r\n\u003c/div\u003e\r\n\u003cscript\u003e\r\n  (function () {\r\n    const stage    = document.getElementById('vw-stage');\r\n    const adviceEl = document.getElementById('vw-advice');\r\n    const hintEl   = document.getElementById('vw-hint');\r\n\r\n    let cycleTimer  = null;\r\n    let isAnimating = false;\r\n    let hasInteracted = false;\r\n\r\n    function sleep(ms) {\r\n      return new Promise(resolve =\u003e setTimeout(resolve, ms));\r\n    }\r\n\r\n    async function fetchAdvice() {\r\n      try {\r\n        const res  = await fetch('https://api.adviceslip.com/advice', { cache: 'no-store' });\r\n        const data = await res.json();\r\n        return data.slip.advice;\r\n      } catch {\r\n        return 'Sometimes the void has nothing to say.';\r\n      }\r\n    }\r\n\r\n    async function showAdvice() {\r\n      if (isAnimating) return;\r\n      isAnimating = true;\r\n\r\n      adviceEl.classList.remove('vw-visible');\r\n      await sleep(adviceEl.textContent ? 2000 : 0);\r\n\r\n      const text = await fetchAdvice();\r\n      adviceEl.textContent = text;\r\n\r\n      await sleep(100);\r\n      adviceEl.classList.add('vw-visible');\r\n      hintEl.style.opacity = '0';\r\n\r\n      await sleep(5000);\r\n\r\n      adviceEl.classList.remove('vw-visible');\r\n      await sleep(2000);\r\n\r\n      isAnimating = false;\r\n      scheduleCycle();\r\n    }\r\n\r\n    function scheduleCycle() {\r\n      clearTimeout(cycleTimer);\r\n      cycleTimer = setTimeout(showAdvice, 3000);\r\n    }\r\n\r\n    function createRipple(x, y) {\r\n      const r = document.createElement('div');\r\n      r.classList.add('vw-ripple');\r\n      r.style.left = x + 'px';\r\n      r.style.top  = y + 'px';\r\n      stage.appendChild(r);\r\n      r.addEventListener('animationend', () =\u003e r.remove());\r\n    }\r\n\r\n    stage.addEventListener('click', function (e) {\r\n      const rect = stage.getBoundingClientRect();\r\n      createRipple(e.clientX - rect.left, e.clientY - rect.top);\r\n\r\n      if (!hasInteracted) {\r\n        hasInteracted = true;\r\n        hintEl.style.display = 'none';\r\n      }\r\n\r\n      if (!isAnimating) {\r\n        clearTimeout(cycleTimer);\r\n        showAdvice();\r\n      }\r\n    });\r\n\r\n    cycleTimer = setTimeout(showAdvice, 2000);\r\n  })();\r\n\u003c/script\u003e","title":"Void Wisdom"},{"content":"\rSomewhere on that grey canvas is a 14\u0026times;14 pixel div. It's invisible. Find it.\n0\rclicks\rclick anywhere to start\r0.0s\rtime\rbest: —\rnew game\rfound: 0\rHow it works\rClick anywhere on the canvas. The heat bar tells you how close you are — cold on the left, scorching on the right. Each click leaves a ripple tinted by proximity. When you land on the div, it reveals itself. Lowest click count wins (against yourself).\n","permalink":"https://steelwhitetable.ca/apps/wheres-the-div/","summary":"\u003cstyle\u003e\r\n  .wtd-wrap {\r\n    --wtd-surface: #f5f5f5;\r\n    --wtd-border: #e0e0e0;\r\n    --wtd-muted: #767676;\r\n    --wtd-success-bg: #eaf7ee;\r\n    --wtd-success-border: #4caf77;\r\n  }\r\n\r\n  @media (prefers-color-scheme: dark) {\r\n    .wtd-wrap {\r\n      --wtd-surface: #252525;\r\n      --wtd-border: #333333;\r\n      --wtd-muted: #888888;\r\n      --wtd-success-bg: #1a2e22;\r\n      --wtd-success-border: #4caf77;\r\n    }\r\n  }\r\n\r\n  .wtd-tagline {\r\n    font-size: 14px;\r\n    color: var(--wtd-muted);\r\n    line-height: 1.5;\r\n    margin-bottom: 1.5rem;\r\n  }\r\n\r\n  .wtd-stats-row {\r\n    display: flex;\r\n    justify-content: space-between;\r\n    align-items: flex-start;\r\n    margin-bottom: 10px;\r\n    gap: 1rem;\r\n  }\r\n\r\n  .wtd-stat { text-align: center; }\r\n\r\n  .wtd-stat-val {\r\n    font-size: 22px;\r\n    font-weight: 600;\r\n    line-height: 1.2;\r\n    font-variant-numeric: tabular-nums;\r\n  }\r\n\r\n  .wtd-stat-lbl {\r\n    font-size: 11px;\r\n    color: var(--wtd-muted);\r\n    margin-top: 2px;\r\n    text-transform: uppercase;\r\n    letter-spacing: 0.06em;\r\n  }\r\n\r\n  .wtd-status-center {\r\n    flex: 1;\r\n    text-align: center;\r\n  }\r\n\r\n  #wtd-status-msg {\r\n    font-size: 13px;\r\n    color: var(--wtd-muted);\r\n    min-height: 18px;\r\n  }\r\n\r\n  #wtd-hint-bar {\r\n    height: 3px;\r\n    border-radius: 2px;\r\n    background: var(--wtd-border);\r\n    margin: 8px auto 0;\r\n    max-width: 240px;\r\n    overflow: hidden;\r\n  }\r\n\r\n  #wtd-hint-fill {\r\n    height: 100%;\r\n    width: 0%;\r\n    border-radius: 2px;\r\n    transition: width 0.15s ease, background 0.15s ease;\r\n  }\r\n\r\n  #wtd-game-area {\r\n    position: relative;\r\n    width: 100%;\r\n    height: 360px;\r\n    background: var(--wtd-surface);\r\n    border: 1px solid var(--wtd-border);\r\n    border-radius: 8px;\r\n    overflow: hidden;\r\n    cursor: crosshair;\r\n    user-select: none;\r\n    margin: 12px 0;\r\n  }\r\n\r\n  #wtd-target {\r\n    position: absolute;\r\n    width: 14px;\r\n    height: 14px;\r\n    opacity: 0;\r\n    cursor: crosshair;\r\n  }\r\n\r\n  #wtd-target.found {\r\n    opacity: 1;\r\n    background: var(--wtd-success-bg);\r\n    border: 2px solid var(--wtd-success-border);\r\n    border-radius: 50%;\r\n    animation: wtd-found-pulse 0.45s ease-out;\r\n  }\r\n\r\n  @keyframes wtd-found-pulse {\r\n    0%   { transform: scale(1); }\r\n    50%  { transform: scale(3.5); }\r\n    100% { transform: scale(1); }\r\n  }\r\n\r\n  .wtd-bottom-row {\r\n    display: flex;\r\n    justify-content: space-between;\r\n    align-items: center;\r\n  }\r\n\r\n  .wtd-meta {\r\n    font-size: 12px;\r\n    color: var(--wtd-muted);\r\n  }\r\n\r\n  .wtd-meta span { font-weight: 600; }\r\n\r\n  .wtd-btn {\r\n    background: transparent;\r\n    border: 1px solid var(--wtd-border);\r\n    font-size: 13px;\r\n    padding: 6px 18px;\r\n    border-radius: 5px;\r\n    cursor: pointer;\r\n    transition: background 0.15s, border-color 0.15s;\r\n    font-family: inherit;\r\n  }\r\n\r\n  .wtd-btn:hover {\r\n    background: var(--wtd-surface);\r\n    border-color: var(--wtd-muted);\r\n  }\r\n\r\n  .wtd-btn:active { transform: scale(0.97); }\r\n\r\n  .wtd-ripple {\r\n    position: absolute;\r\n    border-radius: 50%;\r\n    pointer-events: none;\r\n    animation: wtd-ripple-out 0.5s ease-out forwards;\r\n    border: 1.5px solid;\r\n  }\r\n\r\n  @keyframes wtd-ripple-out {\r\n    0%   { width: 0; height: 0; opacity: 0.8; transform: translate(-50%, -50%); }\r\n    100% { width: 44px; height: 44px; opacity: 0; transform: translate(-50%, -50%); }\r\n  }\r\n\r\n  .wtd-confetti {\r\n    position: absolute;\r\n    width: 7px;\r\n    height: 7px;\r\n    border-radius: 2px;\r\n    pointer-events: none;\r\n    animation: wtd-confetti-fall linear forwards;\r\n  }\r\n\r\n  @keyframes wtd-confetti-fall {\r\n    0%   { transform: translateY(0) rotate(0deg); opacity: 1; }\r\n    100% { transform: translateY(320px) rotate(720deg); opacity: 0; }\r\n  }\r\n\r\n  .wtd-instructions {\r\n    margin-top: 2rem;\r\n    padding-top: 1.5rem;\r\n    border-top: 1px solid var(--wtd-border);\r\n  }\r\n\r\n  .wtd-instructions h2 {\r\n    font-size: 13px;\r\n    font-weight: 600;\r\n    text-transform: uppercase;\r\n    letter-spacing: 0.07em;\r\n    color: var(--wtd-muted);\r\n    margin-bottom: 0.75rem;\r\n  }\r\n\r\n  .wtd-instructions p {\r\n    font-size: 14px;\r\n    color: var(--wtd-muted);\r\n    line-height: 1.7;\r\n  }\r\n\u003c/style\u003e\r\n\u003cdiv class=\"wtd-wrap\"\u003e\r\n  \u003cp class=\"wtd-tagline\"\u003eSomewhere on that grey canvas is a 14\u0026times;14 pixel div. It's invisible. Find it.\u003c/p\u003e","title":"Where's the Div?"}]