# 事象の地平線 — Style Reference
> a black hole sits right of centre on a true black page; the accretion disk, the stars and even the hero title bend around it, and the closer you scroll the slower everything moves.

**Theme:** dark  **Series:** EDGE 005  **World:** 架空のプラネタリウム「北天プラネタリウム」の特別番組ページ。ブラックホールに近づく上映。

## Concept
A planetarium's programme page for one show. The whole page is a single ray-marched view of a Schwarzschild black hole with a thin accretion disk; the DOM is only a left column of text and two pill controls. Scrolling is the approach: the camera moves from 26 to just under 10 Schwarzschild radii, the disk grows until it fills the frame, and a readout in the corner shows the time rate falling from x1.00 toward x0.05 while the disk's rotation visibly slows. The hero title is not HTML. It is drawn on a plane 40 units behind the hole so the end nearest the hole is stretched by the lens, and it fades once the reader leaves the first screen. Each section names one thing the picture is already showing (disk, lensing, time dilation); the copy never explains more than one sentence's worth.

## Signature Technique
Raw WebGL2, one fullscreen triangle, one fragment shader, no libraries.

- **Ray marching in a weak-field metric.** For each pixel, when the camera is outside r = 15, the ray first skips the far field: it jumps to the sphere r = 15 and takes the incoming half of the weak-field deflection analytically (for this force the full deflection at impact parameter b is 1.733 / b; the partial value comes from the closed-form integral of b^3 / (b^2 + s^2)^(5/2)). Rays that miss the sphere get the full 1.733 / b and go straight to the sky. When the camera is already inside the sphere (the close approach, distance 9.8) the march starts at the camera. Inside the sphere it is integrated for up to 150 steps (110 on mobile) with `dt = clamp(r * 0.055, 0.02, 0.45)`, and a ray leaving r = 15.5 takes the outgoing half analytically. Without the skip, a ray from r = 26 used up its steps before reaching the hole, which cut the lensed far side of the disk off flat and hid the shadow on mobile. The bend is the null-geodesic approximation `dir += -1.3 * h2 * pos / r^5 * dt` (the exact coefficient is 1.5; it is eased to 1.3 because the coarse integrator over-bends near the shadow) where `h2 = |pos x dir|^2` is the squared angular momentum and the Schwarzschild radius is 1. Rays with r < 1 are captured and stay black; rays that leave the sphere sample the sky; a ray still circling when the steps run out is counted as captured, so the shadow edge never smears into sky. The sky behind a ray is dimmed by its total bend (`mix(1, 0.06, smoothstep(0.5, 1.6, bend))`, lensing demagnifies), which keeps the gap between the shadow and the lensed disk clean black, and rays bent by 2.2 to 3.0 rad add a warm white `(1, 0.86, 0.66) x 1.6`: the photon ring, a thin full circle around the shadow.
- **Accretion disk.** Detected as a crossing of the y = 0 plane between steps, with the hit interpolated. Radial band 1.9 to 8.5 RS (inner edge 2.1 RS, so the lensed arc sits close to the photon ring); the inner edge is a 0.035 RS smoothstep with a thin white-hot lip `exp(-((r - inner - 0.07) / 0.06)^2)` at 5.5x intensity, and a separate `rim` term pushes the innermost 0.9 RS to white with 3.5x intensity, so the ISCO reads as a hot wire. Colour runs from dark ember (0.55, 0.12, 0.02) through orange (1, 0.45, 0.12) to near white by radius. Texture is two octaves of fbm on polar coordinates, sheared by `-uT * 4 r^-1.5` so inner rings turn faster (Keplerian). Doppler beaming: `dop = clamp(1 + 0.9 * dot(tangent, -dir), 0.25, 2.2)` raised to 1.6 on intensity, and the approaching side is pulled toward white. The disk is nearly opaque (alpha 0.82 + 0.5 band, transmission `1 - 0.97 alpha`), so the near side crosses in front of the shadow as one band and the lensed underside shows as a thin arc below it.
- **Sky.** Two layers of hashed star cells on (azimuth, elevation) with twinkle, plus a faint blue fbm nebula. Because the sky is sampled with the bent direction the stars form the Einstein ring and photon ring around the shadow for free.
- **Lensed title (desktop).** A 4096 x 1024 canvas with 事象の地平線 in Shippori Mincho 600 px is uploaded as a mipmapped texture (`LINEAR_MIPMAP_LINEAR`). Rays are intersected with the plane z = -40 outside any branch, so the mip level comes from smooth screen derivatives and the stretched glyph edges stay clean; the sample is only added for un-captured rays inside the rect `uTextRect`. The rect is computed in JS from the camera: the wanted screen endpoints (7 and 37 percent of width, 80 percent of height) are first pulled inward by the weak-field push `2 / (b * fov)` with impact parameter b = r * fov * dist, then projected onto the plane. The result is a title whose left end reads straight and whose right end bends down into the photon ring, with a faint mirrored copy on the far side of the hole. It fades once s passes 0.3. On portrait the lens is too strong everywhere above the hole, so the h1 is shown as DOM mincho instead.
- **Time dilation.** Only `uT` (the disk's phase) advances at `rate`; stars twinkle on wall-clock time. `rate = lerp(1, 0.05, smoothstep(0.6, 3.0, s))`, `dist = lerp(26, 9.8, smoothstep(0, 3, s))`, elevation 0.26 to 0.12 rad. All three lerp with `1 - exp(-4 dt)`. The readout prints `x` + rate and the distance in RS.
- **Tone map.** `1 - exp(-col * 0.85)` then gamma 2.2 and a mild vignette. The disk core clips to white on purpose; the rest of the page is black, so nothing competes.
- **Mobile.** Full-resolution canvas at 1 CSS px per texel (desktop renders at 0.9 scale and lets the browser upscale), 110 steps, no pointer parallax, the hole raised to 60 percent height so copy sits below it, approach limited to 12.5 RS so the shadow does not fill the portrait frame.
- **Reduced motion.** Disk phase frozen, twinkle frozen, lerps snap. The readout still shows x1.00 and the scroll-driven approach still works.

## Color Palette
| Token | HEX | Role |
|---|---|---|
| --bg | #000000 | page, the only surface |
| --ink | #f2f2f0 | headings, primary button border |
| --mute | #8d8f93 | body copy, eyebrows, pill borders text |
| --hair | rgba(255,255,255,.16) | pill outlines, footer rule |
| --ember | #ff8a3d | focus ring, button hover fill |
| --ember-2 | #ffd2a8 | readout numerals |
| disk ember / orange / white | #8c1f05 / #ff7320 / #fff8f2 | shader colour ramp by radius |
| photon ring | #ffdba8 | thin warm ring at the shadow edge (rays bent 2.2 to 3.0 rad) |
| nebula | #0d0e14 | faint sky haze |

The only colour on the page comes from the disk. The UI borrows a pale version of it for two numbers and nothing else.

## Typography
- **Noto Sans JP** 300 / 400. h2 clamp(28px, 3.1vw, 44px) weight 300, line-height 1.3, letter-spacing 0.02em. Body clamp(15px, 1.1vw, 17px) weight 300, line-height 1.9, max 38ch.
- **Shippori Mincho** 400 for the hero title only, rendered inside the shader at 600 px on a 1024 px tall mipmapped texture (about 80 px on a 900 px viewport). The mincho serif against a sans body marks the title as part of the picture, not the UI.
- **DM Mono** 400 for eyebrows (11px, 0.18em, uppercase), pills, readout, footer.
- Light weights throughout follow the two references: the headline whispers and the disk shouts.

## Spacing & Layout
- Gutter `clamp(20px, 7vw, 112px)`. Copy column max 480px, always left; the hole is centred at 62 percent of width so the two never overlap.
- Sections `min-height: 100svh`, flex, vertically centred; the hero aligns its copy to the bottom with 16vh padding so the lensed title owns the upper half.
- Fixed header (name + one pill), fixed readout bottom right (bottom left on mobile), footer with hairline.
- Breakpoint 760px: copy at the bottom over a black gradient, hole at 60 percent height, scroll hint hidden.

## Motion
- Nothing autoplays except the disk rotation and star twinkle. Scroll controls distance, elevation and time rate; pointer moves the camera by 0.06 / 0.08 rad on desktop.
- Section plateau: the lerp constant 4 per second makes a section change settle in about a second.
- Title fades out (alpha lerp) once s passes 0.55.
- Button hover: fill and border turn ember, text black, 250 ms.
- `prefers-reduced-motion`: disk and twinkle frozen, lerps instant, scroll hint static.

## Components
- **hero**: eyebrow, h1 (visually hidden on desktop, where the shader draws it lensed; visible mincho at clamp(40px, 11vw, 52px) on portrait), one sentence, scroll hint bottom right.
- **section**: eyebrow, h2, one or two sentences, one or two pill tags in DM Mono. On wide screens every section after the hero puts a soft black radial pocket behind the copy (`radial-gradient(closest-side, rgba(0,0,0,.82), rgba(0,0,0,.6) 55%, transparent)`, extended 90 px around the block), so the copy stays readable once the disk fills the frame. On mobile the full-width bottom gradient does that job.
- **pill**: 1px hairline border, 999px radius, 6/14px padding, 11px mono uppercase 0.14em.
- **button**: 1px `--ink` border, 999px radius, 14/26px padding, 14px weight 400 0.1em, trailing 22px rule.
- **readout**: `TIME RATE x0.00  DISTANCE 00.0`, DM Mono 11px, numerals in `--ember-2`, text shadow so it stays legible over the disk.
- **footer**: hairline, `VALE / EDGE 005`, venue name.
- **fallback**: without WebGL2 the h1 becomes visible as a large mincho heading and a bordered note appears.

## Do / Don't
Do
- Keep the page black; let the disk be the only light source.
- Keep one idea per section and name what the picture already shows.
- Let the lens act on real content (title, stars), not on decorative shapes.
- Make the time rate visible in two places at once: the readout and the disk's motion.

Don't
- No purple or blue glow around the hole; the ring is white-hot orange or nothing.
- No particles, grids or floating geometry in the sky.
- No second image; the shader is the picture.
- No copy about awe or the universe; facts about light and time only.

## References
- **Default** (stock/refero/md/default.md): mission-control calm on near black, weight 400 headlines at display size, hairline borders and pill controls, one rationed accent. Abstracted: pill outlines, light headline weight, the readout as instrument text. Changed: the accent is the disk's ember instead of blue, and the surface is pure black rather than layered graphite panels.
- **Bareis + Nicolaus** (stock/refero/md/bareis-nicolaus.md): white chalk on black slate, one typeface doing everything, pill outlines as the only punctuation, no gradients and no elevation. Abstracted: the refusal of decoration and the pill as the single UI shape. Changed: a serif title and a sans body instead of one face, and the "image index" on the right is a live simulation rather than photographs.
