Interactiveqoderverified 17/17
Type Lab
A live editorial typography specimen with six type axes, a draggable baseline, and an accessible before-after comparison
The exact prompt
Build one complete, dependency-free index.html for TYPE LAB, a refined interactive typography specimen and playground. Use plain HTML, inline CSS, and inline JavaScript only. Do not import fonts, scripts, icons, images, or any other resource. Use system fonts only, make no external requests, include no external media, and keep the file under 90 KB. Set lang="en" and use the document title "Type Lab - Interactive Typography Specimen". Include exactly one h1 and semantic header, nav, main, and footer landmarks. Do not use em dash characters in visible copy and do not write console output.
VISUAL DIRECTION
Create a precise editorial composition using near-black #11120f, off-white #f1f0ea, and one chartreuse accent #c7ef45. The page has an off-white header and introduction, a large near-black specimen stage, and a compact off-white controls rail. Use crisp one-pixel borders, sharp corners, small monospaced utility labels, generous display typography, and no decorative imagery. The brand is TYPE LAB with a small T/ mark. The single h1 reads "A playground for letterforms." The introduction says "Edit the words. Push the axes. Compare the result, then take the CSS with you." Header navigation links to Playground, Presets, and Baseline. The main working area is a bordered desktop grid with the specimen on the left and a roughly 350px controls rail on the right. Below 820px, stack the rail under the specimen. Below 560px, simplify navigation, spacing, and control columns. Prevent horizontal overflow at every width and allow vertical scrolling. Every interactive control needs a visible chartreuse focus treatment and a clear hover, active, or pressed state. Honor prefers-reduced-motion by removing motion and smooth scrolling.
LIVE SPECIMEN
The near-black stage is the visual centerpiece and should be about 660px tall on desktop and 540px on small phones. Its large preview text is directly editable using a transparent, borderless textarea styled as display type. Initial text is two lines: "Form follows" and "feeling." Use the system sans stack. Support up to 240 characters, provide a useful placeholder for the empty state, preserve line breaks, update the visible character count, and never inject user text as HTML. The preview uses font-size 112px, weight 560, line-height .92, letter-spacing -.045em, width scale 94%, optical slant 0, left alignment, and normal uppercase, italic, and outline states initially. Resize the textarea to its content so multi-line specimens remain usable. Outline mode uses transparent fill with a 1.5px off-white -webkit-text-stroke and a chartreuse caret. Width scale uses scaleX with an inversely compensated width, while optical slant uses skewX. Keep the text within the clipped stage and permit anywhere wrapping for long strings.
CONTROLS
All controls update the specimen immediately and synchronize paired inputs.
1. Six type axes, each with a range and a numeric input:
- Font size: 32 to 200px, step 1, default 112.
- Weight: 100 to 900, step 10, default 560.
- Line height: .75 to 1.8, step .01, default .92.
- Letter spacing: -.08 to .3em, step .005, default -.045.
- Width scale: 60 to 160%, step 1, default 94.
- Optical slant: -12 to 12 degrees, step 1, default 0.
Clamp persisted and committed numeric values. Numeric typing should apply valid in-range values without disrupting partial entries.
2. Left, Center, and Right alignment buttons with aria-pressed.
3. Uppercase, Italic, and Outline toggle buttons with aria-pressed. Uppercase changes presentation without altering the user's source text.
4. Four curated presets, each changing text and typography and exposing its selected state:
- Editorial: the initial settings and text, described as "Soft and tailored".
- Poster: "MAKE
IT LOUD", 144px, weight 850, line-height .82, tracking -.065em, width 108%, centered, uppercase, described as "Bold and compressed".
- Wire: "OPEN
FORM", 108px, weight 300, line-height .95, tracking .005em, width 100%, left aligned, uppercase, outlined, described as "Open and outlined".
- Quiet: "Less, but
better.", 88px, weight 350, line-height 1.18, tracking .025em, width 88%, left aligned, described as "Airy and measured".
Any manual typography or text change clears the selected preset. Choosing a preset restores the after view.
5. Randomize changes the six axes, alignment, and style toggles while preserving the user's text and revealing the after view. Reset restores all initial settings, text, baseline, comparison, and Editorial preset state.
6. Copy CSS generates a complete .type-lab-specimen rule for the current settings. Use navigator.clipboard.writeText when available. On success, announce "CSS copied to the clipboard." On any failure, do not claim success: reveal a readonly textarea containing the CSS, select it, focus it, and announce "Automatic copy failed. The CSS is selected below for manual copying." Catch rejected clipboard promises. A polite live region also confirms preset, randomize, and reset actions.
BASELINE AND COMPARISON
Place a chartreuse horizontal baseline guide across the stage at 68% from the top initially. It has a visible "Baseline 68%" label and is pointer draggable within 14% to 82%. Make the guide itself a focusable role="slider" with vertical orientation, aria-valuemin, aria-valuemax, aria-valuenow, and a useful aria-valuetext. Arrow keys move it by 1%, Page Up and Page Down by 5%, Home to 14%, and End to 82%. Pointer capture must support dragging beyond the handle and clean up on pointer up and cancel.
Provide a Before / after range from 0 to 100, default 100, with visible Before and After labels and an output such as "100% after". The before layer preserves the original Editorial typography while sharing the current editable text. The current after layer remains full-size and editable underneath. Clip the pointer-transparent before layer from the right by the comparison percentage, so 0 shows before, 100 shows after, and 50 shows before on the left and after on the right. A chartreuse divider and small B / A marker appear only at intermediate values. The range has an informative aria-valuetext. Every style and text change remains synchronized in both layers.
PERSISTENCE AND TELEMETRY
Persist all text, axes, alignment, toggles, baseline, comparison, and selected preset under localStorage key "type-lab.settings.v1". Parse and validate defensively: reject malformed JSON, arrays, wrong types, unknown alignment, and out-of-range numbers without throwing or reporting an expected storage error. Keep a small save-state label that says "Saved locally" after successful writes or "Session only" if storage is blocked. Footer copy is "System fonts only. No uploads. No tracking." and "Settings stay in this browser."
At script start initialize window.__DEMO__ = { name: 'type-lab', ready: false, frames: 0, effects: ['live-specimen', 'six-axis-controls', 'baseline-guide', 'before-after', 'local-persistence'], errors: [] }. Immediately install a window error listener that pushes string messages into errors. Set ready after the first paint using nested requestAnimationFrame callbacks. In normal motion mode, run one requestAnimationFrame loop that increments frames and performs no DOM reads. Do not start the visual telemetry loop in reduced-motion mode. Normal operation must leave errors empty. Use no dependencies, timers, fetch calls, or unhandled promises.
---
TECHNICAL CONTRACT (non-negotiable):
1. Deliver ONE single file: a complete, valid `index.html`. No build step, no bundler,
no framework, no package install. It must open correctly by double-clicking the file
and also over http://.
2. Vanilla only. No React, no Tailwind CDN, no GSAP, no Three.js, no jQuery, no Lottie.
Plain HTML + CSS + modern JavaScript in <style> and <script> tags.
3. NO external media. No .mp4, no .webm, no .gif, no remote images, no base64 blobs of
photos. Every visual must be generated procedurally with CSS gradients, SVG, or
<canvas>. This keeps the page free to host and impossible to break with a dead link.
4. Google Fonts is the ONLY permitted network request, and it must be declared with a
<link rel="preconnect"> plus a system-font fallback stack so the page still looks
intentional offline. If you prefer, skip it and use a pure system stack.
5. Responsive and correct at 390x844 (phone), 768x1024 (tablet) and 1440x900 (desktop).
No horizontal scrollbar at any of those widths. Nothing may overflow its container.
6. Accessibility: exactly one <h1>; real <header>/<main>/<footer>/<nav> landmarks; every
interactive element is a <button> or <a> with an accessible name; visible :focus-visible
ring; body text contrast ratio >= 4.5:1 against its background.
7. Motion safety: wrap all continuous motion in
`@media (prefers-reduced-motion: reduce)` rules that reduce it to a static or
near-static state. The page must remain fully readable and usable with motion off.
8. Performance: no layout thrash. Cache DOM reads, batch writes inside a single
requestAnimationFrame loop, use transform/opacity for animation, and never allocate
inside the hot loop. Target 60fps on a 2019 laptop. Total file under 90 KB.
9. Runtime telemetry hook (used by the automated verifier — implement it exactly):
at the top of your script define
window.__DEMO__ = { name: '<slug>', ready: false, frames: 0, effects: [], errors: [] };
then set `ready = true` after the first paint of your main effect, increment
`frames` once per animation frame inside your rAF loop, and push the name of each
implemented effect into `effects`. Add a `window.addEventListener('error', ...)`
handler that pushes messages into `errors`. Do not rename these fields.
10. Zero console output in the happy path. No console.log left behind. No uncaught
exceptions, no failed network requests.
RESPONSE FORMAT:
Return ONLY the raw HTML inside a single ```html fenced code block. No explanation, no
preamble, no commentary before or after the fence. The file must be complete and
immediately runnable — never truncate, never write "// rest of code here".Acceptance criteria (14)
- editing the large textarea and changing any of the six range or numeric axis controls immediately updates the preview, synchronizes its paired control, and persists validated settings under type-lab.settings.v1 across reloads
- all three alignment buttons, Uppercase, Italic, Outline, four presets, Randomize, and Reset update their visible and aria-pressed states correctly, while Randomize preserves the current text and Reset restores every default
- the chartreuse baseline can be dragged and adjusted with arrow, Page Up, Page Down, Home, and End keys, and the Before / after range clips the default-style before layer from 0 through 100 percent with accessible value feedback
- Copy CSS reports success only after clipboard resolution and otherwise exposes selected CSS with an honest failure message; the page has exactly one h1, no external requests or console output, ready becomes true after first paint, frames advances in normal mode, and errors stays empty
- window.__DEMO__.ready becomes true within 5s
- window.__DEMO__.frames advances (>= 20 frames in 1.2s)
- window.__DEMO__.errors is empty
- zero console errors and zero page errors
- zero failed network requests
- exactly one <h1> with non-empty text
- header, main, footer landmarks present
- no horizontal overflow at 390 / 768 / 1440 widths
- no horizontal overflow after 2s of animation
- prefers-reduced-motion: reduce leaves the page usable and errors empty