← Back to Articles Directory
AI Models • October 15, 2026 • 12 min read

Step 5 Preview: Free OpenCode Frontend HTML Benchmark

Five glowing glass steps supporting a miniature globe, folded paper crane and floating island with a wizard tower.
AI-generated editorial illustration

Three complete HTML outputs, two incomplete game attempts, and a reproducible task-board bug through OpenCode’s free route.

Co-Founder & Lead Programmer of AcceleratedLogic AI

Step 5 Preview is StepFun’s flagship model for agentic work. On October 15, 2026, we submitted four independent frontend challenges through OpenCode’s free Step 5 route: an SVG glass crane, a procedural globe, a floating-island wizard game, and an accessible task board. This article preserves the returned HTML so you can inspect the code and try the demos yourself.

What Step 5 Preview is

StepFun describes Step 5 Preview as a sparse mixture-of-experts model with 600 billion total parameters, 27 billion active parameters, and up to one million tokens of context. Its model-card specifications list text and image input and text output. The published checkpoints use the StepFun model license; open weights do not automatically mean an unrestricted software license. These are publisher specifications, not measurements from our four tasks. Source: StepFun’s model card.
StepFun’s architecture article, dated October 15, describes the training and architecture behind the release. Vercel announced gateway availability on October 8. Those are separate milestones; our session used OpenCode rather than Vercel’s route.

Free access and the test method

OpenCode’s official model documentation lists step-5-preview-free with free input and output for a limited time. It also says this route’s provider follows a zero-retention policy and does not train on submitted data. We checked the public catalog and selected opencode/step-5-preview-free in the installed OpenCode 1.18.35 client. The base model’s maximum context is not a guarantee that this free serving route exposes the same limit. Free availability and terms may change.
Each task had its own fresh CLI session, using opencode run --pure --format json --model opencode/step-5-preview-free. The default build agent and sampling settings were retained. External plugins were disabled, and every prompt prohibited tools, filesystem access, and shell commands. The response had to contain one complete HTML file. The two 3D tasks could import one pinned Three.js module; the SVG and task board had no external dependencies. The first game response was incomplete, so we repeated that exact prompt once in another fresh session. No repair prompt or model substitution was used. The requests overlapped, so their timings are not a controlled serial latency comparison.
The exact prompts and session manifest preserve the task definitions, model route, timestamps, reported token usage, finish reasons and file hashes. Surrounding response whitespace or Markdown fences were removed and a final newline was added; the generated HTML, CSS and JavaScript were not repaired. This is a small exploratory session, not a standardized model ranking.

Results at a glance

Download all original outputs, prompts and check results as a ZIP. The bundle includes the failed game attempt as evidence.
Task / attempt Step time Delivered result Check result
Glass crane 229.4 s Complete HTML Local SVG references resolve; no external assets
Procedural globe 367.2 s Complete HTML JavaScript parses; simulated no-WebGL fallback passes
Wizard game, attempt 1 362.3 s Incomplete HTML Stray tool markup, invalid script, no game implementation
Wizard game, attempt 2 496.7 s Incomplete HTML Ends mid-sentence in start-panel markup; no game JavaScript
Task board 93.6 s Complete HTML 15/16 DOM cases pass; empty saved board reloads incorrectly
All five completed requests reported $0 in OpenCode. Three attempts returned complete documents, covering three of the four task types. A complete document is a delivery check, not proof that every requested behavior works.
The timings above are measured between OpenCode’s step-start and step-finish events. They exclude CLI startup and include reasoning and any provider retry time. OpenCode’s token and cost fields are client-reported telemetry, not an independent billing audit or a pure output-speed measurement.

Test 1: Glass sphere and origami crane

The original file uses SVG gradients, folded polygon facets, crease strokes, reflections, blur filters and animated highlights. It includes an SVG title and description, responsive sizing and a reduced-motion media query. The source checks found no missing local SVG references, duplicate element IDs or external asset URLs. The artwork uses two-dimensional depth cues; this check does not establish physically accurate refraction or photographic realism.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>Crane Suspended — Origami in a Glass Sphere</title>
<style>
  :root{
    --bg-top:#16202f;
    --bg-mid:#0b1018;
    --bg-bot:#05070b;
    --txt:#cbd8e8;
    --muted:#7f90a6;
    --accent:#a8e2f5;
  }
  *,*::before,*::after{box-sizing:border-box}
  html,body{height:100%}
  body{
    margin:0;
    min-height:100dvh;
    display:flex;
    align-items:center;
    justify-content:center;
    padding:clamp(14px,3.5vmin,52px);
    background:
      radial-gradient(120% 90% at 50% -12%, var(--bg-top) 0%, var(--bg-mid) 46%, var(--bg-bot) 100%);
    color:var(--txt);
    font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
    -webkit-font-smoothing:antialiased;
  }
  .stage{width:100%;max-width:min(88vw,860px)}
  figure{margin:0;display:block}
  svg.art{display:block;width:100%;height:auto}
  .sr-only{
    position:absolute;width:1px;height:1px;padding:0;margin:-1px;
    overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;
  }
  figcaption{
    margin-top:clamp(8px,1.8vmin,18px);
    text-align:center;line-height:1.45;
  }
  figcaption .kicker{
    display:block;
    font-size:clamp(9px,1.35vmin,11px);
    letter-spacing:.34em;text-transform:uppercase;
    color:var(--muted);margin-bottom:.5em;
  }
  figcaption .title{
    display:block;
    font-size:clamp(15px,2.6vmin,22px);
    font-weight:550;letter-spacing:.02em;
    color:#eaf4ff;
  }
  figcaption .title em{
    font-style:normal;
    background:linear-gradient(92deg,#ffffff 0%,var(--accent) 55%,#cfe9ff 100%);
    -webkit-background-clip:text;background-clip:text;color:transparent;
  }
  figcaption .note{
    display:block;margin-top:.55em;
    font-size:clamp(10px,1.6vmin,12.5px);color:var(--muted);
    letter-spacing:.06em;
  }
  /* ---------- motion ---------- */
  @keyframes drift{
    0%,100%{transform:translateY(0)}
    50%{transform:translateY(-7px)}
  }
  @keyframes sway{
    0%,100%{transform:translate(0,0) rotate(-2.4deg)}
    50%{transform:translate(-2px,-3.5px) rotate(2.2deg)}
  }
  @keyframes glint{
    0%,100%{opacity:.42}
    50%{opacity:.95}
  }
  @keyframes twinkle{
    0%,100%{transform:scale(.35);opacity:0}
    50%{transform:scale(1);opacity:.95}
  }
  @keyframes causticPulse{
    0%,100%{opacity:.55}
    50%{opacity:.95}
  }
  .drift{animation:drift 11s cubic-bezier(.45,0,.55,1) infinite}
  .sway{animation:sway 9s ease-in-out infinite}
  .glint{animation:glint 6.5s ease-in-out infinite}
  .twinkle{animation:twinkle 4.6s ease-in-out infinite;transform-origin:center}
  .twinkle.b{animation-duration:6.1s;animation-delay:-2.4s}
  .caustic-pulse{animation:causticPulse 7.4s ease-in-out infinite}
  @media (prefers-reduced-motion: reduce){
    .drift,.sway,.glint,.twinkle,.caustic-pulse{animation:none}
    .twinkle{opacity:.8;transform:none}
    .glint{opacity:.75}
  }
</style>
</head>
<body>
<main class="stage">
  <figure>
    <h1 class="sr-only">A folded origami crane suspended inside a clear glass sphere</h1>
    <svg class="art" viewBox="0 0 460 420" preserveAspectRatio="xMidYMid meet"
         role="img" aria-labelledby="artTitle artDesc" xmlns="http://www.w3.org/2000/svg">
      <title id="artTitle">Origami crane inside a glass sphere</title>
      <desc id="artDesc">A polished glass sphere rests on a dark surface, lit softly from the upper left. Inside the sphere a folded paper crane hovers, its sharp facets and crease lines catching light and shadow. The glass shows refracted echoes of the crane near its base, a bright caustic on the surface below, colorful chromatic edges, and crisp specular highlights on the sphere.</desc>

      <defs>
        <!-- paper facets -->
        <linearGradient id="gPaperA" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0" stop-color="#fffdf8"/><stop offset="1" stop-color="#f3e7d2"/>
        </linearGradient>
        <linearGradient id="gPaperB" x1="0" y1="0" x2=".7" y2="1">
          <stop offset="0" stop-color="#fbf2e3"/><stop offset="1" stop-color="#e4d0ab"/>
        </linearGradient>
        <linearGradient id="gPaperC" x1=".1" y1="0" x2=".9" y2="1">
          <stop offset="0" stop-color="#ecd6b4"/><stop offset="1" stop-color="#c0a279"/>
        </linearGradient>
        <linearGradient id="gPaperD" x1="0" y1="0" x2=".8" y2="1">
          <stop offset="0" stop-color="#b2916a"/><stop offset="1" stop-color="#77582f"/>
        </linearGradient>
        <linearGradient id="gPaperE" x1="0" y1="0" x2=".6" y2="1">
          <stop offset="0" stop-color="#ffffff"/><stop offset="1" stop-color="#f6e9d0"/>
        </linearGradient>

        <!-- glass -->
        <radialGradient id="gGlass" cx=".38" cy=".3" r=".82">
          <stop offset="0" stop-color="#ffffff" stop-opacity=".10"/>
          <stop offset=".45" stop-color="#aad6ec" stop-opacity=".13"/>
          <stop offset=".78" stop-color="#1b3f58" stop-opacity=".30"/>
          <stop offset="1" stop-color="#06121e" stop-opacity=".62"/>
        </radialGradient>
        <linearGradient id="gRim" x1=".06" y1=".02" x2=".94" y2="1">
          <stop offset="0" stop-color="#ffffff"/>
          <stop offset=".16" stop-color="#dcf3fd"/>
          <stop offset=".36" stop-color="#7fb4cf"/>
          <stop offset=".55" stop-color="#2d4c63"/>
          <stop offset=".72" stop-color="#132430"/>
          <stop offset=".88" stop-color="#cfeeff"/>
          <stop offset="1" stop-color="#ffffff"/>
        </linearGradient>
        <radialGradient id="gSpec">
          <stop offset="0" stop-color="#ffffff" stop-opacity=".95"/>
          <stop offset=".55" stop-color="#ffffff" stop-opacity=".32"/>
          <stop offset="1" stop-color="#ffffff" stop-opacity="0"/>
        </radialGradient>
        <radialGradient id="gBounce">
          <stop offset="0" stop-color="#d6f4ff" stop-opacity=".45"/>
          <stop offset="1" stop-color="#d6f4ff" stop-opacity="0"/>
        </radialGradient>
        <radialGradient id="gHalo">
          <stop offset="0" stop-color="#9fd4ee" stop-opacity=".18"/>
          <stop offset=".55" stop-color="#6ba7c4" stop-opacity=".06"/>
          <stop offset="1" stop-color="#6ba7c4" stop-opacity="0"/>
        </radialGradient>
        <radialGradient id="gFloorShadow">
          <stop offset="0" stop-color="#000000" stop-opacity=".62"/>
          <stop offset=".6" stop-color="#000000" stop-opacity=".26"/>
          <stop offset="1" stop-color="#000000" stop-opacity="0"/>
        </radialGradient>
        <radialGradient id="gCaustic">
          <stop offset="0" stop-color="#bfeeff" stop-opacity=".6"/>
          <stop offset=".5" stop-color="#7cc6e0" stop-opacity=".2"/>
          <stop offset="1" stop-color="#7cc6e0" stop-opacity="0"/>
        </radialGradient>

        <clipPath id="sphereClip"><circle cx="230" cy="218" r="140"/></clipPath>
        <clipPath id="coreClip"><circle cx="230" cy="218" r="134"/></clipPath>

        <filter id="fBlurS" x="-45%" y="-45%" width="190%" height="190%">
          <feGaussianBlur stdDeviation="2.4"/>
        </filter>
        <filter id="fBlurM" x="-45%" y="-45%" width="190%" height="190%">
          <feGaussianBlur stdDeviation="9"/>
        </filter>
        <filter id="fBlurL" x="-60%" y="-60%" width="220%" height="220%">
          <feGaussianBlur stdDeviation="26"/>
        </filter>
        <filter id="fCast" x="-45%" y="-45%" width="190%" height="190%">
          <feDropShadow dx="8" dy="13" stdDeviation="10" flood-color="#05101a" flood-opacity=".55"/>
        </filter>
        <filter id="fCastOut" x="-45%" y="-45%" width="190%" height="190%">
          <feDropShadow dx="0" dy="18" stdDeviation="16" flood-color="#000000" flood-opacity=".55"/>
        </filter>
        <filter id="fGrain" x="0" y="0" width="100%" height="100%">
          <feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="3" stitchTiles="stitch"/>
          <feColorMatrix type="saturate" values="0"/>
        </filter>

        <!-- ================= THE ORIGAMI CRANE ================= -->
        <g id="craneArt">
          <!-- tail, swept up-right -->
          <polygon points="80,46 150,12 96,44" fill="url(#gPaperD)"/>
          <polygon points="80,46 150,12 124,31" fill="url(#gPaperC)"/>
          <polygon points="80,46 124,31 96,44" fill="url(#gPaperD)"/>
          <!-- neck, sweeping up-left -->
          <polygon points="78,46 26,12 22,6 66,40" fill="url(#gPaperC)"/>
          <polygon points="78,46 26,12 45,40" fill="url(#gPaperE)"/>
          <polygon points="78,46 45,40 66,40" fill="url(#gPaperC)"/>
          <!-- head and beak -->
          <polygon points="30,16 22,6 15,11 25,21" fill="url(#gPaperB)"/>
          <polygon points="22,6 8,9 21,12" fill="url(#gPaperD)"/>
          <circle cx="24.4" cy="11.4" r="1.25" fill="#4a3722"/>
          <!-- far wing -->
          <polygon points="78,46 12,60 64,86" fill="url(#gPaperD)"/>
          <polygon points="78,46 12,60 38,73" fill="url(#gPaperB)"/>
          <polygon points="78,46 38,73 64,86" fill="url(#gPaperD)"/>
          <!-- body kite -->
          <polygon points="78,46 52,84 82,102 110,84" fill="url(#gPaperC)"/>
          <polygon points="78,46 52,84 82,102" fill="url(#gPaperC)"/>
          <polygon points="78,46 82,102 110,84" fill="url(#gPaperB)"/>
          <polygon points="78,46 56,72 110,84" fill="url(#gPaperA)" opacity=".55"/>
          <polygon points="78,46 52,84 70,66" fill="url(#gPaperD)" opacity=".55"/>
          <!-- near wing -->
          <polygon points="82,48 152,56 100,92" fill="url(#gPaperD)"/>
          <polygon points="82,48 152,56 127,72" fill="url(#gPaperA)"/>
          <polygon points="82,48 127,72 100,92" fill="url(#gPaperC)"/>
          <polygon points="127,72 152,56 141,64" fill="url(#gPaperB)" opacity=".7"/>
          <!-- creases -->
          <path d="M80 46L124 31M78 46L45 40M78 46L82 102M52 84L82 102M82 102L110 84
                   M78 46L38 73M38 73L64 86M12 60L38 73M82 48L127 72M127 72L100 92
                   M152 56L127 72M82 102L96 84"
                fill="none" stroke="#96693d" stroke-width=".85" stroke-linecap="round" opacity=".5"/>
          <path d="M26 12L22 6M22 6L15 11M30 16L25 21M12 60L78 46M84 60L100 92"
                fill="none" stroke="#a87c4d" stroke-width=".6" stroke-linecap="round" opacity=".38"/>
          <!-- paper grain -->
          <rect x="0" y="0" width="170" height="120" filter="url(#fGrain)" opacity=".085" style="mix-blend-mode:multiply"/>
        </g>
      </defs>

      <!-- ================= SCENE ================= -->
      <circle cx="230" cy="206" r="215" fill="url(#gHalo)"/>
      <ellipse cx="230" cy="374" rx="132" ry="26" fill="url(#gFloorShadow)"/>
      <ellipse class="caustic-pulse" cx="236" cy="366" rx="66" ry="12" fill="url(#gCaustic)" filter="url(#fBlurS)"/>

      <g class="drift">
        <g filter="url(#fCastOut)">
          <circle cx="230" cy="218" r="140" fill="url(#gGlass)"/>

          <g clip-path="url(#sphereClip)">
            <!-- cast shadow of the crane inside the glass -->
            <g transform="translate(230 206) scale(1.6) translate(-82 -54)" opacity=".55" filter="url(#fCast)">
              <use href="#craneArt"/>
            </g>

            <!-- the crane -->
            <g transform="translate(230 200) scale(1.6) translate(-82 -54)">
              <g class="sway"><use href="#craneArt"/></g>
            </g>

            <!-- refraction: displaced, softened ghost -->
            <g transform="translate(238 208) scale(1.6) translate(-82 -54)" opacity=".16" filter="url(#fBlurM)">
              <use href="#craneArt"/>
            </g>
            <!-- refraction: compressed reflection near the base -->
            <g transform="translate(230 316) scale(1.5 -.42) translate(-82 -54)" opacity=".2" filter="url(#fBlurS)">
              <use href="#craneArt"/>
            </g>

            <!-- volumetric shading inside the glass -->
            <ellipse cx="150" cy="128" rx="120" ry="90" fill="url(#gSpec)" opacity=".1" filter="url(#fBlurL)"/>
            <ellipse cx="292" cy="308" rx="72" ry="46" fill="url(#gBounce)" filter="url(#fBlurM)"/>
            <circle cx="230" cy="218" r="137" fill="none" stroke="#04121c" stroke-width="16" opacity=".2" filter="url(#fBlurM)"/>

            <!-- chromatic edge cues -->
            <path d="M98.4 170.1A140 140 0 0 1 361.6 170.1" fill="none" stroke="#6fe3ff" stroke-width="2.4" opacity=".3"/>
            <path d="M101 171.5A137 137 0 0 1 359 171.5" fill="none" stroke="#ff8fd8" stroke-width="2" opacity=".26"/>
            <path d="M92 280A140 140 0 0 0 176 344" fill="none" stroke="#8fd8ff" stroke-width="2.2" opacity=".22"/>
            <path d="M300 344A140 140 0 0 0 374 268" fill="none" stroke="#ffd9a8" stroke-width="2" opacity=".18"/>

            <!-- sparkles -->
            <g transform="translate(332 126)"><g class="twinkle">
              <path d="M0-9Q1.2-1.2 9 0Q1.2 1.2 0 9Q-1.2 1.2-9 0Q-1.2-1.2 0-9Z" fill="#ffffff"/>
            </g></g>
            <g transform="translate(122 288)"><g class="twinkle b">
              <path d="M0-6Q.8-.8 6 0Q.8.8 0 6Q-.8.8-6 0Q-.8-.8 0-6Z" fill="#dff4ff"/>
            </g></g>
          </g>

          <!-- glass shell -->
          <circle cx="230" cy="218" r="139.6" fill="none" stroke="url(#gRim)" stroke-width="2.6"/>
          <circle cx="230" cy="218" r="134" fill="none" stroke="#ffffff" stroke-width="1" opacity=".14"/>
          <ellipse cx="172" cy="152" rx="66" ry="32" transform="rotate(-32 172 152)" fill="#ffffff" opacity=".42" filter="url(#fBlurM)"/>
          <ellipse class="glint" cx="152" cy="136" rx="27" ry="12" transform="rotate(-34 152 136)" fill="#ffffff" opacity=".7" filter="url(#fBlurS)"/>
          <ellipse class="glint" cx="196" cy="118" rx="10" ry="5" transform="rotate(-30 196 118)" fill="#ffffff" opacity=".8" filter="url(#fBlurS)"/>
          <ellipse cx="286" cy="286" rx="44" ry="18" transform="rotate(28 286 286)" fill="#bfeaff" opacity=".2" filter="url(#fBlurM)"/>
          <path d="M124 236A118 118 0 0 1 150 122" fill="none" stroke="#ffffff" stroke-width="4" opacity=".14" filter="url(#fBlurS)"/>
        </g>
      </g>
    </svg>
    <figcaption>
      <span class="kicker">Study in glass &amp; paper</span>
      <span class="title"><em>Crane Suspended</em></span>
      <span class="note">Pure vector — gradients, facets, creases, refraction</span>
    </figcaption>
  </figure>
</main>
</body>
</html>

Test 2: Procedural globe

The globe’s source generates 1024×512 color, bump and specular maps from seeded 3D noise. It adds a shader-based atmosphere, a 560-point particle ring, pointer-drag rotation, keyboard controls, zoom buttons and the requested speed slider. Reduced-motion handling stops automatic animation. The file also checks for WebGL and catches CDN and initialization failures. Its module script passed the JavaScript parser. A DOM simulation with WebGL disabled displayed the intended failure message and retry control. These checks do not verify shader compilation or the rendered result.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>Procedural Globe — Three.js</title>
<style>
  *,*::before,*::after{box-sizing:border-box}
  [hidden]{display:none !important}
  html,body{height:100%}
  body{
    margin:0;overflow:hidden;background:#04060d;color:#e9eefb;
    font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
    -webkit-font-smoothing:antialiased;
  }
  #scene{position:fixed;inset:0;width:100%;height:100%;display:block;touch-action:none;cursor:grab;outline:none}
  #scene:active{cursor:grabbing}
  #scene:focus-visible{outline:2px solid #7fb2ff;outline-offset:-4px}
  .vignette{position:fixed;inset:0;pointer-events:none;z-index:1;
    background:radial-gradient(circle at 50% 44%,rgba(0,0,0,0) 52%,rgba(0,0,0,.6) 100%)}
  header.brand{position:fixed;top:0;left:0;z-index:2;padding:20px 24px;pointer-events:none}
  .brand h1{margin:0;font-size:clamp(15px,2.1vw,21px);font-weight:600;letter-spacing:.18em;text-transform:uppercase}
  .brand p{margin:7px 0 0;font-size:12.5px;letter-spacing:.03em;color:rgba(233,238,251,.58)}
  .hud{
    position:fixed;left:24px;bottom:24px;z-index:3;display:flex;flex-direction:column;gap:16px;
    min-width:250px;padding:16px 18px;border-radius:18px;
    background:rgba(11,17,33,.62);border:1px solid rgba(255,255,255,.09);
    box-shadow:0 18px 50px rgba(0,0,0,.45);
    backdrop-filter:blur(16px) saturate(1.25);-webkit-backdrop-filter:blur(16px) saturate(1.25);
  }
  .zoom-row{display:flex;gap:10px}
  .btn{
    width:44px;height:44px;display:grid;place-items:center;border-radius:12px;
    border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.06);color:#dbe6ff;cursor:pointer;
    transition:transform .15s ease,background .15s ease,border-color .15s ease;
  }
  .btn:hover{background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.3)}
  .btn:active{transform:scale(.93)}
  .btn:focus-visible{outline:2px solid #7fb2ff;outline-offset:2px}
  .btn svg{width:18px;height:18px}
  .slider-block{display:flex;flex-direction:column;gap:6px}
  .slider-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;
    font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;color:rgba(233,238,251,.72)}
  .slider-head output{font-size:12px;letter-spacing:.02em;text-transform:none;color:#9fd2ff;
    font-variant-numeric:tabular-nums}
  input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:22px;margin:0;background:transparent;cursor:pointer}
  input[type=range]::-webkit-slider-runnable-track{height:5px;border-radius:999px;
    background:linear-gradient(90deg,#4f7fd0,#8fd6ff 65%,#26334f)}
  input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:18px;height:18px;margin-top:-6.5px;
    border-radius:50%;background:#eaf4ff;border:1px solid rgba(255,255,255,.75);
    box-shadow:0 0 0 4px rgba(120,180,255,.2),0 2px 8px rgba(0,0,0,.5)}
  input[type=range]::-moz-range-track{height:5px;border-radius:999px;
    background:linear-gradient(90deg,#4f7fd0,#8fd6ff 65%,#26334f)}
  input[type=range]::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:#eaf4ff;
    border:1px solid rgba(255,255,255,.75)}
  input[type=range]:focus-visible{outline:2px solid #7fb2ff;outline-offset:4px;border-radius:8px}
  .hint{position:fixed;right:22px;bottom:24px;z-index:2;font-size:12px;color:rgba(233,238,251,.48);pointer-events:none}
  .status{position:fixed;inset:0;z-index:10;display:grid;place-items:center;padding:24px;
    background:radial-gradient(circle at 50% 40%,rgba(12,20,40,.88),rgba(3,5,12,.96));
    backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);transition:opacity .5s ease}
  .status.is-hidden{opacity:0;pointer-events:none}
  .card{width:min(430px,100%);text-align:center;padding:34px 30px 30px;border-radius:22px;
    background:rgba(12,18,36,.74);border:1px solid rgba(255,255,255,.1);box-shadow:0 24px 70px rgba(0,0,0,.55)}
  .card h2{margin:0 0 10px;font-size:18px;font-weight:600;letter-spacing:.03em}
  .card p{margin:0;font-size:13.5px;line-height:1.65;color:rgba(233,238,251,.72)}
  .spinner{width:44px;height:44px;margin:0 auto 22px;border-radius:50%;
    border:3px solid rgba(143,182,255,.16);border-top-color:#8fd6ff;animation:spin 1s linear infinite}
  @keyframes spin{to{transform:rotate(360deg)}}
  .retry{margin-top:22px;padding:10px 22px;border-radius:12px;border:1px solid rgba(255,255,255,.18);
    background:rgba(120,170,255,.16);color:#e8f2ff;font-size:13px;letter-spacing:.05em;cursor:pointer}
  .retry:hover{background:rgba(120,170,255,.3)}
  .retry:focus-visible{outline:2px solid #7fb2ff;outline-offset:2px}
  noscript div{position:fixed;inset:0;display:grid;place-items:center;padding:24px;text-align:center;font-size:14px}
  @media (max-width:560px){
    .hud{left:14px;right:14px;bottom:14px;min-width:0}
    .hint{display:none}
    .brand p{max-width:230px}
  }
  @media (prefers-reduced-motion: reduce){
    .spinner{animation:none;border-top-color:rgba(143,182,255,.5)}
    .btn{transition:none}
    .status{transition:none}
  }
</style>
</head>
<body>
<canvas id="scene" tabindex="0"
        aria-label="Interactive procedural globe. Drag, or use the arrow keys to rotate and plus and minus to zoom."></canvas>
<div class="vignette" aria-hidden="true"></div>

<header class="brand">
  <h1>Procedural Globe</h1>
  <p>Noise-generated continents · rendered with Three.js</p>
</header>

<section class="hud" aria-label="Globe controls">
  <div class="zoom-row">
    <button class="btn" id="zoomIn" type="button" aria-label="Zoom in" title="Zoom in">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
        <circle cx="11" cy="11" r="6.5"></circle><path d="M11 8.5v5M8.5 11h5M20 20l-4.4-4.4"></path>
      </svg>
    </button>
    <button class="btn" id="zoomOut" type="button" aria-label="Zoom out" title="Zoom out">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
        <circle cx="11" cy="11" r="6.5"></circle><path d="M8.5 11h5M20 20l-4.4-4.4"></path>
      </svg>
    </button>
  </div>
  <div class="slider-block">
    <div class="slider-head">
      <label for="speed">Rotation speed</label>
      <output id="speedOut" for="speed">0.36 rad/s</output>
    </div>
    <input id="speed" type="range" min="0" max="100" step="1" value="30" aria-describedby="speedOut">
  </div>
</section>

<p class="hint">Drag to spin · scroll or use the buttons to zoom</p>

<div class="status" id="status" role="status" aria-live="polite">
  <div class="card">
    <div class="spinner" id="statusSpin" aria-hidden="true"></div>
    <h2 id="statusTitle">Loading Three.js</h2>
    <p id="statusMsg">Fetching the 3D engine from the CDN…</p>
    <button class="retry" id="retry" type="button" hidden>Try again</button>
  </div>
</div>

<noscript><div>This interactive globe requires JavaScript to be enabled.</div></noscript>

<script type="module">
const THREE_URL = 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';

const statusEl   = document.getElementById('status');
const statusSpin = document.getElementById('statusSpin');
const statusTitle= document.getElementById('statusTitle');
const statusMsg  = document.getElementById('statusMsg');
const retryBtn   = document.getElementById('retry');

const clamp = (v, a, b) => Math.min(b, Math.max(a, v));
const mix   = (a, b, t) => a + (b - a) * t;
const nextFrame = () => new Promise(r => requestAnimationFrame(() => setTimeout(r, 0)));

function step(title, msg) { statusTitle.textContent = title; statusMsg.textContent = msg; }

function fail(msg) {
  statusEl.hidden = false;
  statusEl.classList.remove('is-hidden');
  statusEl.removeAttribute('aria-hidden');
  statusEl.setAttribute('role', 'alert');
  statusSpin.hidden = true;
  statusTitle.textContent = 'Unable to display the globe';
  statusMsg.textContent = msg;
  retryBtn.hidden = false;
  retryBtn.focus();
}

function hideStatus() {
  statusEl.classList.add('is-hidden');
  statusEl.setAttribute('aria-hidden', 'true');
  setTimeout(() => { statusEl.hidden = true; }, 600);
}

retryBtn.addEventListener('click', () => location.reload());

function mulberry32(a) {
  return function () {
    a |= 0; a = (a + 0x6D2B79F5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

/* ---------- Procedural planet textures (colour / bump / specular) ---------- */
function buildPlanetTextures(THREE) {
  const W = 1024, H = 512, SEA = 0.515;
  const rnd = mulberry32(20261015);
  const perm = new Uint8Array(512);
  {
    const p = new Uint8Array(256);
    for (let i = 0; i < 256; i++) p[i] = i;
    for (let i = 255; i > 0; i--) { const j = (rnd() * (i + 1)) | 0; const t = p[i]; p[i] = p[j]; p[j] = t; }
    for (let i = 0; i < 512; i++) perm[i] = p[i & 255];
  }
  const fade = t => t * t * t * (t * (t * 6 - 15) + 10);
  const hash = (i, j, k) => perm[(perm[(perm[i & 255] + j) & 255] + k) & 255] / 255;
  function vnoise(x, y, z) {
    const xi = Math.floor(x), yi = Math.floor(y), zi = Math.floor(z);
    const u = fade(x - xi), v = fade(y - yi), w = fade(z - zi);
    const c000 = hash(xi, yi, zi),     c100 = hash(xi + 1, yi, zi);
    const c010 = hash(xi, yi + 1, zi), c110 = hash(xi + 1, yi + 1, zi);
    const c001 = hash(xi, yi, zi + 1), c101 = hash(xi + 1, yi, zi + 1);
    const c011 = hash(xi, yi + 1, zi + 1), c111 = hash(xi + 1, yi + 1, zi + 1);
    return mix(mix(mix(c000, c100, u), mix(c010, c110, u), v),
               mix(mix(c001, c101, u), mix(c011, c111, u), v), w);
  }
  function fbm(x, y, z, oct) {
    let a = 1, f = 1, s = 0, n = 0;
    for (let i = 0; i < oct; i++) { s += a * vnoise(x * f, y * f, z * f); n += a; a *= 0.5; f *= 2; }
    return s / n;
  }

  const colour = new Uint8ClampedArray(W * H * 4);
  const relief = new Uint8ClampedArray(W * H * 4);
  const shine  = new Uint8ClampedArray(W * H * 4);

  for (let y = 0; y < H; y++) {
    const lat = Math.PI * 0.5 - ((y + 0.5) / H) * Math.PI;
    const cl = Math.cos(lat), sl = Math.sin(lat);
    for (let x = 0; x < W; x++) {
      const lon = ((x + 0.5) / W) * Math.PI * 2;
      const nx = cl * Math.cos(lon), ny = sl, nz = cl * Math.sin(lon);

      let e = fbm(nx * 1.7 + 7.3, ny * 1.7 + 2.9, nz * 1.7 + 5.1, 5);
      e += 0.16 * fbm(nx * 4.3 + 21, ny * 4.3 + 13, nz * 4.3 + 3, 3) - 0.08;
      const moist = fbm(nx * 2.6 + 31, ny * 2.6 + 17, nz * 2.6 + 9, 3);
      const polar = clamp((Math.abs(lat) - 1.02) / 0.3, 0, 1);

      let r, g, b, sp, hv;
      if (e < SEA) {
        const t = clamp((SEA - e) * 4.5, 0, 1);
        r = mix(30, 4, t); g = mix(96, 20, t); b = mix(150, 58, t);
        sp = 235; hv = SEA;
      } else {
        if (moist < 0.42)      { r = 176; g = 148; b = 100; }
        else if (moist < 0.6)  { r = 128; g = 142; b = 90; }
        else                   { r = 62;  g = 112; b = 68; }
        const rock = clamp((e - SEA - 0.11) / 0.22, 0, 1);
        r = mix(r, 118, rock); g = mix(g, 110, rock); b = mix(b, 102, rock);
        sp = 28; hv = e;
      }
      const snow = clamp((e - SEA - 0.26) / 0.2, 0, 1);
      const ice  = clamp(Math.max(polar, snow), 0, 1);
      r = mix(r, 238, ice); g = mix(g, 244, ice); b = mix(b, 250, ice);
      if (ice > 0.35) sp = 120;

      const i4 = (y * W + x) * 4;
      colour[i4] = r; colour[i4 + 1] = g; colour[i4 + 2] = b; colour[i4 + 3] = 255;
      const bv = clamp(hv, 0, 1) * 255;
      relief[i4] = relief[i4 + 1] = relief[i4 + 2] = bv; relief[i4 + 3] = 255;
      shine[i4] = shine[i4 + 1] = shine[i4 + 2] = sp; shine[i4 + 3] = 255;
    }
  }

  function toTexture(data, srgb) {
    const c = document.createElement('canvas');
    c.width = W; c.height = H;
    c.getContext('2d').putImageData(new ImageData(data, W, H), 0, 0);
    const t = new THREE.CanvasTexture(c);
    if (srgb) t.colorSpace = THREE.SRGBColorSpace;
    return t;
  }
  return { colour: toTexture(colour, true), bump: toTexture(relief), spec: toTexture(shine) };
}

function buildSparkTexture(THREE) {
  const c = document.createElement('canvas');
  c.width = c.height = 64;
  const ctx = c.getContext('2d');
  const g = ctx.createRadialGradient(32, 32, 0, 32, 32, 32);
  g.addColorStop(0, 'rgba(255,255,255,1)');
  g.addColorStop(0.35, 'rgba(190,225,255,.75)');
  g.addColorStop(1, 'rgba(120,180,255,0)');
  ctx.fillStyle = g;
  ctx.fillRect(0, 0, 64, 64);
  return new THREE.CanvasTexture(c);
}

/* ---------- Scene ---------- */
const ATMO_VS = `
varying vec3 vNormalW; varying vec3 vViewW;
void main() {
  vec4 wp = modelMatrix * vec4(position, 1.0);
  vNormalW = normalize(mat3(modelMatrix) * normal);
  vViewW = normalize(cameraPosition - wp.xyz);
  gl_Position = projectionMatrix * viewMatrix * wp;
}`;

const ATMO_FS = `
uniform vec3 uColor; uniform vec3 uSun; uniform float uIntensity;
varying vec3 vNormalW; varying vec3 vViewW;
void main() {
  vec3 n = normalize(vNormalW);
  vec3 v = normalize(vViewW);
  float rim = clamp(1.0 - abs(dot(n, v)), 0.0, 1.0);
  float glow = pow(rim, 2.4);
  float lit = 0.22 + 0.78 * max(dot(n, normalize(uSun)), 0.0);
  gl_FragColor = vec4(uColor, glow * lit * uIntensity);
}`;

function init(THREE, maps) {
  const canvas = document.getElementById('scene');
  const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, powerPreference: 'high-performance' });
  renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
  renderer.setSize(window.innerWidth, window.innerHeight, false);
  renderer.outputColorSpace = THREE.SRGBColorSpace;
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = 1.06;

  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x04060e);

  const camera = new THREE.PerspectiveCamera(42, window.innerWidth / window.innerHeight, 0.1, 400);
  camera.position.set(0, 0, 3.6);

  const sun = new THREE.DirectionalLight(0xfff1dd, 2.2);
  sun.position.set(5, 2.2, 3.4);
  scene.add(sun);
  scene.add(new THREE.AmbientLight(0x2b3a5e, 0.35));
  scene.add(new THREE.HemisphereLight(0x334a7a, 0x05070f, 0.28));

  /* Starfield */
  const starRnd = mulberry32(4242);
  function starLayer(count, size, colour, radius) {
    const pos = new Float32Array(count * 3);
    for (let i = 0; i < count; i++) {
      const u = starRnd() * 2 - 1, th = starRnd() * Math.PI * 2, s = Math.sqrt(1 - u * u);
      pos[i * 3] = Math.cos(th) * s * radius;
      pos[i * 3 + 1] = u * radius;
      pos[i * 3 + 2] = Math.sin(th) * s * radius;
    }
    const geo = new THREE.BufferGeometry();
    geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
    return new THREE.Points(geo, new THREE.PointsMaterial({
      color: colour, size, sizeAttenuation: false, transparent: true,
      opacity: 0.9, depthWrite: false
    }));
  }
  const stars = new THREE.Group();
  stars.add(starLayer(1100, 1.6, 0xffffff, 90));
  stars.add(starLayer(180, 2.8, 0x9fc4ff, 70));
  scene.add(stars);

  /* Globe */
  const world = new THREE.Group();
  scene.add(world);

  maxAniso(renderer, maps);
  maps.colour.anisotropy = renderer.capabilities.getMaxAnisotropy();

  const globe = new THREE.Mesh(
    new THREE.SphereGeometry(1, 96, 64),
    new THREE.MeshPhongMaterial({
      map: maps.colour, bumpMap: maps.bump, bumpScale: 0.022,
      specularMap: maps.spec, specular: new THREE.Color(0x2a4c7a), shininess: 24
    })
  );
  world.add(globe);

  /* Atmosphere */
  const atmo = new THREE.Mesh(
    new THREE.SphereGeometry(1, 64, 48),
    new THREE.ShaderMaterial({
      uniforms: {
        uColor: { value: new THREE.Color(0x5fa8ff) },
        uSun: { value: sun.position.clone().normalize() },
        uIntensity: { value: 1.15 }
      },
      vertexShader: ATMO_VS, fragmentShader: ATMO_FS,
      transparent: true, blending: THREE.AdditiveBlending,
      side: THREE.BackSide, depthWrite: false
    })
  );
  atmo.scale.setScalar(1.23);
  world.add(atmo);

  /* Orbiting particle ring */
  const RING = 560;
  const ringGeo = new THREE.BufferGeometry();
  const posArr = new Float32Array(RING * 3);
  const ang = new Float32Array(RING), rad = new Float32Array(RING);
  const yOff = new Float32Array(RING), spd = new Float32Array(RING);
  const ringRnd = mulberry32(777);
  for (let i = 0; i < RING; i++) {
    ang[i] = ringRnd() * Math.PI * 2;
    rad[i] = 1.34 + ringRnd() * 0.36;
    yOff[i] = (ringRnd() - 0.5) * 0.05;
    spd[i] = 0.10 + ringRnd() * 0.12;
    posArr[i * 3] = Math.cos(ang[i]) * rad[i];
    posArr[i * 3 + 1] = yOff[i];
    posArr[i * 3 + 2] = Math.sin(ang[i]) * rad[i];
  }
  ringGeo.setAttribute('position', new THREE.BufferAttribute(posArr, 3));
  const ring = new THREE.Points(ringGeo, new THREE.PointsMaterial({
    map: buildSparkTexture(THREE), size: 0.032, sizeAttenuation: true,
    color: 0xbfe4ff, transparent: true, opacity: 0.95,
    blending: THREE.AdditiveBlending, depthWrite: false
  }));
  const ringOrbit = new THREE.Group();
  ringOrbit.rotation.set(-0.22, 0, 0.44);
  ringOrbit.add(ring);
  world.add(ringOrbit);

  function updateRing(dt) {
    const arr = ringGeo.attributes.position.array;
    for (let i = 0; i < RING; i++) {
      ang[i] += spd[i] * dt;
      const r = rad[i];
      arr[i * 3] = Math.cos(ang[i]) * r;
      arr[i * 3 + 1] = yOff[i] + Math.sin(ang[i] * 3) * 0.02;
      arr[i * 3 + 2] = Math.sin(ang[i]) * r;
    }
    ringGeo.attributes.position.needsUpdate = true;
  }

  /* Controls */
  const speedInput = document.getElementById('speed');
  const speedOut = document.getElementById('speedOut');
  const zoomIn = document.getElementById('zoomIn');
  const zoomOut = document.getElementById('zoomOut');
  const MIN_D = 1.55, MAX_D = 8;

  const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
  let reduced = mq.matches;
  let speed = 0;
  function syncSpeed() {
    speed = (speedInput.value / 100) * 1.2;
    speedOut.textContent = speed.toFixed(2) + ' rad/s';
  }
  if (reduced) speedInput.value = 0;
  syncSpeed();
  speedInput.addEventListener('input', syncSpeed);
  if (mq.addEventListener) {
    mq.addEventListener('change', ev => {
      reduced = ev.matches;
      if (reduced) { speedInput.value = 0; spin = 0; }
      syncSpeed();
    });
  }

  let yaw = 0.9, pitch = 0.32, spin = 0;
  let dist = 3.6, targetDist = 3.6;
  let dragging = false, lastX = 0, lastY = 0, vel = 0;
  let dirty = true;

  canvas.addEventListener('pointerdown', e => {
    dragging = true;
    canvas.setPointerCapture(e.pointerId);
    lastX = e.clientX; lastY = e.clientY; vel = 0;
    canvas.focus({ preventScroll: true });
    dirty = true;
  });
  canvas.addEventListener('pointermove', e => {
    if (!dragging) return;
    const dx = e.clientX - lastX, dy = e.clientY - lastY;
    lastX = e.clientX; lastY = e.clientY;
    yaw += dx * 0.0055;
    pitch = clamp(pitch + dy * 0.0055, -1.25, 1.25);
    vel = dx * 0.055;
    dirty = true;
  });
  function endDrag() {
    if (!dragging) return;
    dragging = false;
    spin = reduced ? 0 : vel * 0.9;
    dirty = true;
  }
  canvas.addEventListener('pointerup', endDrag);
  canvas.addEventListener('pointercancel', endDrag);
  canvas.addEventListener('lostpointercapture', endDrag);

  canvas.addEventListener('wheel', e => {
    e.preventDefault();
    targetDist = clamp(targetDist * Math.exp(e.deltaY * 0.0012), MIN_D, MAX_D);
    dirty = true;
  }, { passive: false });

  zoomIn.addEventListener('click', () => { targetDist = clamp(targetDist * 0.78, MIN_D, MAX_D); dirty = true; });
  zoomOut.addEventListener('click', () => { targetDist = clamp(targetDist * 1.28, MIN_D, MAX_D); dirty = true; });

  canvas.addEventListener('keydown', e => {
    switch (e.key) {
      case 'ArrowLeft':  yaw -= 0.09; break;
      case 'ArrowRight': yaw += 0.09; break;
      case 'ArrowUp':    pitch = clamp(pitch - 0.07, -1.25, 1.25); break;
      case 'ArrowDown':  pitch = clamp(pitch + 0.07, -1.25, 1.25); break;
      case '+': case '=': targetDist = clamp(targetDist * 0.82, MIN_D, MAX_D); break;
      case '-': case '_': targetDist = clamp(targetDist * 1.22, MIN_D, MAX_D); break;
      default: return;
    }
    e.preventDefault();
    dirty = true;
  });

  function onResize() {
    const w = window.innerWidth, h = window.innerHeight;
    camera.aspect = w / h;
    camera.updateProjectionMatrix();
    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
    renderer.setSize(w, h, false);
    dirty = true;
  }
  window.addEventListener('resize', onResize);
  window.addEventListener('orientationchange', onResize);

  /* Render loop */
  let last = performance.now();
  function tick(now) {
    requestAnimationFrame(tick);
    const dt = Math.min(0.05, (now - last) / 1000) || 0;
    last = now;

    if (!reduced && speed > 0) { yaw += speed * dt; dirty = true; }
    if (Math.abs(spin) > 0.0005) { yaw += spin * dt; spin *= Math.exp(-2.2 * dt); dirty = true; }
    else spin = 0;

    if (!reduced && speed > 0) {
      updateRing(dt);
      ringOrbit.rotation.y += dt * 0.05;
      stars.rotation.y -= dt * 0.004;
      dirty = true;
    }
    if (Math.abs(dist - targetDist) > 1e-4) {
      dist += (targetDist - dist) * Math.min(1, dt * 7.5);
      dirty = true;
    }

    world.rotation.set(pitch, yaw, 0.07);
    camera.position.set(0, 0, dist);

    if (dirty) { dirty = false; renderer.render(scene, camera); }
  }
  requestAnimationFrame(tick);
}

function maxAniso() {}

/* ---------- Boot ---------- */
function hasWebGL() {
  try {
    const c = document.createElement('canvas');
    return !!(c.getContext('webgl2') || c.getContext('webgl'));
  } catch (e) { return false; }
}

if (!hasWebGL()) {
  fail('WebGL is unavailable in this browser, so the globe cannot be rendered. Try an up-to-date Chrome, Edge, Firefox, or Safari with hardware acceleration enabled.');
} else {
  const timer = setTimeout(() => fail('Three.js took too long to load from cdn.jsdelivr.net. Check your network connection and try again.'), 20000);
  step('Loading Three.js', 'Fetching the 3D engine from the CDN…');
  import(THREE_URL).then(async THREE => {
    clearTimeout(timer);
    try {
      step('Generating terrain', 'Painting continents from procedural noise…');
      await nextFrame();
      const maps = buildPlanetTextures(THREE);
      step('Assembling the scene', 'Building the globe, atmosphere, and orbiting light ring…');
      await nextFrame();
      init(THREE, maps);
      hideStatus();
    } catch (err) {
      console.error(err);
      fail('The scene failed to initialise. ' + (err && err.message ? err.message : ''));
    }
  }).catch(err => {
    clearTimeout(timer);
    console.error(err);
    fail('The Three.js module could not be downloaded from cdn.jsdelivr.net. Check your connection or any content blocker, then try again.');
  });
}
</script>
</body>
</html>

Test 3: Floating-island wizard game

The first response is incomplete. It contains the HUD and menu markup plus the beginning of a boot script, then ends in stray tool-call markup without closing the script or HTML document. It supplies no movement, collision, collection or turret implementation. It also refers to an undeclared failElementShown variable. OpenCode reported a stop finish reason, so the evidence does not establish why generation ended early. The original partial response is preserved here as a failed submission, not a playable game.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>Skybound Wizard — Floating Isles</title>
<style>
  *{box-sizing:border-box}
  html,body{margin:0;height:100%;overflow:hidden;background:#bfe6ff;color:#2b2547;
    font-family:"Trebuchet MS",ui-rounded,system-ui,Verdana,sans-serif;
    -webkit-user-select:none;user-select:none;touch-action:none}
  #app{position:fixed;inset:0}
  canvas{display:block}
  .hud{position:fixed;inset:0;pointer-events:none;padding:12px;z-index:5}
  .card{background:rgba(255,255,255,.78);border:2px solid rgba(255,255,255,.9);
    border-radius:14px;padding:10px 14px;box-shadow:0 6px 18px rgba(60,50,110,.16);backdrop-filter:blur(4px)}
  #topL{position:absolute;top:12px;left:12px;min-width:190px}
  #topR{position:absolute;top:12px;right:12px;text-align:right;line-height:1.5}
  #topR b{color:#6b3fb5;font-size:1.15em}
  .hprow{display:flex;align-items:center;gap:10px}
  .bar{width:140px;height:16px;background:#e7e2f5;border-radius:9px;overflow:hidden;border:1px solid #cbc3e8}
  .bar>i{display:block;height:100%;width:100%;background:linear-gradient(90deg,#7ef0b0,#4fd1c5);transition:width .12s}
  .hprow b{font-size:15px}
  #phase{position:absolute;top:96px;left:14px;font-weight:bold;letter-spacing:.06em;color:#4a3f77;text-shadow:0 1px 0 #fff}
  #hint{position:absolute;left:50%;transform:translateX(-50%);bottom:12px;font-size:13.5px;
    background:rgba(255,255,255,.72);border-radius:999px;padding:7px 16px;white-space:nowrap;
    border:1px solid rgba(255,255,255,.9);box-shadow:0 4px 12px rgba(60,50,110,.14)}
  #flash{position:fixed;inset:0;pointer-events:none;background:#ff4d6d;opacity:0;z-index:4}
  .ov{position:fixed;inset:0;display:none;align-items:center;justify-content:center;z-index:10;
    background:radial-gradient(circle at 50% 30%,rgba(180,230,255,.75),rgba(70,60,120,.72));padding:18px}
  .ov.show{display:flex}
  .panel{max-width:520px;width:100%;background:rgba(255,255,255,.93);border-radius:22px;padding:24px 26px;
    text-align:center;box-shadow:0 18px 50px rgba(30,20,70,.35);border:3px solid #fff}
  .panel h1{margin:0 0 6px;font-size:34px;color:#6b3fb5;letter-spacing:.02em}
  .panel h2{margin:0 0 10px;font-size:27px;color:#6b3fb5}
  .panel p{margin:6px 0;font-size:15px;line-height:1.5}
  .keys{display:grid;grid-template-columns:1fr 1fr;gap:6px 14px;margin:14px 0 4px;font-size:14px;text-align:left}
  .keys b{color:#3d8f8a}
  kbd{background:#efeafd;border:1px solid #cbc3e8;border-bottom-width:2px;border-radius:6px;padding:1px 7px;font-size:13px}
  button{pointer-events:auto;margin-top:14px;font:inherit;font-weight:bold;font-size:17px;color:#fff;
    background:linear-gradient(180deg,#a58bff,#7a5bf0);border:none;border-radius:14px;padding:12px 26px;cursor:pointer;
    box-shadow:0 6px 0 #5b42c8,0 10px 18px rgba(60,40,140,.3);transition:transform .08s,box-shadow .08s}
  button:active{transform:translateY(4px);box-shadow:0 2px 0 #5b42c8}
  .tiny{font-size:13px;color:#5a5178}
  #touch{position:fixed;inset:0;display:none;z-index:6;pointer-events:none}
  #stick{position:absolute;left:22px;bottom:22px;width:118px;height:118px;border-radius:50%;
    background:rgba(255,255,255,.28);border:2px solid rgba(255,255,255,.7);pointer-events:auto}
  #knob{position:absolute;left:39px;top:39px;width:40px;height:40px;border-radius:50%;
    background:rgba(255,255,255,.85);box-shadow:0 2px 8px rgba(0,0,0,.2)}
  #jumpb{position:absolute;right:26px;bottom:34px;width:86px;height:86px;border-radius:50%;pointer-events:auto;
    background:rgba(165,139,255,.55);border:2px solid rgba(255,255,255,.8);color:#fff;font-weight:bold;
    display:flex;align-items:center;justify-content:center;font-size:15px}
  @media (max-width:560px){
    .panel h1{font-size:26px}.keys{grid-template-columns:1fr}#hint{font-size:11.5px;bottom:8px;padding:6px 10px}
    .bar{width:96px}#topR{font-size:13px}
  }
</style>
</head>
<body>
<div id="app"></div>
<div id="flash"></div>

<div class="hud">
  <div class="card" id="topL">
    <div class="hprow"><div class="bar"><i id="hpfill"></i></div><b id="hplbl">100</b></div>
    <div class="tiny" style="margin-top:4px">Health</div>
  </div>
  <div class="card" id="topR">Score <b id="score">0</b><br>Crystals <b id="crys">0/11</b></div>
  <div id="phase">Dawn</div>
  <div id="hint">WASD move &nbsp;·&nbsp; Space jump &nbsp;·&nbsp; Drag = orbit camera &nbsp;·&nbsp; Wheel = zoom &nbsp;·&nbsp; Click / F = spell</div>
</div>

<div id="touch">
  <div id="stick"><div id="knob"></div></div>
  <div id="jumpb">JUMP</div>
</div>

<div class="ov" id="start">
  <div class="panel">
    <h1>Skybound Wizard</h1>
    <p>Collect all <b>11 crystals</b> scattered across the pastel floating isles.</p>
    <div class="keys">
      <div><b>WASD</b> walk (relative to camera)</div><div><b>Space</b> jump / double-tap hop</div>
      <div><b>Mouse drag</b> orbit camera</div><div><b>Wheel</b> zoom in / out</div>
      <div><b>Click / F</b> cast bolt (stuns turrets)</div><div><b>Esc / P</b> pause</div>
    </div>
    <p class="tiny">Watch the elemental guardians. Fall off and you lose health — the realm drags you back to safe ground.</p>
    <button id="startBtn">Begin Adventure</button>
  </div>
</div>

<div class="ov" id="win">
  <div class="panel">
    <h2>Victory, Skybound!</h2>
    <p id="winTxt">Every crystal gathered.</p>
    <button id="winBtn">Play Again</button>
  </div>
</div>

<div class="ov" id="lose">
  <div class="panel">
    <h2>The Void Took You...</h2>
    <p id="loseTxt">Your robe is singed, but the islands remain.</p>
    <button id="loseBtn">Try Again</button>
  </div>
</div>

<div class="ov" id="pause">
  <div class="panel">
    <h2>Paused</h2>
    <p class="tiny">Focus lost — the dream holds its breath.</p>
    <button id="resumeBtn">Resume</button>
  </div>
</div>

<div class="ov" id="fail">
  <div class="panel">
    <h2>Something went wrong</h2>
    <p id="failmsg">Unable to start the experience.</p>
    <p class="tiny">Please check your WebGL support and internet connection (the game needs the Three.js CDN).</p>
    <button onclick="location.reload()">Reload</button>
  </div>
</div>

<script type="module">
/* ============================ boot ============================ */
const $ = id => document.getElementById(id);
const show = el => el.classList.add('show');
const hide = el => el.classList.remove('show');
const fail = m => { $('failmsg').textContent = m; show($('fail')); };

function webglOK(){
  try{
    const c = document.createElement('canvas');
    return !!(window.WebGLRenderingContext && (c.getContext('webgl') || c.getContext('experimental-webgl')));
  }catch(e){ return false; }
}

let THREE = null;
try{
  THREE = await Promise.race([
    import('https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js'),
    new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 12000))
  ]);
}catch(e){
  fail('Could not load Three.js from the CDN (' + e.message + ').');
}
if(THREE && !webglOK()){
  fail('Your browser or GPU does not support WebGL.');
  THREE = null;
}
if(THREE && !failElementShown) start(THREE);

</parameter>
</function>
</tool_call>
The same-prompt retry also failed delivery. It stops mid-sentence inside the start-panel HTML, before a game script or closing document tags. This time OpenCode reported a length finish reason, indicating a generation-length limit rather than a complete answer. Its telemetry reports 30,249 reasoning tokens and 1,751 visible output tokens, a combined 32,000. We did not manually set an output cap, and these figures do not establish the base model’s maximum output. The record does not establish why the first attempt stopped with malformed markup. We have no playable game from either attempt. The second original is embedded below as another incomplete response.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Skylands Wizard — Crystal Hunt</title>
<style>
:root{--ink:#2f3260}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;overflow:hidden;background:#bfe6ff;color:var(--ink);font-family:"Trebuchet MS","Segoe UI",system-ui,sans-serif;touch-action:none;-webkit-user-select:none;user-select:none}
canvas{position:fixed;inset:0;display:block}
#hud{position:fixed;inset:0;z-index:4;pointer-events:none}
.card{background:rgba(255,255,255,.8);border:2px solid #fff;border-radius:14px;padding:7px 12px;font-weight:700;font-size:15px;box-shadow:0 6px 16px rgba(60,70,120,.2);backdrop-filter:blur(5px)}
#topbar{position:absolute;top:12px;left:12px;right:12px;display:flex;gap:10px;justify-content:space-between;align-items:flex-start}
#stats{display:flex;align-items:center;gap:14px}
#hearts{display:flex;gap:5px}
.heart{position:relative;width:16px;height:15px;transition:.2s}
.heart::before,.heart::after{content:"";position:absolute;width:10px;height:15px;background:#ff6f91;border-radius:10px 10px 0 0}
.heart::before{left:6px;transform:rotate(46deg);transform-origin:0 100%}
.heart::after{transform:rotate(-46deg);transform-origin:100% 100%}
.heart.lost::before,.heart.lost::after{background:rgba(120,130,170,.25)}
#hint{position:absolute;left:12px;bottom:12px;font-size:13.5px;max-width:min(600px,74vw);line-height:1.55;font-weight:600}
#hint b{color:#5a4fd0}
#toast{position:absolute;top:72px;left:50%;transform:translateX(-50%) translateY(-8px);font-weight:800;font-size:17px;padding:8px 16px;border-radius:12px;background:rgba(47,50,96,.85);color:#fff;opacity:0;transition:.25s}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
#flash{position:fixed;inset:0;background:radial-gradient(circle,transparent 42%,rgba(255,60,80,.55));opacity:0;pointer-events:none;transition:opacity .4s;z-index:6}
#flash.on{opacity:1;transition:opacity .04s}
.gem{width:12px;height:12px;background:#8ef2ff;transform:rotate(45deg);display:inline-block;box-shadow:0 0 9px #8ef2ff}
.overlay{position:fixed;inset:0;z-index:8;display:grid;place-items:center;padding:18px;background:rgba(120,150,200,.32);backdrop-filter:blur(3px)}
.panel{width:min(580px,94vw);background:rgba(255,255,255,.94);border:3px solid #fff;border-radius:22px;box-shadow:0 22px 60px rgba(40,50,100,.35);padding:24px;text-align:center}
h1{font-size:clamp(22px,4.6vw,34px);letter-spacing:.5px;margin-bottom:6px;background:linear-gradient(90deg,#6c5ce7,#00b4d8,#ff6f91);-webkit-background-clip:text;background-clip:text;color:transparent}
h2{font-size:clamp(20px,4vw,30px);margin-bottom:6px}
p.sub{font-size:14px;opacity:.8;margin-bottom:14px}
.keys{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin:12px 0 18px}
.key{background:#f0f3ff;border:2px solid #d8ddf7;border-radius:10px;padding:6px 10px;font-size:13px;font-weight:700}
.btn{cursor:pointer;border:0;border-radius:14px;padding:13px 26px;font:inherit;font-weight:800;font-size:16px;color:#fff;background:linear-gradient(135deg,#6c5ce7,#00b4d8);box-shadow:0 10px 22px rgba(80,90,200,.35);transition:transform .12s}
.btn:hover{transform:translateY(-2px)}
.btn:active{transform:translateY(1px)}
#endStats{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;margin:14px 0 18px}
#endStats div{background:#f2f5ff;border-radius:12px;padding:8px 14px;min-width:84px}
#endStats b{display:block;font-size:19px}
#endStats span{font-size:11px;opacity:.7;text-transform:uppercase;letter-spacing:.06em}
.hidden{display:none!important}
#fatal .panel{border-color:#ffb3c0;background:#fff5f7}
#fatal h2{color:#d63f5c}
#fatal .detail{font-size:12.5px;opacity:.75;margin-top:8px;word-break:break-word}
@media (max-width:560px){#hint{font-size:12px}.card{font-size:13px;padding:6px 9px}}
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="hud">
  <div id="topbar">
    <div class="card" id="stats">
      <div id="hearts"></div>
      <div>Score <b id="score">0</b></div>
      <div style="display:flex;align-items:center;gap:6px"><span class="gem"></span><b id="crystalCount">0</b>/8</div>
    </div>
    <div class="card"><span id="phase">Day</span> &middot; <b id="timer">0:00</b></div>
  </div>
  <div id="hint" class="card"><b>WASD</b> move &middot; <b>Space</b> jump &middot; <b>Drag</b> orbit camera &middot; <b>Wheel</b> zoom &middot; <b>P / Esc</b> pause<br>Crystals sit on islands and on the small floating platforms &mdash; every one is reachable. Turrets fire on sight, so break line of sight behind islands.</div>
  <div id="toast"></div>
</div>
<div id="flash"></div>
<div id="overlay" class="overlay">
  <div id="startPanel" class="panel">
    <h1>Skylands Wizard</h1>
    <p class="sub">Reclaim the 8 crystals of the floating is

Test 4: Accessible task board

This task exercises application behavior: adding, moving and deleting cards, title search, counts, reset confirmation, and persistence. The returned file uses real form controls, visible labels, a status region and textContent for task titles. Its inline JavaScript parsed successfully. The node-based DOM checks passed 15 of 16 cases, including literal handling of an HTML-like task title, case-insensitive search, blocked storage, malformed saved JSON, and a simulated storage quota failure.
The failing case is reproducible: delete all three cards, then reload the saved state. The board restores the three demo cards instead of preserving the empty board. Its loader uses tasks.length ? tasks : seedTasks(), which treats an intentional empty list as missing data. Normal nonempty changes survive the simulated reload. The original remains embedded below with this defect intact, so its result is inspectable.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Task Board</title>
<style>
  :root {
    color-scheme: light dark;
    --bg: #f4f6fb;
    --surface: #ffffff;
    --surface-alt: #eef1f8;
    --text: #1a2033;
    --muted: #5b6478;
    --border: #d7dce8;
    --accent: #2b5cff;
    --accent-text: #ffffff;
    --danger: #c02525;
    --todo: #6b7a99;
    --doing: #b06a00;
    --done: #1c7a4a;
    --focus: #0b3cd4;
    --shadow: 0 1px 2px rgba(16, 24, 40, 0.08), 0 4px 12px rgba(16, 24, 40, 0.06);
  }
  @media (prefers-color-scheme: dark) {
    :root {
      --bg: #101422;
      --surface: #191f31;
      --surface-alt: #1f263a;
      --text: #eef1f8;
      --muted: #a6b0c6;
      --border: #2c3category;
      --border: #2c3448;
      --accent: #7d9bff;
      --accent-text: #101422;
      --danger: #ff8080;
      --todo: #9fb0d0;
      --doing: #f0b45c;
      --done: #6fd39b;
      --focus: #9db6ff;
      --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 6px 16px rgba(0, 0, 0, 0.35);
    }
  }
  * { box-sizing: border-box; }
  html, body { margin: 0; padding: 0; }
  body {
    background: var(--bg);
    color: var(--text);
    font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    padding: 24px 16px 48px;
  }
  .sr-only {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  .shell { max-width: 1080px; margin: 0 auto; }
  header.app { margin-bottom: 20px; }
  header.app h1 { font-size: 1.6rem; margin: 0 0 4px; letter-spacing: -0.01em; }
  header.app p { margin: 0; color: var(--muted); font-size: 0.95rem; }

  .panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow);
    padding: 16px;
  }
  form.add-task { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; }
  .field { display: flex; flex-direction: column; gap: 6px; flex: 1 1 240px; }
  .field label { font-size: 0.85rem; font-weight: 600; color: var(--muted); }

  input[type="text"] {
    font: inherit;
    color: var(--text);
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 10px 12px;
    width: 100%;
  }
  input[type="text"]::placeholder { color: var(--muted); }

  button {
    font: inherit;
    font-weight: 600;
    cursor: pointer;
    border-radius: 8px;
    border: 1px solid transparent;
    padding: 10px 14px;
  }
  .btn-primary {
    background: var(--accent);
    color: var(--accent-text);
    border-color: var(--accent);
  }
  .btn-primary:hover { filter: brightness(1.07); }
  .btn-ghost {
    background: transparent;
    color: var(--text);
    border-color: var(--border);
  }
  .btn-ghost:hover { background: var(--surface-alt); }
  .btn-danger {
    background: transparent;
    color: var(--danger);
    border-color: var(--danger);
    padding: 6px 10px;
    font-size: 0.85rem;
  }
  .btn-danger:hover { background: rgba(192, 37, 37, 0.1); }

  :focus-visible {
    outline: 3px solid var(--focus);
    outline-offset: 2px;
  }

  .toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    align-items: flex-end;
    margin: 16px 0;
  }

  .notice {
    margin: 0 0 16px;
    padding: 10px 12px;
    border: 1px dashed var(--border);
    border-radius: 8px;
    background: var(--surface-alt);
    color: var(--muted);
    font-size: 0.9rem;
  }
  .notice[hidden] { display: none; }

  .board {
    display: grid;
    gap: 16px;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    align-items: start;
  }
  .column {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow);
    padding: 14px;
    min-height: 180px;
  }
  .column-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin: 0 0 12px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--border);
  }
  .column-head h2 {
    font-size: 1.05rem;
    margin: 0;
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--todo); flex: none; }
  .column[data-column="doing"] .dot { background: var(--doing); }
  .column[data-column="done"] .dot { background: var(--done); }
  .count {
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--muted);
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 2px 10px;
  }
  ul.tasks { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
  li.card {
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-left: 4px solid var(--todo);
    border-radius: 10px;
    padding: 12px;
    display: grid;
    gap: 10px;
  }
  .column[data-column="doing"] li.card { border-left-color: var(--doing); }
  .column[data-column="done"] li.card { border-left-color: var(--done); }
  li.card .title { font-weight: 600; overflow-wrap: anywhere; }
  li.card .controls { display: flex; gap: 8px; flex-wrap: wrap; }
  li.card .btn-move {
    background: var(--accent);
    color: var(--accent-text);
    border-color: var(--accent);
    padding: 6px 10px;
    font-size: 0.85rem;
  }
  li.card .btn-move:hover { filter: brightness(1.07); }
  li.card .btn-move[aria-disabled="true"] {
    background: transparent;
    color: var(--muted);
    border-color: var(--border);
    cursor: not-allowed;
  }
  .empty {
    margin: 4px 0 0;
    padding: 14px 12px;
    border: 1px dashed var(--border);
    border-radius: 8px;
    color: var(--muted);
    font-size: 0.9rem;
    text-align: center;
  }
  @media (max-width: 520px) {
    body { padding: 16px 10px 40px; }
    form.add-task { flex-direction: column; align-items: stretch; }
    button { width: 100%; }
    .toolbar { flex-direction: column; align-items: stretch; }
    li.card .controls button { width: auto; }
  }
</style>
</head>
<body>
<div class="shell">
  <header class="app">
    <h1>Task board</h1>
    <p>Organize work across three stages. Everything is saved in this browser.</p>
  </header>

  <p class="notice" id="storage-notice" hidden></p>

  <section class="panel" aria-labelledby="add-heading">
    <h2 class="sr-only" id="add-heading">Add a task</h2>
    <form class="add-task" id="task-form" novalidate>
      <div class="field">
        <label for="task-title">Task title</label>
        <input type="text" id="task-title" name="task-title" placeholder="e.g. Draft agenda" autocomplete="off" maxlength="120" required>
      </div>
      <button type="submit" id="add-task">Add task</button>
    </form>
  </section>

  <div class="toolbar">
    <div class="field">
      <label for="task-search">Search tasks</label>
      <input type="text" id="task-search" name="task-search" placeholder="Filter by title" autocomplete="off">
    </div>
    <button type="button" class="btn-ghost" id="reset-demo">Reset demo</button>
  </div>

  <main class="board" id="board" aria-label="Task board"></main>
</div>

<div class="sr-only" id="live-region" role="status" aria-live="polite" aria-atomic="true"></div>

<script>
(function () {
  "use strict";

  var COLUMNS = [
    { id: "todo", label: "To do" },
    { id: "doing", label: "Doing" },
    { id: "done", label: "Done" }
  ];
  var ORDER = { todo: 0, doing: 1, done: 2 };
  var SEEDS = [
    { title: "Plan launch", status: "todo" },
    { title: "Build prototype", status: "doing" },
    { title: "Write brief", status: "done" }
  ];
  var STORAGE_KEY = "task-board.v1";

  var board = document.getElementById("board");
  var form = document.getElementById("task-form");
  var titleInput = document.getElementById("task-title");
  var searchInput = document.getElementById("task-search");
  var resetButton = document.getElementById("reset-demo");
  var live = document.getElementById("live-region");
  var storageNotice = document.getElementById("storage-notice");

  function createStore() {
    try {
      var probe = "__task_board_probe__";
      window.localStorage.setItem(probe, "1");
      window.localStorage.removeItem(probe);
      return {
        persistent: true,
        read: function () { return window.localStorage.getItem(STORAGE_KEY); },
        write: function (value) { window.localStorage.setItem(STORAGE_KEY, value); }
      };
    } catch (error) {
      var memory = null;
      return {
        persistent: false,
        read: function () { return memory; },
        write: function (value) { memory = value; }
      };
    }
  }

  var store = createStore();

  function makeId() {
    return "t_" + Date.now().toString(36) + "_" + Math.random().toString(36).slice(2, 8);
  }

  function sanitizeTask(raw) {
    if (!raw || typeof raw !== "object") { return null; }
    var title = typeof raw.title === "string" ? raw.title.trim() : "";
    var status = Object.prototype.hasOwnProperty.call(ORDER, raw.status) ? raw.status : "";
    if (!title || !status) { return null; }
    return { id: makeId(), title: title.slice(0, 200), status: status };
  }

  function seedTasks() {
    return SEEDS.map(sanitizeTask);
  }

  function load() {
    var raw = store.read();
    if (typeof raw !== "string" || !raw) { return seedTasks(); }
    try {
      var parsed = JSON.parse(raw);
      if (!parsed || !Array.isArray(parsed.tasks)) { return seedTasks(); }
      var tasks = parsed.tasks.map(sanitizeTask).filter(Boolean);
      return tasks.length ? tasks : seedTasks();
    } catch (error) {
      return seedTasks();
    }
  }

  function save() {
    try {
      store.write(JSON.stringify({ tasks: tasks }));
    } catch (error) {
      store = { persistent: false, read: function () { return null; }, write: function () {} };
      showStorageNotice();
    }
    showStorageNotice();
  }

  function showStorageNotice() {
    if (store.persistent) {
      storageNotice.hidden = true;
      storageNotice.textContent = "";
    } else {
      storageNotice.hidden = false;
      storageNotice.textContent = "Storage is unavailable, so changes are kept in memory for this session only.";
    }
  }

  var tasks = load();
  var searchTerm = "";

  function announce(message) {
    live.textContent = "";
    window.setTimeout(function () { live.textContent = message; }, 30);
  }

  function matches(task) {
    if (!searchTerm) { return true; }
    return task.title.toLowerCase().indexOf(searchTerm) !== -1;
  }

  function createButton(kind, label, hiddenLabel) {
    var button = document.createElement("button");
    button.type = "button";
    button.className = kind === "move" ? "btn-move" : "btn-danger";
    button.textContent = label;
    if (hiddenLabel) {
      var sr = document.createElement("span");
      sr.className = "sr-only";
      sr.textContent = " " + hiddenLabel;
      button.appendChild(sr);
    }
    return button;
  }

  function buildColumn(column) {
    var section = document.createElement("section");
    section.className = "column";
    section.dataset.column = column.id;
    section.setAttribute("aria-label", column.label);

    var head = document.createElement("div");
    head.className = "column-head";

    var heading = document.createElement("h2");
    var dot = document.createElement("span");
    dot.className = "dot";
    dot.setAttribute("aria-hidden", "true");
    heading.appendChild(dot);
    heading.appendChild(document.createTextNode(column.label));

    var count = document.createElement("span");
    count.className = "count";

    head.appendChild(heading);
    head.appendChild(count);

    var list = document.createElement("ul");
    list.className = "tasks";

    var empty = document.createElement("p");
    empty.className = "empty";
    empty.hidden = true;

    section.appendChild(head);
    section.appendChild(list);
    section.appendChild(empty);
    board.appendChild(section);

    return { root: section, list: list, count: count, empty: empty };
  }

  function render() {
    board.textContent = "";
    var visibleTotal = 0;

    COLUMNS.forEach(function (column) {
      var parts = buildColumn(column);
      var inColumn = tasks.filter(function (task) {
        return task.status === column.id && matches(task);
      });
      visibleTotal += inColumn.length;
      inColumn.forEach(function (task) { parts.list.appendChild(renderCard(task, column)); });

      parts.count.textContent = String(inColumn.length);
      parts.count.setAttribute("aria-label", inColumn.length + (inColumn.length === 1 ? " task" : " tasks") + " in " + column.label);

      if (!inColumn.length) {
        parts.empty.hidden = false;
        parts.empty.textContent = searchTerm
          ? "No tasks match \u201C" + searchTerm + "\u201D in " + column.label + "."
          : "No tasks in " + column.label + " yet.";
      } else {
        parts.empty.hidden = true;
        parts.empty.textContent = "";
      }
    });

    return visibleTotal;
  }

  function renderCard(task, column) {
    var item = document.createElement("li");
    item.className = "card";
    item.dataset.taskId = task.id;

    var title = document.createElement("p");
    title.className = "title";
    title.textContent = task.title;
    item.appendChild(title);

    var controls = document.createElement("div");
    controls.className = "controls";

    var isLast = ORDER[task.status] === ORDER.done;
    var nextColumn = COLUMNS[ORDER[task.status] + 1];

    var move = createButton("move", "Move forward", "from " + column.label + (nextColumn ? " to " + nextColumn.label : ""));
    if (isLast) {
      move.setAttribute("aria-disabled", "true");
      move.dataset.blocked = "true";
      move.setAttribute("title", "Completed tasks stay in Done");
    } else {
      move.dataset.taskId = task.id;
      move.setAttribute("title", "Move to " + nextColumn.label);
    }
    move.addEventListener("click", function () {
      if (move.getAttribute("aria-disabled") === "true") {
        announce(task.title + " is already in Done and cannot move forward.");
        return;
      }
      moveTask(task.id);
    });
    controls.appendChild(move);

    var del = createButton("delete", "Delete", task.title);
    del.dataset.taskId = task.id;
    del.setAttribute("title", "Delete task");
    del.addEventListener("click", function () { deleteTask(task.id); });
    controls.appendChild(del);

    item.appendChild(controls);
    return item;
  }

  function findTask(id) {
    for (var i = 0; i < tasks.length; i++) {
      if (tasks[i].id === id) { return tasks[i]; }
    }
    return null;
  }

  function moveTask(id) {
    var task = findTask(id);
    if (!task) { return; }
    var next = COLUMNS[ORDER[task.status] + 1];
    if (!next) {
      announce(task.title + " is already in Done.");
      return;
    }
    var from = COLUMNS[ORDER[task.status]].label;
    task.status = next.id;
    save();
    var total = render();
    var target = board.querySelector('[data-task-id="' + id + '"]');
    if (target) {
      var focusable = target.querySelector("button:not([aria-disabled='true'])");
      if (focusable) { focusable.focus(); }
    }
    announce("Moved " + task.title + " from " + from + " to " + next.label + ". " + total + " tasks shown.");
  }

  function deleteTask(id) {
    var task = findTask(id);
    if (!task) { return; }
    tasks = tasks.filter(function (item) { return item.id !== id; });
    save();
    var total = render();
    announce("Deleted " + task.title + ". " + tasks.length + " tasks remaining.");
    if (total === 0) { searchInput.focus(); }
  }

  form.addEventListener("submit", function (event) {
    event.preventDefault();
    var title = titleInput.value.trim();
    if (!title) {
      announce("Enter a task title before adding.");
      titleInput.focus();
      return;
    }
    tasks.push({ id: makeId(), title: title, status: "todo" });
    titleInput.value = "";
    save();
    var total = render();
    var first = board.querySelector('.column[data-column="todo"] li.card:last-child');
    if (first) {
      var focusable = first.querySelector("button:not([aria-disabled='true'])");
      if (focusable) { focusable.focus(); }
    }
    announce("Added " + title + " to To do. " + total + " tasks shown.");
  });

  searchInput.addEventListener("input", function () {
    searchTerm = searchInput.value.trim().toLowerCase();
    var total = render();
    if (searchTerm) {
      announce(total === 0
        ? "No tasks match " + searchInput.value.trim() + "."
        : total + (total === 1 ? " task matches " : " tasks match ") + searchInput.value.trim() + ".");
    }
  });

  resetButton.addEventListener("click", function () {
    var ok = window.confirm("Reset the demo board? This removes your current tasks and restores the three seed cards.");
    if (!ok) { return; }
    tasks = seedTasks();
    searchTerm = "";
    searchInput.value = "";
    titleInput.value = "";
    save();
    render();
    announce("Demo reset. 3 seed tasks restored.");
  });

  render();
  showStorageNotice();
})();
</script>
</body>
</html>
The article’s isolated preview uses an opaque sandbox that blocks localStorage and modal dialogs. The task board therefore falls back to session memory inside the embed; its confirmation-based reset needs the standalone HTML. Our DOM reset checks supplied explicit true/false confirmation responses, rather than exercising an actual browser dialog.

What we checked, and what remains unverified

The check report records SHA-256 integrity, document metadata, duplicate IDs, local SVG references, external URLs and inline JavaScript syntax. The repository’s scripts/check-step5-benchmark.mjs reproduces these checks and the 16 task-board cases using esbuild and jsdom. The task-board checks were written after inspecting its output; they are diagnostic cases, not a preregistered benchmark suite. A failed artifact case is recorded as a benchmark finding; it is not hidden by editing the submission.
Browser control was unavailable for this session. jsdom simulates a DOM in Node.js; it does not render the page or implement a real browser’s layout, WebGL or audio. We have not verified globe rendering, shader compilation, camera feel, crystal reachability, turret balance, audio playback, mobile usability or the absence of browser-console errors. The embedded originals let readers inspect and try those parts. The Three.js demos require internet access to their pinned CDN import and a WebGL-capable browser.

What this session tells us

This free OpenCode setup delivered the SVG, globe and task board, but failed to deliver a playable wizard game in two attempts. The task board also shows why a plausible-looking submission still needs state-transition and persistence checks. The preserved files support inspection of those specific outcomes; they do not establish an overall ranking against other models. Visual quality and 3D gameplay remain unverified in a real browser.