枠の中でスクロール・クリックして動きを試せます。端末指定は実寸表示です。幅が足りない場合は横にスクロールできます。
再現プロンプト
回転トーラスと高コントラストのモノクロ人物写真を、どちらも輝度→燐光グリーンの文字グリッドへ変換して描く架空ASCIIレンダラPHOSPHORの1ページ。ソース切替トグルを備え、写真そのものは表示しない。 配色・構成・動きの数値をまとめた指示書です。 お使いのAIに貼り、素材やお店の情報を伝えて実装してください。仕上がりは利用するAIと素材によって変わります。
Build a single self-contained HTML file (inline CSS/JS) for "PHOSPHOR", a fictional real-time ASCII rendering engine. Its hero has two switchable inputs written into the same monospace character grid: a rotating torus sampled every frame with a classic donut.c-style parametric surface and software depth buffer, and a high-contrast monochrome head-and-shoulders portrait loaded from a local image. A title-bar toggle swaps "source: torus" / "source: portrait". The portrait must never be displayed directly: cover-fit it into an offscreen canvas at the live grid resolution, compute Rec.709 luma, apply levels/gamma/posterisation, then map only those values to characters. The pointer's X position drives torus sample density, Y drives an extra viewing tilt. Every section below the hero keeps the terminal register: man-page headers, a flag list, a shell-prompt install command. May use a CDN script for gsap. The page scrolls with the normal window (no inner scroll container needed for the standalone output).
## Art direction
- Terminal palette throughout the whole page, not just the hero: background #050705, panel #0A0D0A, phosphor green accent #4AF626 for all text, borders, and glow — no other hue anywhere. Monospace font stack (ui-monospace, "SF Mono", "Courier New", monospace) applied globally.
- A 1px outer border (rgba(74,246,38,0.22)) frames the page. A full-page CRT scanline overlay (repeating-linear-gradient, 1px lines every 3px, 5% green opacity, mix-blend-mode: overlay) drifts continuously via a background-position keyframe loop 0→120px over 6s (SCANLINE_LOOP_S) linear infinite, disabled under prefers-reduced-motion.
- A fixed top nav (56px, translucent panel background, blurred, 1px bottom hairline) shows the wordmark "PHOSPHOR" with a "ascii render engine" sub-label, links to man / flags / charset, and a "$ install" pill button. A 2px top progress bar fills accent green as the page scrolls (scrub 0.3, trigger = full content height).
- The hero wraps its canvas in a simulated terminal window: a title bar with three colored traffic-light dots (#FF5F56 red, #FFBD2E yellow, #27C93F green, purely decorative chrome), a title "phosphor — /dev/tty0", and the source toggle button on the right. Inside, the canvas fills a 16:10 aspect box with an inner box-shadow vignette and a shell prompt line ("$ ") pinned to the bottom-left with a typed command and a blinking block cursor (█, 1s step-end infinite, disabled under reduced motion).
## The ASCII renderer and its two sources (must match exactly)
Character grid: fixed CSS-pixel cell size 9×16px (CELL_WIDTH / CELL_HEIGHT), 14px font (FONT_SIZE). Columns/rows are derived every resize from the live canvas box (columns = floor(width / 9), rows = floor(height / 16)), clamped to a maximum of 120 columns × 56 rows (MAX_COLS / MAX_ROWS) for performance, with a ResizeObserver on the canvas host re-deriving them and reallocating the depth/luminance buffers.
Torus geometry (classic donut.c parametrization): tube radius 1 (TORUS_R1), donut radius 2.1 (TORUS_R2), camera distance 5 (CAMERA_DISTANCE, "K2"). Every frame, for a grid of theta/phi angle pairs, compute: circlex = R2 + R1·cos(theta), circley = R1·sin(theta); rotate by two angles A and B (A tumbles the tube at 0.6 rad/s — ROTATE_SPEED_A — B tumbles the donut at 0.3 rad/s — ROTATE_SPEED_B, both continuously accumulating, plus B gets an additional pointer-driven tilt described below); project with x = circlex·(cosB·cosphi + sinA·sinB·sinphi) − circley·cosA·sinB, y = circlex·(sinB·cosphi − sinA·cosB·sinphi) + circley·cosA·cosB, z = K2 + cosA·circlex·sinphi + circley·sinA, ooz = 1/z. Screen-project with K1 = columns · K2 · 0.375 / (R1 + R2) (PROJECTION_K1_FACTOR = 3/8, the classic donut.c constant): xp = floor(columns/2 + K1·ooz·x·CELL_ASPECT_CORRECTION), yp = floor(rows/2 − K1·ooz·y), where CELL_ASPECT_CORRECTION = CELL_HEIGHT / CELL_WIDTH (≈1.78) stretches X so a non-square character cell still reads as a round torus. Compute a luminance term L = cosphi·costheta·sinB − cosA·costheta·sinphi − sinA·sintheta + cosB·(cosA·sintheta − costheta·sinA·sinphi); discard samples with L ≤ 0 (back-facing). For visible samples, keep a per-cell Float32Array depth buffer keyed by ooz (closer wins); on a winning sample, normalize luminance = clamp01((L / 1.3)^0.85) (LUMINANCE_NORMALIZE = 1.3, LUMINANCE_GAMMA = 0.85) and store it in a parallel luminance buffer, then index straight into the character ramp " .:-=+*#%@" (CHARSET, 10 characters, sparse/dim to dense/bright): charIndex = min(charset.length − 1, floor(luminance · charset.length)).
Mouse-driven density and tilt: on every pointermove over the terminal, record the normalized 0..1 position within the canvas host. Each frame, lerp a "current" pointer position 8% of the way toward that raw target on both axes (DENSITY_LERP = 0.08 for X, MOUSE_TILT_LERP = 0.08 for Y). X selects the sample density: theta steps = round(lerp(44, 132, x)) (MIN/MAX_THETA_STEPS), phi steps = round(lerp(18, 54, x)) (MIN/MAX_PHI_STEPS) — moving the cursor right visibly thickens the mesh from a sparse wireframe-like scatter to a dense, nearly solid surface. Y adds an extra viewing angle: tilt = (y − 0.5) · 2 · 1.1 rad (MOUSE_TILT_MAX = 1.1), added on top of the continuously accumulating B rotation.
Portrait source: load "/img/demos/ascii-field/source-portrait.webp" into an HTMLImageElement. On load and every grid resize, cover-fit it into a temporary offscreen canvas sized exactly columns×rows, multiplying its sampled width by CELL_HEIGHT/CELL_WIDTH so the 9×16px output cells restore the portrait's natural proportions. For every pixel calculate Rec.709 luma = (R×0.2126 + G×0.7152 + B×0.0722)/255, map levels with clamp01((luma−0.055)/(0.9−0.055)), gamma-correct with power 0.78, then posterise upward to 9 steps. Keep zero as a blank cell so the black studio backdrop disappears. Store the result in a Float32Array and rebuild the character rows from it whenever portrait is the selected source. Never append or paint an img element into the visible terminal.
Both sources use one draw path: set canvas font to the 14px monospace stack and fillStyle to #4AF626, then for each row build one string by joining that row's character array and call fillText once per row. The source toggle relabels itself "source: torus" / "source: portrait"; torus animation continues only as the procedural input, while portrait remains a still ASCII field. A CSS drop-shadow filter (2px blur, accent color at 55% opacity) is applied to the whole canvas element for the glow, not per-glyph.
## Section: man phosphor
A "$ man phosphor" prompt line, then a man-page block: NAME ("phosphor — real-time ASCII field renderer for the terminal"), SYNOPSIS (monospace: "phosphor render [--source=torus|portrait] [--density=0.0-1.0] [--tilt=-1.0-1.0]"), DESCRIPTION (two short paragraphs explaining the procedural depth-buffer source and the photo-luminance source, while stating explicitly that neither source is ever shown as raw pixels).
## Section: phosphor --flags
A "$ phosphor --help" prompt, an uppercase "Flags" heading, and a definition list (no numbered "01/02/03" styling) of five flag rows with 1px hairline separators: --source, --density, --tilt, --charset, --grid, each with a one-line description matching the mechanics above (the --grid row states the fixed 9×16px cell size explicitly).
## Section: phosphor --charset --preview
A prompt line, an "The ramp" heading, one sentence on gamma-correction and indexing, then the ten CHARSET characters rendered large (2rem) side by side with per-character opacity increasing left to right (space rendered as a visible middle-dot for legibility), and a thin horizontal gradient bar beneath fading from near-transparent to full accent — a direct visual specimen of the brightness-to-glyph mapping used by the renderer above.
## Footer
A "$ explore phosphor" prompt, a static process line "SOURCE → DENSITY → TILT → CHARACTER RAMP", a button "Prepare a renderer brief →" opening the local planning dialog, and a bottom row with "© 2026 PHOSPHOR" and "AN INTERACTIVE RENDERER STUDY". Do not provide an installer or command for a fictional package.
## Scroll animations
Every reveal on the page — prompt lines, man-page headers/paragraphs, flag rows, charset elements, footer lines — uses one shared "print" reveal instead of a generic fade-up: set clip-path: inset(0 100% 0 0) and opacity 0.001, then on ScrollTrigger onEnter (trigger = the element, start "top 90%", toggleActions "play none none reverse") animate to clip-path: inset(0 0% 0 0) and opacity 1 over 0.6s (CONTENT_REVEAL_DURATION) with ease "power3.out", staggered 0.06s (CONTENT_REVEAL_STAGGER) per row within a group. onLeaveBack reverses over 0.3s with ease "power2.in". No element anywhere uses a plain translateY + opacity fade, and no numbered "01/02/03" row styling appears anywhere on the page.
## Idle motion, typed prompt, and interaction
On mount, the hero's shell prompt types out "phosphor render --source=torus --live" one character every 42ms (TYPE_INTERVAL_MS) via setInterval, then leaves a blinking cursor — the page is never a static frame even before the user moves the pointer. The scanline overlay's continuous drift is the other idle detail. The torus itself is the page's primary mouse-reactive element (density on X, tilt on Y), satisfying the "at least one pointer-reactive element" requirement richly rather than minimally.
## Performance & accessibility
Create the canvas element inside a mount effect (append/remove it manually) rather than a persistent framework-owned ref. Clamp devicePixelRatio to max 2 and reset the 2D context's transform on every resize (never compounding scale). Reallocate the depth buffer, luminance buffer, and character grid only on resize, not per frame. Pause the render loop via an IntersectionObserver on the page root when it leaves the viewport, and resume on re-entry. Respect prefers-reduced-motion: cut the rotation/animation time-scale to 25%, disable the scanline drift and cursor blink, and set the typed prompt to its final text instantly instead of animating character by character; every print-reveal is set to its resting state immediately. All interactive elements (nav links, the render-mode toggle, the install/contact links) are real buttons/links with visible hover states and sufficient contrast (#4AF626 on #050705). The scanline layer, terminal traffic-light dots, and charset gradient bar are aria-hidden; the man-page and flag content remain fully readable as plain static text regardless of canvas/animation support.
## Action flow and content
Implement the following local planning experience for the primary CTA, in the visual language of the page. Use a real button opening a labelled modal dialog. Choosing a pricing card must preselect that exact plan. A navigation link may move to another section; a planning CTA must lead to its review flow.
Use this content for the dialog, including the option details and notes:
{
"slug": "ascii-field",
"brand": "PHOSPHOR",
"accent": "#527340",
"title": "Configure a renderer brief",
"intro": "Select the source you want to explore and save the renderer requirements. No software is installed from this demonstration.",
"facts": [
[
"Starting point",
"Choose a focus, describe the audience and keep a scope brief."
],
[
"Review",
"Scope, supplied material, timing and the intended use."
],
[
"Handover to discuss",
"A renderer specification covering source, density, character ramp, resize and motion controls."
]
],
"groups": [
{
"label": "Project focus",
"options": [
{
"title": "Procedural form",
"detail": "Torus geometry sampled into a luminance ramp; density and tilt become controls."
},
{
"title": "Image study",
"detail": "An image sampled into character cells, with a legible resting state and original colour reference."
}
]
}
],
"brief": "Describe the audience, the intended result, the material you already have and any timing constraints.",
"notes": [
"This is a local project brief. Scope, availability and any fee would be agreed in a separate conversation."
]
}
- Explain before input that this is a fictional brand sample: choices can be reviewed and saved, with no external submission, booking, payment or registration. Use each brand's language. Do not launch a mail client or present an installer for a fictional product.
- Give every option group a legend, inputs explicit labels and errors linked descriptions. Validate options against the allowed values, requested dates as actual dates from today onward, and free text as 10–2,000 trimmed characters. Blank spaces alone must fail. Tracking accepts the sample reference TG-2048 only.
- Focus the first invalid field and preserve every entered value. On success, show the selected option WITH its full detail, requested date or brief, relevant facts and notes. An edit action returns to the preserved form. Do not claim a real reservation, purchase or account has succeeded.
- Save the complete review as UTF-8 text. A browser download request is not proof of a file being written. On download failure, retain the full text in a labelled, selectable textarea and offer retry. Do not show a false success state.
- Constrain modal content to the viewport with internal scrolling. Support keyboard and touch, trap focus while open, close on Escape and restore focus to the exact initiating control. Keep these operations available with motion disabled.
- If a separate project-contact CTA is present, let it collect a project brief and review it locally; a product selection CTA must retain the product choice. Verify both flows independently.