personal-site/public/style.css
badbl0cks 0e6d352dca
Rework the CRT raster, glow and flicker effects
The three effects had drifted apart from each other and from the token
system. Consolidate them behind --crt-* tokens and fix what was broken.

- The animated text-shadow silently overrode the static one, so
  reduce-motion users saw a 5px white glow and everyone else a 3px
  currentColor glow. Both now come from one declaration, with the
  aberration offset factored out into --crt-shift.
- Step the aberration instead of tweening it. text-shadow is not
  compositable and this one inherits to every glyph, so the smooth
  version repainted the whole document at refresh rate.
- The flicker was invisible: a near-black tint at an effective alpha of
  0.008-0.096 over a black background, a peak lift of under 2/255.
  Retint with the accent at ~3%.
- Anchor the vignette to the viewport. It was sized to body's box, which
  is document-height, so on long pages the bloom sat off-screen.
- Pin the overlays with position: fixed rather than painting
  document-sized layers, and give the grille hard stops so it is three
  stripes rather than a smooth R->G->B ramp.
- Scale the raster pitch down at >=2dppx, where a 2px scanline is 4
  device px and resamples into moire at fractional DPR.
- Raise --color-mg to oklch(0.62): at 0.5 it was 3.5:1 on the
  background and 1.7:1 over the vignette centre. Halve the vignette
  alpha via its own token, keeping --color-accent-transparent at 50%
  for ProjectCard's glow.

Collapses the two keyframe blocks from 193 lines to 40.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:39:05 -07:00

171 lines
5.5 KiB
CSS

@layer reset, config, font, utilities, theme;
@import "reset.css" layer(reset);
@import "theme.css" layer(theme);
/* The chromatic-aberration offset, as a 0..1 multiplier of
--crt-aberration-max. Registering it gives it a typed initial value, so the
calc() in body's text-shadow stays valid when the animation is not running.
Note the var(--crt-shift, 0) fallbacks at the use sites: without one, an
unresolved --crt-shift makes the calc() invalid at computed-value time,
which drops the *whole* text-shadow — glow included — not just the smear.
The animation itself does not depend on this rule, because step-end never
interpolates; it only ever substitutes a literal keyframe value. */
@property --crt-shift {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@font-face {
font-family: "unscii16";
src:
url("unscii-16.woff") format("woff"),
url("unscii-16.ttf") format("ttf");
}
@font-face {
font-family: "unscii8";
src:
url("unscii-8.woff") format("woff"),
url("unscii-8.ttf") format("ttf");
}
@layer font {
:root {
/* Traditional point sizes for 16px font */
--pt-double-canon: 3.75rem; /*60px*/
--pt-canon: 3.25rem; /*52px*/
--pt-double-great-primer: 2.5rem; /*40px*/
--pt-double-english: 2rem; /*32px*/
--pt-double-pica: 1.75rem; /*28px*/
--pt-paragon: 1.5rem; /*24px*/
--pt-great-primer: 1.375rem; /*22px*/
--pt-english: 1.125rem; /*18px*/
--pt-pica: 1rem; /*16px*/
--pt-small-pica: 0.875rem; /*14px*/
--pt-long-primer: 0.75rem; /*12px*/
--pt-bourgeois: 0.625rem; /*10px*/
--pt-minion: 0.5rem; /*8px*/
}
}
@layer utilities {
.hidden {
display: none;
visibility: hidden;
}
}
@layer config {
:root {
/* Color */
--color-fg: oklch(1 0 0);
/* 0.62, not 0.5: at 0.5 this was 3.5:1 on the background, under AA, and
1.7:1 against the centre of the CRT vignette. 0.62 gives 5.8:1 / 4.4:1. */
--color-mg: oklch(0.62 0 0);
--color-bg: oklch(0 0 0);
--color-accent: oklch(0.6 0.18 142.5);
--color-accent-bright: oklch(0.8 0.18 142.5);
--color-glow: oklch(0.8328 0 0);
--color-accent-transparent: oklch(0.6 0.18 142.5 / 50%);
/* CRT
The raster (scanline + aperture grille) and the phosphor (vignette,
glow, chromatic aberration). Every value the effect uses lives here so
the whole look can be retuned without touching the keyframes. */
--crt-scanline-pitch: 2px;
--crt-grille-pitch: 3px;
--crt-scanline-color: rgb(0 0 0 / 0.25);
--crt-grille-r: rgb(255 0 0 / 0.06);
--crt-grille-g: rgb(0 255 0 / 0.02);
--crt-grille-b: rgb(0 0 255 / 0.06);
/* Deliberately not --color-accent-transparent, which ProjectCard uses for
its box-shadow glow and wants at 50%. As a full-screen wash behind text,
50% costs too much contrast; 25% keeps --color-mg above 4.5:1. */
--crt-vignette-core: oklch(0.6 0.18 142.5 / 25%);
--crt-vignette-edge: var(--color-bg);
--crt-shadow-cool: rgb(0 30 255 / 0.5);
--crt-shadow-warm: rgb(255 0 80 / 0.3);
--crt-glow-blur: 3px;
/* In em so the smear scales with the fluid root font-size. */
--crt-aberration-max: 0.15em;
--crt-flicker-base: 0.03;
/* Spacing */
--gutter-large: clamp(3ch, 2.5vmax, 3.75ch);
--stack-large: clamp(1.25ex, 2.5vmax, 1.75ex);
--gutter-small: clamp(0.5ch, 1vmax, 1ch);
--stack-small: clamp(0.25ex, 0.5vmax, 0.5ex);
--line-length: 75ch;
--page-max-inline-size: min(
var(--line-length),
100dvi - var(--gutter-large) * 2
);
--page-padding-inline: calc((100dvi - var(--page-max-inline-size)) / 2);
/* Typography */
--font: unscii16, "Courier New", monospace;
--font-alternative: unscii8, "Courier New", monospace;
--font-weight: 400;
--font-weight-semibold: 600;
--font-weight-bold: 900;
--font-size-min: 100%;
--font-size-max: 150%;
--font-size-clamp: clamp(
var(--font-size-min),
var(--font-size-min) * 0.9 + 0.5dvi,
var(--font-size-max)
);
--leading: 1.4;
/* Traditional point sizes for 12px font */
--pt-double-canon: 4.666rem; /*56px*/
--pt-canon: 3.999rem; /*48px*/
--pt-double-great-primer: 2.999rem; /*36px*/
--pt-double-english: 2.333rem; /*28px*/
--pt-double-pica: 2rem; /*24px*/
--pt-paragon: 1.666rem; /*20px*/
--pt-great-primer: 1.5rem; /*18px*/
--pt-english: 1.166rem; /*14px*/
--pt-pica: 1rem; /*12px*/
--pt-small-pica: 0.916rem; /*11px*/
--pt-long-primer: 0.833rem; /*10px*/
--pt-bourgeois: 0.75rem; /*9px*/
--pt-minion: 0.583rem; /*7px*/
font-size: var(--font-size-clamp);
font: -apple-system-body;
font-family: var(--font);
font-weight: var(--font-weight);
line-height: var(--leading);
color: var(--color-fg);
background-color: var(--color-bg);
accent-color: var(--color-accent);
color-scheme: dark light;
}
@supports (font: -apple-system-body) and
(not (-webkit-touch-callout: default)) {
:root {
font-size: var(--font-size-clamp);
}
}
/* The raster is specified in CSS px, so on a 2dppx panel a 2px scanline is
4 device px — coarse, and on fractional DPR it resamples into moire.
Tightening the pitch keeps the apparent scale roughly constant. */
@media (min-resolution: 2dppx) {
:root {
--crt-scanline-pitch: 1.5px;
--crt-grille-pitch: 2.25px;
}
}
@media (prefers-color-scheme: light) {
}
@media (prefers-contrast: more) {
}
@media (prefers-reduced-motion: reduce) {
}
}