/* ==========================================================================
   Seeded appearances shared by the homepage and coffee catalogue
   Content remains semantic in each page; structure comes from styles.css.
   appearance.js chooses only the bounded tokens defined by the design system.
   ========================================================================== */

html[data-look] {
  /* One responsive semantic scale is shared by every appearance. Individual
     typefaces still bring their own voice, while font-size-adjust normalizes
     their lowercase x-height so a nominal size reads consistently. */
  --fs-display: 2.75rem;
  --fs-body: 1.0625rem;
  --fs-desc: 0.96875rem;
  --fs-heading: 0.8125rem;
  --fs-meta: 0.78125rem;
  --fs-tag: 0.75rem;
  --fs-footer: 0.71875rem;
  --appearance-body-family: var(--serif);
  --appearance-display-family: var(--mono);
  --appearance-annotation-family: var(--mono);
  --appearance-display-size: var(--fs-display);
  --appearance-display-weight: 600;
  --appearance-body-size: var(--fs-body);
  --appearance-body-weight: 400;
  --appearance-line-height: 1.55;
  --image-contrast: 1.08;
  --image-brightness: 1;
  --image-opacity: 0.96;
  --image-blend-mode: screen;
  --grain-url: none;
  --grain-size: 320px;
  --grain-opacity: 0;
  --grain-blend: normal;
  --grain-contrast: 1;
  --grain-brightness: 1;
  --grain-rate: 900ms;
  --paper-texture-url: none;
  --paper-texture-size: 840px;
  --paper-texture-opacity: 0;
  --paper-texture-contrast: 1;
  --paper-texture-brightness: 1;
  --paper-texture-blend: multiply;
  --terminal-font-family: "Fira Mono", monospace;
  --terminal-display-weight: 500;
  --terminal-name-spacing: -0.018em;
  --terminal-heading-spacing: 0.035em;
  --edition-divider-style: solid;
  --edition-leader-style: dotted;
  --edition-divider-opacity: 100%;
  --edition-leader-opacity: 100%;
  --edition-utility-border-style: solid;
  --edition-utility-radius: var(--radius);
  --edition-utility-color: var(--border);
  --blob-link-bloom-near: 68%;
  --blob-link-bloom-far: 32%;
  --blob-link-bloom-radius: 9px;
  --appearance-document-height: 100vh;
  --eno-gradient-a: none;
  --eno-gradient-b: none;
  --eno-gradient-c: none;
  --eno-cycle: 300s;
  --eno-phase: 0s;
  --eno-hue-cycle: 420s;
  --eno-hue-phase: 0s;
  --eno-softness: 28px;
  --eno-saturation: 1;
  --eno-diffusion: 0.12;
  --film-name-spacing: -0.02em;
  --film-halation: #ff493d;
  --film-bloom-radius: 5px;
  --film-halation-radius: 11px;
  --film-halation-strength: 60%;
  --film-vignette: 0.24;
  --film-base: #100f12;
  --film-stock: #000000;
  --film-perf-stock: #000000;
  --film-perf-edge: #e6c99b;
  --film-gate-margin: 12px;
  --film-gate-line: calc(var(--film-gate-margin) + 1px);
  --film-gate-fall: 130px;
  --film-gate-fall-x: 60px;
  --film-header-rebate: 13px;
  --film-perf-pitch: 44px;
  --film-perf-hole-width: 26px;
  --film-perf-hole-height: 18px;
  --film-perf-radius: 5px;
  --film-perf-height: 54px;
  --film-perf-hole-url: none;
  --film-perf-halo-blur: 7px;
  --film-perf-halo-opacity: 0.18;
  --film-perf-bleed-opacity: 0.07;
  --film-perf-bleed-blur: 14px;
  --film-edge-header: "";
  --film-edge-footer: "";
  --film-edge-print-opacity: 0.6;
  --film-dust-url: none;
  --film-dust-size: 300px;
  --film-dust-opacity: 0.45;
  --crt-cell: 1px;
  --crt-depth: 76;
  --crt-grille: 0.38;
  --crt-halo-red: 32%;
  --crt-halo-blue: 28%;
  --crt-halo-strength: 42%;
  --crt-halo-radius: 2;
  --crt-image-halo: 28%;
  --crt-red-x: -1;
  --crt-red-y: 0;
  --crt-blue-x: 1;
  --crt-blue-y: 0;
  --crt-triad-phase: 0;
  --crt-selection: #6e2074;
  --crt-vignette: 0.62;
  --crt-roll-period: 7s;
  --crt-fringe-red: 255 75 70;
  --crt-fringe-blue: 72 124 255;
  --crt-grille-red: #ff4d4d;
  --crt-grille-green: #4dff4d;
  --crt-grille-blue: #4d4dff;
  --crt-cursor-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' shape-rendering='crispEdges'%3E%3Cpath fill='%23000' d='M1 0h3v2h2v2h2v2h2v2H6v2H4v2H2V8H1z'/%3E%3Cpath fill='%23eaf7ff' d='M2 1h1v2h2v2h2v2h2v1H5v2H3V7H2z'/%3E%3C/svg%3E");
  --crt-cursor-hotspot: 1 1;
  --crt-roll-height: 26vh;
  --crt-glare-angle: 118deg;
  --crt-glare-center: 49%;
  --crt-tube-x: 125%;
  --crt-tube-y: 105%;
  min-height: 100%;
  /* Keep the browser-owned glass and any area beyond the document dark. The
     artwork itself is supplied by the real document layers below. */
  background: var(--browser-frame);
}

html[data-look] body {
  position: relative;
  min-height: 100vh;
  isolation: isolate;
  overflow-x: hidden;
  background: transparent;
  font-family: var(--appearance-body-family);
  font-size: var(--appearance-body-size);
  font-size-adjust: 0.5;
  font-weight: var(--appearance-body-weight);
  line-height: var(--appearance-line-height);
}

/* A fixed palette-colored canvas remains behind the document during elastic
   overscroll, so the browser never exposes an unrelated white surface. */
.appearance-base-layer {
  position: fixed;
  z-index: 0;
  inset: -100vh -100vw;
  pointer-events: none;
  background: var(--bg);
}

/* The composer builds a real feTurbulence tile per edition. Size, frequency,
   octave quality, contrast, brightness, strength, and shift rate are seeded. */
html[data-look] body::before {
  content: "";
  position: fixed;
  z-index: 4;
  inset: -20%;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
  background-image: var(--grain-url);
  background-size: var(--grain-size) var(--grain-size);
  filter: contrast(var(--grain-contrast)) brightness(var(--grain-brightness));
  animation: appearance-grain-shift var(--grain-rate) steps(1) infinite;
  will-change: transform;
}

@keyframes appearance-grain-shift {
  0% { transform: translate3d(0, 0, 0); }
  25% { transform: translate3d(-3%, 2%, 0); }
  50% { transform: translate3d(2%, -3%, 0); }
  75% { transform: translate3d(-2%, -2%, 0); }
  100% { transform: translate3d(3%, 1%, 0); }
}

/* Shifting the tile itself keeps the 70mm grain alive without translating a
   screen-blended layer. Safari can intermittently clip that transformed
   layer to its backing-store bounds, exposing dead-black bands at the frame
   edge on every animation step. */
@keyframes appearance-film-grain-shift {
  0% { background-position: 0 0; }
  25% { background-position: -17px 11px; }
  50% { background-position: 13px -19px; }
  75% { background-position: -11px -13px; }
  100% { background-position: 19px 7px; }
}

html[data-look="simple"] body::before { content: none; }

/* Paper fibre belongs to the physical document rather than the viewport. This
   real document layer is sized from the rendered page, so it travels at the
   exact speed of the content on short desktop and long mobile layouts alike. */
.paper-texture-layer {
  display: none;
  position: absolute;
  z-index: 5;
  inset: 0 0 auto;
  height: var(--appearance-document-height);
  pointer-events: none;
  opacity: var(--paper-texture-opacity);
  mix-blend-mode: var(--paper-texture-blend);
  background-image: var(--paper-texture-url);
  background-position: top left;
  background-size: var(--paper-texture-size) var(--paper-texture-size);
  filter: contrast(var(--paper-texture-contrast)) brightness(var(--paper-texture-brightness));
}

html[data-look="paper"] .paper-texture-layer { display: block; }

html[data-look] .page {
  position: relative;
  z-index: auto;
  padding-top: max(12px, calc(env(safe-area-inset-top) + 12px));
  padding-right: max(48px, calc(env(safe-area-inset-right) + 48px));
  padding-bottom: max(12px, calc(env(safe-area-inset-bottom) + 12px));
  padding-left: max(48px, calc(env(safe-area-inset-left) + 48px));
}

/* Homepage structure is responsive, never seeded: one column until the
   viewport can hold two readable measures, then exactly two. Paper switches
   later because its larger editorial type needs wider columns. Seeded page
   widths and gaps may vary, but cannot change the column count. */
html[data-look] .page--home .columns {
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 840px) {
  html[data-look]:not([data-look="paper"]) .page--home .columns {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1120px) {
  html[data-look="paper"] .page--home .columns {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

html[data-look] .name {
  font-family: var(--appearance-display-family);
  font-size: var(--appearance-display-size);
  font-weight: var(--appearance-display-weight);
  max-width: 100%;
  white-space: normal;
  overflow-wrap: anywhere;
}

html[data-look] .intro-text {
  /* Keep the portrait beside a useful text measure, but let the identity copy
     take a fresh line before flexbox can squeeze it into a sliver. */
  min-width: min(240px, 100%);
  container-type: inline-size;
}

/* The identity name is the one exception to the shared semantic type scale:
   it keeps that scale as its ceiling, then fits its real text container when
   the portrait and a two-column tablet layout leave less room. Without cqi,
   the safe wrapping rule above remains the fallback. */
html[data-look] .page--home .intro-text .name {
  font-size: min(12cqi, var(--appearance-display-size));
}

@supports (font-size: 1cqi) {
  html[data-look] .page--home .intro-text .name {
    white-space: nowrap;
    overflow-wrap: normal;
  }
}

/* Intrinsic widths from a heading or row must never enlarge a generated grid
   item past the minmax(0, 1fr) track that owns it. */
html[data-look] .page--home .columns > *,
html[data-look] .page--home .stack,
html[data-look] .page--home section {
  min-width: 0;
}

/* Long publication rows can wrap inside a narrow generated column. The
   leader and tag then move together to the available line instead of clipping. */
html[data-look] .row {
  flex-wrap: wrap;
  row-gap: 2px;
}

html[data-look] .row > a:first-child {
  min-width: 0;
  max-width: 100%;
  white-space: normal;
  overflow-wrap: break-word;
}

html[data-look] .leader {
  min-width: 24px;
}

html[data-look] :is(.heading, .recipe-subheading, .meta, .tag, .ago, .dates, .measure, .recipe-time, .recipe-note-label, .footer, .appearance-control),
html[data-look] :is(a[target="_blank"]:not(.flickr-photo-link), .internal-link)::after {
  font-family: var(--appearance-annotation-family);
}

html[data-look] .recipe-method li::marker {
  font-family: var(--appearance-annotation-family);
}

/* Generated-page text links answer through each edition's palette. Keep the response
   typographic: the page already contains enough leader lines and rules. */
html[data-look] a:not(.flickr-photo-link) {
  transition: color 0.15s ease, box-shadow 0.15s ease, text-shadow 0.18s ease;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

html[data-look]:not([data-look="crt"]) a:not(.flickr-photo-link):is(:hover, :focus-visible) {
  color: var(--ink-hover);
}

/* --------------------------------------------------------------------------
   appearance-aware photographs — one monochrome source, seven light systems
   -------------------------------------------------------------------------- */

.appearance-image {
  position: relative;
  display: block;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: transparent;
  isolation: auto;
  line-height: 0;
  transition: border-color 0.15s ease, box-shadow 0.18s ease;
}

html[data-look] a.appearance-image:is(:hover, :focus-visible) {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 72%, transparent);
}

.appearance-image img {
  position: relative;
  z-index: 1;
  border: 0;
  border-radius: 0;
  background: transparent;
  filter:
    grayscale(1)
    contrast(var(--image-contrast))
    brightness(var(--image-brightness));
  mix-blend-mode: var(--image-blend-mode);
  opacity: var(--image-opacity);
}

.appearance-image--portrait {
  flex: none;
  width: 148px;
  aspect-ratio: 1;
}

.appearance-image--portrait .photo {
  position: absolute;
  z-index: 1;
  top: -10%;
  left: -10%;
  width: 120%;
  max-width: none;
  height: 120%;
  object-position: 46% 60%;
}

.flickr-photo-link.appearance-image { margin-bottom: 8px; }

html[data-look="simple"] .appearance-image img {
  filter: grayscale(1);
  mix-blend-mode: normal;
  opacity: 1;
}

html[data-look="paper"] {
  --image-blend-mode: multiply;
}

html[data-look="blobs"] {
  --image-blend-mode: luminosity;
}

html[data-look="eno"] {
  --image-blend-mode: luminosity;
}

html[data-look="crt"] {
  --image-blend-mode: normal;
}

html[data-look="terminal"] {
  --image-blend-mode: normal;
}

html[data-look="70mm"] {
  --image-blend-mode: luminosity;
}

/* --------------------------------------------------------------------------
   appearance control — shared manual choice for the current browsing session
   -------------------------------------------------------------------------- */

.appearance-control {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px 14px;
  margin: 0 0 32px;
  padding-bottom: 9px;
  border-bottom: 1px solid var(--rule);
  color: var(--footnote);
  font-size: var(--fs-footer);
  letter-spacing: 0.05em;
  text-transform: lowercase;
}

.appearance-options {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: 3px;
}

.appearance-option {
  flex-shrink: 0;
  white-space: nowrap;
  min-height: 28px;
  margin: 0;
  padding: 3px 7px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--footnote);
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}

.appearance-reload {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  gap: 4px;
  padding-inline: 7px;
  border-color: var(--border);
  color: var(--muted);
}

.appearance-reload-icon {
  font-family: var(--appearance-annotation-family);
  font-size: 1em;
  line-height: 1;
}

/* The top and footer utilities are one matched control size in every look. */
.copy-markdown {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 28px;
  padding: 3px 7px;
  box-sizing: border-box;
  white-space: nowrap;
}

.appearance-option:hover,
.appearance-option:focus-visible {
  color: var(--ink-hover);
  border-color: var(--border);
}

.appearance-option[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
}

.appearance-status {
  min-width: 9.5em;
  color: var(--faint);
  text-align: right;
}

/* --------------------------------------------------------------------------
   seeded rule grammar — one restrained editorial treatment per edition
   -------------------------------------------------------------------------- */

html[data-look][data-rule-style="solid"] {
  --edition-divider-style: solid;
  --edition-leader-style: solid;
}

html[data-look][data-rule-style="dotted"] {
  --edition-divider-style: dotted;
  --edition-leader-style: dotted;
}

html[data-look][data-rule-style="dashed"] {
  --edition-divider-style: dashed;
  --edition-leader-style: dashed;
}

/* 70mm keeps its own physical film boundaries; the generic dividers are
   suppressed later in the film rules. */
html[data-look][data-rule-style="film"] {
  --edition-divider-style: solid;
  --edition-leader-style: solid;
}

html[data-look] .leader {
  border-bottom-style: var(--edition-leader-style);
  border-bottom-color: color-mix(in srgb, var(--hairline) var(--edition-leader-opacity), transparent);
}

html[data-look] .appearance-control {
  border-bottom-style: var(--edition-divider-style);
  border-bottom-color: color-mix(in srgb, var(--rule) var(--edition-divider-opacity), transparent);
}

html[data-look] .footer {
  border-top-style: var(--edition-divider-style);
  border-top-color: color-mix(in srgb, var(--rule) var(--edition-divider-opacity), transparent);
}

html[data-look][data-utility-treatment="outline"] {
  --edition-utility-border-style: solid;
  --edition-utility-radius: var(--radius);
  --edition-utility-color: var(--border);
}

html[data-look][data-utility-treatment="hairline"] {
  --edition-utility-border-style: solid;
  --edition-utility-radius: 0;
  --edition-utility-color: color-mix(in srgb, var(--rule) 82%, var(--border));
}

html[data-look][data-utility-treatment="dashed"] {
  --edition-utility-border-style: dashed;
  --edition-utility-radius: 0;
  --edition-utility-color: var(--border);
}

html[data-look][data-utility-treatment="quiet"] {
  --edition-utility-border-style: solid;
  --edition-utility-radius: var(--radius);
  --edition-utility-color: color-mix(in srgb, var(--border) 72%, transparent);
}

html[data-look][data-utility-treatment="film"] {
  --edition-utility-border-style: solid;
  --edition-utility-radius: 0;
  --edition-utility-color: var(--border);
}

html[data-look][data-utility-treatment="signal"] {
  --edition-utility-border-style: solid;
  --edition-utility-radius: 0;
  --edition-utility-color: var(--border);
}

html[data-look][data-utility-treatment="terminal-solid"] {
  --edition-utility-border-style: solid;
  --edition-utility-radius: 0;
  --edition-utility-color: var(--border);
}

html[data-look][data-utility-treatment="terminal-dotted"] {
  --edition-utility-border-style: dotted;
  --edition-utility-radius: 0;
  --edition-utility-color: var(--border);
}

html[data-look][data-utility-treatment="terminal-dashed"] {
  --edition-utility-border-style: dashed;
  --edition-utility-radius: 0;
  --edition-utility-color: var(--border);
}

html[data-look] :is(.appearance-reload, .appearance-toggle, .copy-markdown, .copy-recipe) {
  border-style: var(--edition-utility-border-style);
  border-radius: var(--edition-utility-radius);
  border-color: var(--edition-utility-color);
}

/* --------------------------------------------------------------------------
   simple — the original dual serif/mono voice with five tonal palettes
   -------------------------------------------------------------------------- */

html[data-look="simple"] body {
  --appearance-body-family: var(--serif);
  --appearance-display-family: var(--mono);
  --appearance-annotation-family: var(--mono);
}

/* --------------------------------------------------------------------------
   paper — all serif, editorial scale, ink on generated stock
   -------------------------------------------------------------------------- */

html[data-look="paper"] body {
  --appearance-body-family: "Newsreader", Georgia, serif;
  --appearance-display-family: "Newsreader", Georgia, serif;
  --appearance-annotation-family: "Newsreader", Georgia, serif;
  --appearance-line-height: 1.62;
  --appearance-display-weight: 600;
  --grain-blend: multiply;
  letter-spacing: 0.002em;
}

/* Paper grain sits above the entire edition, including images and type. */
html[data-look="paper"] body::before {
  z-index: 6;
  mix-blend-mode: var(--grain-blend);
}

html[data-look="paper"] .name {
  line-height: 0.98;
  letter-spacing: -0.035em;
}

html[data-look="paper"] :is(.heading, .recipe-subheading, .meta, .tag, .ago, .dates, .measure, .recipe-time, .recipe-note-label, .calculator-label, .calculator-unit, .calculator-ratio-one, .calculator-ratio-separator, .calculator-measure, .copy-recipe, .footer, .appearance-control) {
  letter-spacing: 0.035em;
}

html[data-look="paper"] :is(.heading, .recipe-subheading) {
  font-size: var(--fs-heading);
  font-weight: 600;
  color: var(--accent);
}

html[data-look="paper"] :is(.copy-markdown, .copy-recipe, .calculator-input, .calculator-select),
html[data-look="paper"] .appearance-option {
  border-radius: 0;
}

/* --------------------------------------------------------------------------
   blob — contemporary sans typography + scroll-responsive colour fields
   -------------------------------------------------------------------------- */

html[data-look="blobs"] body {
  --appearance-body-family: "DM Sans", sans-serif;
  --appearance-display-family: "DM Sans", sans-serif;
  --appearance-annotation-family: "DM Sans", sans-serif;
  --appearance-display-weight: 700;
  --appearance-line-height: 1.55;
  --appearance-body-weight: 400;
  --grain-blend: screen;
}

html[data-look="blobs"] .name {
  line-height: var(--blob-name-line, 0.96);
  letter-spacing: var(--blob-name-spacing, -0.045em);
}

html[data-look="blobs"] :is(.heading, .recipe-subheading) {
  font-family: var(--appearance-display-family);
  font-size: var(--fs-heading);
  font-weight: var(--blob-heading-weight, 700);
  letter-spacing: 0.035em;
}

html[data-look="blobs"] :is(.copy-markdown, .copy-recipe, .calculator-input, .calculator-select, .appearance-option) { border-radius: 7px; }

/* Blob links bloom from the edition's selected accent. The near and far halo
   strengths and radius are seeded, while the text itself stays crisp. */
html[data-look="blobs"] a:not(.flickr-photo-link):is(:hover, :focus-visible) {
  text-shadow:
    0 0 3px color-mix(in srgb, var(--accent) var(--blob-link-bloom-near), transparent),
    0 0 var(--blob-link-bloom-radius) color-mix(in srgb, var(--accent) var(--blob-link-bloom-far), transparent);
}

html[data-look="blobs"] a.appearance-image:is(:hover, :focus-visible) {
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--accent) 78%, transparent),
    0 0 var(--blob-link-bloom-radius) color-mix(in srgb, var(--accent) var(--blob-link-bloom-far), transparent);
}

.appearance-backdrop {
  display: none;
  position: absolute;
  z-index: 0;
  inset: 0;
  overflow: hidden;
  overflow: clip;
  overflow-clip-margin: 100vh;
  contain: layout style;
  pointer-events: none;
}

html[data-look="blobs"] .appearance-backdrop { display: block; }

/* Scroll compensation lives on a lightweight wrapper; the expensive blur and
   autonomous drift stay on a cached inner surface. Keeping those transforms on
   separate compositor layers prevents mobile Safari from re-rasterizing a
   large blurred field on every scroll frame. */
.appearance-blob-track {
  --blob-drift-speed: 28s;
  --blob-drift-delay: 0s;
  --blob-from-x: -6%;
  --blob-from-y: -4%;
  --blob-from-scale: 0.96;
  --blob-to-x: 8%;
  --blob-to-y: 7%;
  --blob-to-scale: 1.08;
  position: absolute;
  z-index: 1;
  min-width: 260px;
  max-width: 840px;
  aspect-ratio: 1;
  transform: translate3d(0, var(--blob-parallax-y, 0px), 0);
  backface-visibility: hidden;
  will-change: transform;
}

.appearance-blob {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    color-mix(in srgb, var(--blob-color) calc(var(--blob-strength) * 100%), transparent),
    transparent 70%
  );
  filter: blur(var(--blob-blur));
  opacity: var(--blob-opacity);
  animation: appearance-blob-drift var(--blob-drift-speed) ease-in-out infinite alternate;
  animation-delay: var(--blob-drift-delay);
  backface-visibility: hidden;
  will-change: transform;
}

.appearance-blob-track:nth-child(1) {
  --blob-color: var(--blob-1-color, #326f67);
  --blob-strength: var(--blob-1-strength, 0.36);
  --blob-blur: var(--blob-1-blur, 42px);
  --blob-opacity: var(--blob-1-opacity, 1);
  width: var(--blob-1-size, 58vw);
}

.appearance-blob-track:nth-child(2) {
  --blob-color: var(--blob-2-color, #743e33);
  --blob-strength: var(--blob-2-strength, 0.36);
  --blob-blur: var(--blob-2-blur, 42px);
  --blob-opacity: var(--blob-2-opacity, 0.9);
  width: var(--blob-2-size, 58vw);
}

.appearance-blob-track:nth-child(3) {
  --blob-color: var(--blob-3-color, #604381);
  --blob-strength: var(--blob-3-strength, 0.36);
  --blob-blur: var(--blob-3-blur, 42px);
  --blob-opacity: var(--blob-3-opacity, 0.9);
  width: var(--blob-3-size, 58vw);
}

.appearance-blob-track:nth-child(4) {
  --blob-color: var(--blob-4-color, #315f79);
  --blob-strength: var(--blob-4-strength, 0.36);
  --blob-blur: var(--blob-4-blur, 42px);
  --blob-opacity: var(--blob-4-opacity, 0.82);
  width: var(--blob-4-size, 58vw);
}

.appearance-blob-track:nth-child(5) {
  --blob-color: var(--blob-5-color, #753f68);
  --blob-strength: var(--blob-5-strength, 0.36);
  --blob-blur: var(--blob-5-blur, 42px);
  --blob-opacity: var(--blob-5-opacity, 0.78);
  width: var(--blob-5-size, 58vw);
}

.appearance-blob-track:nth-child(6) {
  --blob-color: var(--blob-6-color);
  --blob-opacity: var(--blob-6-opacity);
  --blob-strength: var(--blob-6-strength);
  --blob-blur: var(--blob-6-blur);
  width: var(--blob-6-size, 58vw);
}

.appearance-blob-track:nth-child(7) {
  --blob-color: var(--blob-7-color);
  --blob-opacity: var(--blob-7-opacity);
  --blob-strength: var(--blob-7-strength);
  --blob-blur: var(--blob-7-blur);
  width: var(--blob-7-size, 58vw);
}

@keyframes appearance-blob-drift {
  from {
    transform: translate3d(var(--blob-from-x), var(--blob-from-y), 0) scale(var(--blob-from-scale));
  }
  to {
    transform: translate3d(var(--blob-to-x), var(--blob-to-y), 0) scale(var(--blob-to-scale));
  }
}

html[data-blob-count="3"] .appearance-blob-track:nth-child(n + 4),
html[data-blob-count="4"] .appearance-blob-track:nth-child(n + 5),
html[data-blob-count="5"] .appearance-blob-track:nth-child(n + 6) { display: none; }

html[data-blob-layout="diagonal"] .appearance-blob-track:nth-child(1) { top: -6%; left: -16%; }
html[data-blob-layout="diagonal"] .appearance-blob-track:nth-child(2) { top: 18%; right: -12%; }
html[data-blob-layout="diagonal"] .appearance-blob-track:nth-child(3) { top: 36%; left: 30%; }
html[data-blob-layout="diagonal"] .appearance-blob-track:nth-child(4) { top: 58%; right: -8%; }
html[data-blob-layout="diagonal"] .appearance-blob-track:nth-child(5) { top: 78%; left: -10%; }
html[data-blob-layout="diagonal"] .appearance-blob-track:nth-child(6) { top: 88%; left: 36%; }
html[data-blob-layout="diagonal"] .appearance-blob-track:nth-child(7) { top: 96%; right: -14%; }

html[data-blob-layout="orbit"] .appearance-blob-track:nth-child(1) { top: -8%; right: 6%; }
html[data-blob-layout="orbit"] .appearance-blob-track:nth-child(2) { top: 16%; left: -16%; }
html[data-blob-layout="orbit"] .appearance-blob-track:nth-child(3) { top: 34%; right: -8%; }
html[data-blob-layout="orbit"] .appearance-blob-track:nth-child(4) { top: 56%; left: 16%; }
html[data-blob-layout="orbit"] .appearance-blob-track:nth-child(5) { top: 76%; right: 2%; }
html[data-blob-layout="orbit"] .appearance-blob-track:nth-child(6) { top: 88%; left: -12%; }
html[data-blob-layout="orbit"] .appearance-blob-track:nth-child(7) { top: 96%; right: 24%; }

html[data-blob-layout="horizon"] .appearance-blob-track:nth-child(1) { top: 2%; left: -14%; }
html[data-blob-layout="horizon"] .appearance-blob-track:nth-child(2) { top: 10%; right: -12%; }
html[data-blob-layout="horizon"] .appearance-blob-track:nth-child(3) { top: 34%; left: 28%; }
html[data-blob-layout="horizon"] .appearance-blob-track:nth-child(4) { top: 58%; right: 12%; }
html[data-blob-layout="horizon"] .appearance-blob-track:nth-child(5) { top: 78%; left: -8%; }
html[data-blob-layout="horizon"] .appearance-blob-track:nth-child(6) { top: 88%; right: -12%; }
html[data-blob-layout="horizon"] .appearance-blob-track:nth-child(7) { top: 96%; left: 30%; }

html[data-blob-layout="scatter"] .appearance-blob-track:nth-child(1) { top: -7%; left: 18%; }
html[data-blob-layout="scatter"] .appearance-blob-track:nth-child(2) { top: 19%; left: -14%; }
html[data-blob-layout="scatter"] .appearance-blob-track:nth-child(3) { top: 37%; right: -10%; }
html[data-blob-layout="scatter"] .appearance-blob-track:nth-child(4) { top: 59%; left: 34%; }
html[data-blob-layout="scatter"] .appearance-blob-track:nth-child(5) { top: 79%; left: -12%; }
html[data-blob-layout="scatter"] .appearance-blob-track:nth-child(6) { top: 89%; right: -10%; }
html[data-blob-layout="scatter"] .appearance-blob-track:nth-child(7) { top: 96%; left: 24%; }

/* --------------------------------------------------------------------------
   eno — the complete document is one slowly changing luminous lightbox
   -------------------------------------------------------------------------- */

.eno-lightbox {
  display: none;
  position: absolute;
  z-index: 0;
  inset: 0;
  min-height: 100%;
  overflow: hidden;
  pointer-events: none;
  background: var(--eno-gradient-a);
  animation: eno-hue-drift var(--eno-hue-cycle) linear var(--eno-hue-phase) infinite;
  will-change: filter;
}

html[data-look="eno"] .eno-lightbox { display: block; }

/* The root stays as the dark browser frame outside the document. The absolute
   lightbox itself scales once across the body's full responsive height, so
   scrolling still travels through one continuous artwork. */
html[data-look="eno"] {
  background: var(--browser-frame);
}

.eno-lightbox-state {
  position: absolute;
  inset: -8vmax;
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  filter: blur(var(--eno-softness)) saturate(var(--eno-saturation));
  transform: scale(1.04);
  will-change: opacity;
}

.eno-lightbox-state:nth-child(1) {
  opacity: 1;
  background-image: var(--eno-gradient-a);
}

.eno-lightbox-state:nth-child(2) {
  opacity: 0;
  background-image: var(--eno-gradient-b);
  animation: eno-lightbox-state-b var(--eno-cycle) ease-in-out var(--eno-phase) infinite;
}

.eno-lightbox-state:nth-child(3) {
  opacity: 0;
  background-image: var(--eno-gradient-c);
  animation: eno-lightbox-state-c var(--eno-cycle) ease-in-out var(--eno-phase) infinite;
}

.eno-lightbox::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: var(--eno-diffusion);
  background:
    radial-gradient(ellipse at 50% 42%, rgb(255 255 255 / 0.72), transparent 64%),
    linear-gradient(115deg, rgb(255 255 255 / 0.35), transparent 38% 66%, rgb(255 255 255 / 0.18));
  mix-blend-mode: soft-light;
}

@keyframes eno-lightbox-state-b {
  0%, 8%, 100% { opacity: 0; }
  28%, 44% { opacity: 1; }
  62% { opacity: 0; }
}

@keyframes eno-lightbox-state-c {
  0%, 44%, 100% { opacity: 0; }
  66%, 84% { opacity: 1; }
}

@keyframes eno-hue-drift {
  from { filter: hue-rotate(0deg); }
  to { filter: hue-rotate(360deg); }
}

html[data-look="eno"] body {
  --appearance-body-family: "Montserrat", sans-serif;
  --appearance-display-family: "Montserrat", sans-serif;
  --appearance-annotation-family: "Montserrat", sans-serif;
  --appearance-display-weight: 600;
  --appearance-line-height: 1.64;
  --appearance-body-weight: 400;
  --grain-blend: soft-light;
  background: transparent;
}

html[data-look="eno"] .name {
  line-height: 1.02;
  letter-spacing: 0.075em;
  text-transform: uppercase;
}

html[data-look="eno"] :is(.heading, .recipe-subheading) {
  font-family: var(--appearance-display-family);
  font-size: var(--fs-heading);
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

html[data-look="eno"] .tagline { letter-spacing: 0.015em; }

html[data-look="eno"] :is(.meta, .tag, .ago, .dates, .measure, .recipe-time, .recipe-note-label, .footer, .appearance-control) {
  letter-spacing: 0.105em;
}

html[data-look="eno"] :is(.copy-markdown, .copy-recipe, .calculator-input, .calculator-select, .appearance-option) {
  background-color: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* --------------------------------------------------------------------------
   70mm — projected film gate, rear-plane light leaks, bloom, and halation
   -------------------------------------------------------------------------- */

html[data-look="70mm"] body {
  --appearance-body-family: Georgia, "Times New Roman", serif;
  --appearance-display-family: "Courier Prime", "Courier New", monospace;
  --appearance-annotation-family: "Courier Prime", "Courier New", monospace;
  --appearance-display-weight: 700;
  --appearance-line-height: 1.64;
  --grain-blend: screen;
}

/* Every appearance receives these neutral regions. Outside 70mm they collapse
   back into the original source order without affecting layout or semantics. */
.film-header,
.film-header-inner,
.film-exposure,
.film-exposure-inner,
.film-footer,
.film-footer-inner { display: contents; }

/* In 70mm, the page shell becomes the physical film strip. Its three regions
   own their own backgrounds so the exposed image cannot bleed into either
   utility area. The inner wrappers retain the existing responsive measure. */
html[data-look="70mm"] .page {
  width: 100%;
  max-width: none;
  margin: 0;
  padding: 0;
  container-type: normal;
}

html[data-look="70mm"] :is(.film-header, .film-exposure, .film-footer, .film-perforation) {
  display: block;
  width: 100%;
}

html[data-look="70mm"] :is(.film-header, .film-footer) {
  background: var(--film-stock);
}

html[data-look="70mm"] :is(.film-header-inner, .film-exposure-inner, .film-footer-inner) {
  display: block;
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding-inline: max(48px, calc(env(safe-area-inset-left) + 48px));
  padding-right: max(48px, calc(env(safe-area-inset-right) + 48px));
}

html[data-look="70mm"] .page--narrow :is(.film-header-inner, .film-exposure-inner, .film-footer-inner) {
  max-width: var(--page-narrow);
}

html[data-look="70mm"] :is(.film-header-inner, .film-footer-inner) {
  position: relative;
}

html[data-look="70mm"] .film-header-inner {
  padding-top: max(12px, calc(env(safe-area-inset-top) + 12px));
  padding-bottom: var(--film-header-rebate);
}

html[data-look="70mm"] .film-exposure {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* This subtree carries several blur-heavy, animated layers (gate,
     perforation bleed, grain, streaks, stains). Containment scopes their
     paint to this box explicitly, so the browser can cache/tile it
     independently instead of re-rasterizing it as part of a larger region
     during scroll — the likely cause of tiles briefly showing unpainted on
     real mobile hardware. */
  contain: paint;
  background: var(--film-base);
}

/* The gate is where emulsion falls off into dead black before meeting the
   stock: a soft non-linear fade ending in a hard 1px frame line, folding in
   the extra rebate margin so the perforation row sits inside a black field
   rather than forming its edge. */
.film-gate {
  display: none;
  position: absolute;
  /* The gate sits above the film surface but below semantic content. This
     softens grain and damage at the frame edge without dimming the page. */
  z-index: 7;
  inset: 0;
  pointer-events: none;
}

html[data-look="70mm"] .film-exposure .film-gate { display: block; }

html[data-look="70mm"] .film-gate {
  background:
    linear-gradient(to bottom,
      #000 0, #000 var(--film-gate-line),
      rgb(0 0 0 / 0.55) calc(var(--film-gate-line) + 0.4 * var(--film-gate-fall)),
      transparent calc(var(--film-gate-line) + var(--film-gate-fall)),
      transparent calc(100% - var(--film-gate-line) - var(--film-gate-fall)),
      rgb(0 0 0 / 0.55) calc(100% - var(--film-gate-line) - 0.4 * var(--film-gate-fall)),
      #000 calc(100% - var(--film-gate-line)),
      #000 100%),
    linear-gradient(to right,
      #000 0,
      rgb(0 0 0 / 0.55) calc(0.4 * var(--film-gate-fall-x)),
      transparent var(--film-gate-fall-x),
      transparent calc(100% - var(--film-gate-fall-x)),
      rgb(0 0 0 / 0.55) calc(100% - 0.4 * var(--film-gate-fall-x)),
      #000 100%);
}

html[data-look="70mm"] .film-exposure-inner {
  position: relative;
  z-index: 8;
  padding-top: 34px;
  padding-bottom: 46px;
  /* Give the actual content its own stable compositor layer via a static 3D
     transform (not will-change — that pairing is what's now suspected on
     the grain layer above it). If WebKit is mishandling this layer's own
     backing store inside the isolated stacking context while grain's
     animation keeps ticking above it, an independent layer here should stop
     it from being caught up in that. */
  transform: translateZ(0);
}

html[data-look="70mm"] .film-exposure .columns { margin-bottom: 0; }

html[data-look="70mm"] .film-footer-inner {
  padding-top: 12px;
  padding-bottom: max(12px, calc(env(safe-area-inset-bottom) + 12px));
}

/* The grain belongs to the exposed frame only: it overlays live content and
   images, but never the stock, perforations, or elastic overscroll fallback. */
html[data-look="70mm"] body::before {
  content: none;
}

/* Surface debris sits just under the grain: sparse dust (a seeded SVG tile,
   same technique as the perforation holes) plus a few hairline scratches.
   Both are a print-surface phenomenon, so — like grain — they sit above the
   content rather than behind it, kept faint enough not to fight legibility. */
.film-emulsion {
  display: none;
  position: absolute;
  z-index: 5;
  inset: 0;
  pointer-events: none;
  background-image: var(--film-dust-url);
  background-repeat: repeat;
  background-size: var(--film-dust-size) var(--film-dust-size);
  opacity: var(--film-dust-opacity);
}

html[data-look="70mm"] .film-exposure .film-emulsion { display: block; }

.film-scratch {
  position: absolute;
  top: var(--film-scratch-top, 0%);
  bottom: var(--film-scratch-bottom, 0%);
  left: var(--film-scratch-x);
  width: var(--film-scratch-width);
  background: linear-gradient(to bottom, transparent, var(--ink-bright) 12%, var(--ink-bright) 88%, transparent);
  opacity: var(--film-scratch-opacity);
  transform: rotate(var(--film-scratch-angle));
  pointer-events: none;
}

.film-scratch:nth-child(1) {
  --film-scratch-x: var(--film-scratch-1-x);
  --film-scratch-width: var(--film-scratch-1-width);
  --film-scratch-angle: var(--film-scratch-1-angle);
  --film-scratch-opacity: var(--film-scratch-1-opacity);
  --film-scratch-top: var(--film-scratch-1-top);
  --film-scratch-bottom: var(--film-scratch-1-bottom);
}

.film-scratch:nth-child(2) {
  --film-scratch-x: var(--film-scratch-2-x);
  --film-scratch-width: var(--film-scratch-2-width);
  --film-scratch-angle: var(--film-scratch-2-angle);
  --film-scratch-opacity: var(--film-scratch-2-opacity);
  --film-scratch-top: var(--film-scratch-2-top);
  --film-scratch-bottom: var(--film-scratch-2-bottom);
}

.film-scratch:nth-child(3) {
  --film-scratch-x: var(--film-scratch-3-x);
  --film-scratch-width: var(--film-scratch-3-width);
  --film-scratch-angle: var(--film-scratch-3-angle);
  --film-scratch-opacity: var(--film-scratch-3-opacity);
  --film-scratch-top: var(--film-scratch-3-top);
  --film-scratch-bottom: var(--film-scratch-3-bottom);
}

html[data-film-scratches="0"] .film-scratch,
html[data-film-scratches="1"] .film-scratch:nth-child(n + 2),
html[data-film-scratches="2"] .film-scratch:nth-child(n + 3) { display: none; }

html[data-look="70mm"] .film-exposure::after {
  content: "";
  position: absolute;
  /* Keep the projected grain as the exposure's top surface: it prints over
     every live pixel in the frame, including photographs and type. */
  z-index: 9;
  inset: 0;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
  background-image: var(--grain-url);
  background-repeat: repeat;
  /* Cap the tile's width to the frame's own width, never a seeded px value
     alone: this layer is edge-to-edge with no gutter on mobile, so a tile
     wider than the phone's own viewport left a real gap where WebKit failed
     to repeat it, showing as a bare, ungrained band. Height keeps the full
     seeded size — only width was ever the failure mode. */
  background-size: min(var(--grain-size), 100%) var(--grain-size);
  filter: contrast(var(--grain-contrast)) brightness(var(--grain-brightness));
  animation: appearance-film-grain-shift var(--grain-rate) steps(1) infinite;
  /* Keep this paint-driven. Promoting or translating a screen-blended layer
     reintroduces the WebKit backing-store clipping fixed above. */
}

html[data-look="70mm"] .name {
  line-height: 1;
  letter-spacing: var(--film-name-spacing);
  text-shadow:
    0 0 1px color-mix(in srgb, var(--ink-bright) 72%, transparent),
    0 0 var(--film-bloom-radius) color-mix(in srgb, var(--ink-bright) 28%, transparent);
}

html[data-look="70mm"] :is(.heading, .recipe-subheading) {
  font-family: var(--appearance-display-family);
  font-size: var(--fs-heading);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-shadow: 0 0 var(--film-bloom-radius) color-mix(in srgb, var(--accent) 30%, transparent);
}

html[data-look="70mm"] :is(.meta, .tag, .ago, .dates, .measure, .recipe-time, .recipe-note-label, .footer, .appearance-control) {
  letter-spacing: 0.055em;
}

.film-backdrop {
  display: none;
  position: absolute;
  z-index: 0;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

html[data-look="70mm"] .film-exposure .film-backdrop { display: block; }

.film-backdrop::after {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 0;
  background-image: radial-gradient(ellipse at 50% 42%, transparent 42%, rgb(0 0 0 / var(--film-vignette)) 100%);
  background-repeat: no-repeat;
  background-position: center top;
  background-size: 100% min(100%, 1100px);
}

/* Grouping wrappers only exist so each damage system gets its own clean
   :nth-child count — display:contents removes them from layout/stacking
   entirely (their position:absolute children still resolve against
   .film-backdrop) while keeping each system's siblings unmixed. */
.film-stains,
.film-lights,
.film-streaks {
  display: contents;
}

/* Soft, irregular development staining — desaturated toward the frame base
   and multiplied against it, so it reads as chemical unevenness rather than
   a light source. Anchored to the frame edge in vh, like the leaks below. */
.film-stain {
  position: absolute;
  width: min(var(--film-stain-size), 640px);
  aspect-ratio: var(--film-stain-ratio);
  left: var(--film-stain-x);
  top: var(--film-stain-top, auto);
  bottom: var(--film-stain-bottom, auto);
  background: radial-gradient(ellipse at var(--film-stain-focal-x) var(--film-stain-focal-y), var(--film-stain-color), transparent 70%);
  filter: blur(var(--film-stain-blur));
  opacity: var(--film-stain-opacity);
  mix-blend-mode: multiply;
  transform: translate3d(-50%, 0, 0) rotate(var(--film-stain-angle));
  pointer-events: none;
}

.film-stain:nth-child(1) {
  --film-stain-color: var(--film-stain-1-color);
  --film-stain-size: var(--film-stain-1-size);
  --film-stain-ratio: var(--film-stain-1-ratio);
  --film-stain-x: var(--film-stain-1-x);
  --film-stain-top: var(--film-stain-1-top);
  --film-stain-bottom: var(--film-stain-1-bottom);
  --film-stain-angle: var(--film-stain-1-angle);
  --film-stain-focal-x: var(--film-stain-1-focal-x);
  --film-stain-focal-y: var(--film-stain-1-focal-y);
  --film-stain-blur: var(--film-stain-1-blur);
  --film-stain-opacity: var(--film-stain-1-opacity);
}

.film-stain:nth-child(2) {
  --film-stain-color: var(--film-stain-2-color);
  --film-stain-size: var(--film-stain-2-size);
  --film-stain-ratio: var(--film-stain-2-ratio);
  --film-stain-x: var(--film-stain-2-x);
  --film-stain-top: var(--film-stain-2-top);
  --film-stain-bottom: var(--film-stain-2-bottom);
  --film-stain-angle: var(--film-stain-2-angle);
  --film-stain-focal-x: var(--film-stain-2-focal-x);
  --film-stain-focal-y: var(--film-stain-2-focal-y);
  --film-stain-blur: var(--film-stain-2-blur);
  --film-stain-opacity: var(--film-stain-2-opacity);
}

html[data-film-stains="0"] .film-stain,
html[data-film-stains="1"] .film-stain:nth-child(n + 2) { display: none; }

/* Leaks enter at the film edge, elongated along the strip, anchored in px/vh
   from the nearest frame edge rather than a percentage of document height. */
.film-light {
  position: absolute;
  width: min(var(--film-light-size), 760px);
  aspect-ratio: var(--film-light-ratio, 1.6);
  left: var(--film-light-x);
  top: var(--film-light-top, auto);
  bottom: var(--film-light-bottom, auto);
  border-radius: 50%;
  opacity: var(--film-light-opacity);
  background: radial-gradient(circle, var(--film-light-color), transparent 68%);
  filter: blur(var(--film-light-blur));
  transform: translate3d(-50%, 0, 0);
}

.film-light:nth-child(1) {
  --film-light-color: var(--film-light-1-color);
  --film-light-size: var(--film-light-1-size);
  --film-light-ratio: var(--film-light-1-ratio);
  --film-light-x: var(--film-light-1-x);
  --film-light-top: var(--film-light-1-top);
  --film-light-bottom: var(--film-light-1-bottom);
  --film-light-blur: var(--film-light-1-blur);
  --film-light-opacity: var(--film-light-1-opacity);
}

.film-light:nth-child(2) {
  --film-light-color: var(--film-light-2-color);
  --film-light-size: var(--film-light-2-size);
  --film-light-ratio: var(--film-light-2-ratio);
  --film-light-x: var(--film-light-2-x);
  --film-light-top: var(--film-light-2-top);
  --film-light-bottom: var(--film-light-2-bottom);
  --film-light-blur: var(--film-light-2-blur);
  --film-light-opacity: var(--film-light-2-opacity);
}

.film-light:nth-child(3) {
  --film-light-color: var(--film-light-3-color);
  --film-light-size: var(--film-light-3-size);
  --film-light-ratio: var(--film-light-3-ratio);
  --film-light-x: var(--film-light-3-x);
  --film-light-top: var(--film-light-3-top);
  --film-light-bottom: var(--film-light-3-bottom);
  --film-light-blur: var(--film-light-3-blur);
  --film-light-opacity: var(--film-light-3-opacity);
}

/* Linear leaks — a directional counterpart to the radial ones above: the
   kind of light that got in along a straight edge of the magazine. Most
   editions show one or none; a few show two. */
.film-streak {
  position: absolute;
  width: min(var(--film-streak-length), 1400px);
  height: var(--film-streak-thickness);
  left: var(--film-streak-x);
  top: var(--film-streak-top, auto);
  bottom: var(--film-streak-bottom, auto);
  background: linear-gradient(90deg, transparent, var(--film-streak-color), transparent);
  opacity: var(--film-streak-opacity);
  filter: blur(var(--film-streak-blur));
  transform: translate3d(-50%, 0, 0) rotate(var(--film-streak-angle));
  pointer-events: none;
}

.film-streak:nth-child(1) {
  --film-streak-color: var(--film-streak-1-color);
  --film-streak-length: var(--film-streak-1-length);
  --film-streak-thickness: var(--film-streak-1-thickness);
  --film-streak-angle: var(--film-streak-1-angle);
  --film-streak-x: var(--film-streak-1-x);
  --film-streak-top: var(--film-streak-1-top);
  --film-streak-bottom: var(--film-streak-1-bottom);
  --film-streak-blur: var(--film-streak-1-blur);
  --film-streak-opacity: var(--film-streak-1-opacity);
}

.film-streak:nth-child(2) {
  --film-streak-color: var(--film-streak-2-color);
  --film-streak-length: var(--film-streak-2-length);
  --film-streak-thickness: var(--film-streak-2-thickness);
  --film-streak-angle: var(--film-streak-2-angle);
  --film-streak-x: var(--film-streak-2-x);
  --film-streak-top: var(--film-streak-2-top);
  --film-streak-bottom: var(--film-streak-2-bottom);
  --film-streak-blur: var(--film-streak-2-blur);
  --film-streak-opacity: var(--film-streak-2-opacity);
}

html[data-film-streaks="0"] .film-streak,
html[data-film-streaks="1"] .film-streak:nth-child(n + 2) { display: none; }

/* A very faint, heavily blurred echo of the hole tile, phase-locked to the
   same pitch, seeping from the top perforation row into the frame below. */
.film-perf-bleed {
  display: none;
  position: absolute;
  z-index: 0;
  inset: 0 0 auto;
  height: var(--film-perf-height);
  pointer-events: none;
  background-image: var(--film-perf-hole-url);
  background-repeat: repeat-x;
  background-size: var(--film-perf-pitch) var(--film-perf-height);
  background-position: center;
  opacity: var(--film-perf-bleed-opacity);
  filter: blur(var(--film-perf-bleed-blur));
}

html[data-look="70mm"] .film-backdrop .film-perf-bleed { display: block; }

/* Holes are the light, not an object with a glow behind an opaque rectangle:
   a blurred warm bleed (allowed to spill past the strip, since nothing here
   clips it) sits under a crisp dim-warm hole fill with its own top-edge catch
   light, both cut from the same seeded rounded-rect SVG tile. */
.film-perforation {
  display: none;
  position: relative;
  height: var(--film-perf-height);
  pointer-events: none;
  background: var(--film-perf-stock);
}

.film-perforation::before,
.film-perforation::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: var(--film-perf-hole-url);
  background-repeat: repeat-x;
  background-size: var(--film-perf-pitch) var(--film-perf-height);
  background-position: center;
}

.film-perforation::before {
  z-index: 1;
  opacity: var(--film-perf-halo-opacity);
  filter: blur(var(--film-perf-halo-blur));
}

.film-perforation::after {
  z-index: 2;
}

html[data-look="70mm"] .film-perforation { display: block; }

/* Decorative, seeded, aria-hidden edge print — a frame counter in the header
   stock, a keykode-style line in the footer stock. Reproduces with ?seed=. */
.film-edge-print {
  display: none;
  position: absolute;
  pointer-events: none;
  font-family: var(--appearance-annotation-family);
  font-size: 9px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--film-perf-edge) 45%, #000);
  opacity: var(--film-edge-print-opacity);
  white-space: nowrap;
}

html[data-look="70mm"] .film-edge-print { display: block; }

.film-edge-print--header {
  top: max(4px, calc(env(safe-area-inset-top) + 4px));
  left: 0;
}

.film-edge-print--header::before { content: var(--film-edge-header); }

.film-edge-print--footer {
  bottom: max(4px, calc(env(safe-area-inset-bottom) + 4px));
  right: 0;
}

.film-edge-print--footer::before { content: var(--film-edge-footer); }

html[data-look="70mm"] .appearance-control {
  margin: 0;
  padding-bottom: 0;
  border-bottom-color: transparent;
}

html[data-look="70mm"] .footer {
  padding-top: 0;
  border-top-color: transparent;
}

/* Bright text blooms white first, then leaves the restrained red fringe that
   gives Cinestill-like halation its recognisable edge. */
html[data-look="70mm"] a:not(.flickr-photo-link):is(:hover, :focus-visible) {
  color: var(--ink-bright);
  text-shadow:
    0 0 2px var(--ink-bright),
    0 0 var(--film-bloom-radius) color-mix(in srgb, var(--ink-bright) 55%, transparent),
    0 0 var(--film-halation-radius) color-mix(in srgb, var(--film-halation) var(--film-halation-strength), transparent);
}

html[data-look="70mm"] a.appearance-image:is(:hover, :focus-visible) {
  border-color: var(--film-halation);
  box-shadow:
    0 0 2px color-mix(in srgb, var(--ink-bright) 60%, transparent),
    0 0 var(--film-halation-radius) color-mix(in srgb, var(--film-halation) var(--film-halation-strength), transparent);
}

/* Unexposed stock is still film, so the same halation answers it too — but
   only where light is actually landing: on hover/focus, never at rest. A
   resting or selected control stays the plain amber-accent voice every other
   look already uses (see .appearance-option[aria-pressed="true"] above). */
html[data-look="70mm"] :is(.appearance-option, .copy-markdown, .copy-recipe):is(:hover, :focus-visible) {
  color: var(--ink-bright);
  border-color: var(--film-halation);
  background: color-mix(in srgb, var(--film-halation) 8%, transparent);
  text-shadow:
    0 0 2px var(--ink-bright),
    0 0 var(--film-halation-radius) color-mix(in srgb, var(--film-halation) var(--film-halation-strength), transparent);
  box-shadow: 0 0 var(--film-halation-radius) color-mix(in srgb, var(--film-halation) 42%, transparent);
}

/* --------------------------------------------------------------------------
   CRT — the full /trials/scanline/ mask over the homepage signal
   -------------------------------------------------------------------------- */

.crt-mask-layer {
  display: none;
  position: fixed;
  z-index: 5;
  inset: 0;
  pointer-events: none;
}

html[data-look="crt"] .crt-mask-layer { display: block; }

.crt-mask-lines {
  mix-blend-mode: multiply;
  background-image: repeating-linear-gradient(
    to bottom,
    #fff 0 var(--crt-cell),
    var(--crt-gap) var(--crt-cell) calc(var(--crt-cell) * 2)
  );
}

.crt-mask-grille {
  mix-blend-mode: multiply;
  opacity: var(--crt-grille);
  background-position-x: calc(var(--crt-cell) * var(--crt-triad-phase));
  background-image: repeating-linear-gradient(
    to right,
    var(--crt-grille-red) 0 var(--crt-cell),
    var(--crt-grille-green) var(--crt-cell) calc(var(--crt-cell) * 2),
    var(--crt-grille-blue) calc(var(--crt-cell) * 2) calc(var(--crt-cell) * 3)
  );
}

.crt-mask-roll {
  bottom: auto;
  height: var(--crt-roll-height);
  mix-blend-mode: screen;
  background: linear-gradient(
    to bottom,
    transparent,
    rgb(255 255 255 / 0.04) 55%,
    rgb(255 255 255 / 0.075) 72%,
    transparent
  );
  animation: appearance-crt-roll var(--crt-roll-period) linear infinite;
  will-change: transform;
}

@keyframes appearance-crt-roll {
  from { transform: translate3d(0, -30vh, 0); }
  to { transform: translate3d(0, 105vh, 0); }
}

.crt-mask-tube {
  background:
    linear-gradient(var(--crt-glare-angle), transparent 23%, rgb(255 255 255 / 0.018) var(--crt-glare-center), transparent 73%),
    radial-gradient(var(--crt-tube-x) var(--crt-tube-y) at 50% 50%, transparent 52%, rgb(0 0 0 / var(--crt-vignette)) 100%);
}

html[data-look="crt"] body {
  --appearance-body-family: "VT323", monospace;
  --appearance-display-family: "VT323", monospace;
  --appearance-annotation-family: "VT323", monospace;
  --appearance-display-weight: 400;
  --appearance-body-weight: 400;
  --grain-blend: screen;
  --crt-gap: color-mix(in srgb, #000 calc(var(--crt-depth) * 1%), #fff);
  -webkit-font-smoothing: none;
  font-smooth: never;
  font-variant-ligatures: none;
  /* Per-phosphor convergence and bloom, kept to the physical cell so the
     bitmap source stays legible rather than becoming a filtered soft layer. */
  text-shadow:
    calc(var(--crt-cell) * var(--crt-red-x)) calc(var(--crt-cell) * var(--crt-red-y)) 0 rgb(var(--crt-fringe-red) / var(--crt-halo-red)),
    calc(var(--crt-cell) * var(--crt-blue-x)) calc(var(--crt-cell) * var(--crt-blue-y)) 0 rgb(var(--crt-fringe-blue) / var(--crt-halo-blue)),
    0 0 calc(var(--crt-cell) * var(--crt-halo-radius)) color-mix(in srgb, currentColor var(--crt-halo-strength), transparent);
}

/* The native selection stays inside the real browser selection model, but its
   field is the exact RGB inverse of the edition's accent phosphor. */
html[data-look="crt"] ::selection {
  color: var(--ink-bright);
  background-color: var(--crt-selection);
  text-shadow: none;
}

html[data-look="crt"] .page {
  /* Do not filter the full page: that rasterizes a smooth intermediate layer
     before the grille gets to it. The palette itself supplies the beam level. */
  filter: none;
}

html[data-look="crt"] .name {
  line-height: 1;
  letter-spacing: 0;
  text-transform: uppercase;
}

html[data-look="crt"] :is(.heading, .recipe-subheading) {
  font-size: var(--fs-heading);
  font-weight: 400;
  letter-spacing: 2px;
  text-transform: uppercase;
}

html[data-look="crt"] :is(.desc, .recipe-method, .recipe-note) { font-size: var(--fs-desc); }

html[data-look="crt"] :is(.desc, .tagline, .tag, .ago, .meta, .measure, .recipe-time, .recipe-note-label, .footer, .appearance-control) {
  font-weight: 400;
}

html[data-look="crt"] .meta { font-size: var(--fs-meta); }
html[data-look="crt"] :is(.tag, .measure, .ago, .recipe-time, .recipe-note-label) { font-size: var(--fs-tag); }
html[data-look="crt"] :is(.footer, .appearance-control) { font-size: var(--fs-footer); }
html[data-look="crt"] :is(.meta, .tag, .ago, .measure, .recipe-time, .recipe-note-label, .footer, .appearance-control) { letter-spacing: 1px; }

/* Photographs use a genuinely low-resolution source inside their unchanged
   frames; nearest-neighbour expansion makes the portrait share the same cells
   as the signal rather than merely receiving a grille on top. */
html[data-look="crt"] .appearance-image img {
  image-rendering: pixelated;
}

html[data-look="crt"] .appearance-image {
  box-shadow:
    inset 0 0 0 1px rgb(255 255 255 / 0.10),
    0 0 calc(var(--crt-cell) * 6) color-mix(in srgb, var(--ink) var(--crt-image-halo), transparent);
}

/* The hardware-style pointer is deliberately made of square cells, including
   its black outline, echoing the same "coarser monitor" cell doubling as the
   signal (see appearance.js) but sized in whole CSS pixels rather than
   --crt-cell's sub-pixel size, so it stays a legible, clickable size on
   retina displays. Its fill is the edition's own bright phosphor rather than
   a fixed ice-white. */
html[data-look="crt"] :is(body, body *) {
  cursor: var(--crt-cursor-url) var(--crt-cursor-hotspot), default;
}

html[data-look="crt"] .appearance-image--portrait .photo {
  top: -10%;
  left: -10%;
  width: 60%;
  height: 60%;
  transform: scale(2);
  transform-origin: top left;
}

html[data-look="crt"] :is(.copy-markdown, .copy-recipe, .calculator-input, .calculator-select, .appearance-option) { border-radius: 0; }

html[data-look="crt"] :is(.calculator-label, .calculator-unit, .calculator-ratio-one, .calculator-ratio-separator, .calculator-measure, .copy-recipe) {
  font-size: var(--fs-tag);
  font-weight: 400;
  letter-spacing: 1px;
}

html[data-look="crt"] :is(.calculator-input, .calculator-select) {
  font-size: var(--fs-desc);
  font-weight: 400;
  letter-spacing: 1px;
}

/* A CRT edition either carries one-pixel scan rules or a deliberately coarser
   two-pixel block signal. The utility frame echoes the latter without changing
   its control size or focus target. */
html[data-look="crt"] {
  --crt-rule-width: 1px;
}

html[data-look="crt"][data-crt-signal="block"] {
  --crt-rule-width: 2px;
}

html[data-look="crt"] .leader {
  border-bottom-width: var(--crt-rule-width);
}

html[data-look="crt"] .appearance-control {
  border-bottom-width: var(--crt-rule-width);
}

html[data-look="crt"] .footer {
  border-top-width: var(--crt-rule-width);
}

/* Grid items stretch by default. CRT footer links instead keep the same
   text-width phosphor selection used by links in the main content. */
html[data-look="crt"] .footer > a { inline-size: fit-content; }
html[data-look="crt"] .footer-date { justify-self: start; }
html[data-look="crt"] .footer-domain { justify-self: end; }

html[data-look="crt"] :is(.appearance-reload, .copy-markdown, .copy-recipe) {
  border-color: color-mix(in srgb, var(--accent) 58%, var(--border));
  text-shadow: 0 0 2px currentColor;
  box-shadow: 0 0 5px color-mix(in srgb, var(--accent) 28%, transparent);
}

html[data-look="crt"][data-crt-signal="block"] :is(.appearance-reload, .copy-markdown, .copy-recipe) {
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--accent) 42%, transparent),
    0 0 5px color-mix(in srgb, var(--accent) 28%, transparent);
}

html[data-look="crt"] :is(.appearance-reload, .copy-markdown, .copy-recipe):is(:hover, :focus-visible) {
  color: var(--accent);
  border-color: var(--accent);
  background-color: color-mix(in srgb, var(--accent) 10%, transparent);
  text-shadow: 0 0 3px currentColor;
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--accent) 58%, transparent),
    0 0 10px color-mix(in srgb, var(--accent) 68%, transparent);
}

/* CRT links respond like a phosphor selection block. The stronger marker
   sizing remains text-only: the authored ↗︎ and → characters never change. */
html[data-look="crt"] a:not(.flickr-photo-link):is(:hover, :focus-visible) {
  color: var(--bg);
  background-color: var(--accent);
  text-shadow: none;
  box-shadow: 0 0 0 1px var(--accent), 0 0 8px color-mix(in srgb, var(--accent) 58%, transparent);
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

html[data-look="crt"] :is(a[target="_blank"]:not(.flickr-photo-link), .internal-link)::after {
  margin-left: 0.22em;
  color: inherit;
  font-size: 1.12em;
  font-weight: 400;
  line-height: 1;
  text-shadow: none;
}

html[data-look="crt"] a:not(.flickr-photo-link):is(:hover, :focus-visible)::after {
  text-shadow: none;
}

/* --------------------------------------------------------------------------
   terminal — quiet all-mono typesetting, prompts, grain, no CRT glow
   -------------------------------------------------------------------------- */

html[data-look="terminal"] body {
  --appearance-body-family: var(--terminal-font-family);
  --appearance-display-family: var(--terminal-font-family);
  --appearance-annotation-family: var(--terminal-font-family);
  --appearance-display-weight: var(--terminal-display-weight);
  --appearance-line-height: 1.6;
  --appearance-body-weight: 400;
  --grain-blend: screen;
}

html[data-look="terminal"] .name {
  font-size: var(--appearance-display-size);
  line-height: 1.05;
  letter-spacing: var(--terminal-name-spacing);
}

html[data-look="terminal"] :is(.heading, .recipe-subheading) {
  font-weight: 500;
  letter-spacing: var(--terminal-heading-spacing);
}

html[data-look="terminal"] :is(.heading, .recipe-subheading)::before {
  display: inline-block;
  margin-right: 0.55em;
  color: var(--faint);
}

html[data-look="terminal"][data-terminal-prompt="tilde"] :is(.heading, .recipe-subheading)::before { content: "~/"; }
html[data-look="terminal"][data-terminal-prompt="dot"] :is(.heading, .recipe-subheading)::before { content: "./"; }
html[data-look="terminal"][data-terminal-prompt="chevron"] :is(.heading, .recipe-subheading)::before { content: ">"; }

html[data-look="terminal"] :is(.copy-markdown, .copy-recipe, .calculator-input, .calculator-select, .appearance-option) {
  border-radius: 0;
}

/* The selected terminal prompt also annotates the edition status. It remains
   decorative and is hidden with that status on compact mobile controls. */
html[data-look="terminal"] .appearance-status::before {
  display: inline-block;
  margin-right: 0.35em;
  color: var(--faint);
}

html[data-look="terminal"][data-terminal-prompt="tilde"] .appearance-status::before { content: "~/"; }
html[data-look="terminal"][data-terminal-prompt="dot"] .appearance-status::before { content: "./"; }
html[data-look="terminal"][data-terminal-prompt="chevron"] .appearance-status::before { content: ">"; }

/* Terminal links use the same compact selection inversion as CRT, without the
   phosphor halo. The block follows wrapped text instead of stretching a row. */
html[data-look="terminal"] a:not(.flickr-photo-link):is(:hover, :focus-visible) {
  color: var(--bg);
  background-color: var(--accent);
  text-shadow: none;
  box-shadow: 0 0 0 1px var(--accent);
}

html[data-look="terminal"] a:not(.flickr-photo-link):is(:hover, :focus-visible)::after {
  color: inherit;
  text-shadow: none;
}

@media (min-width: 641px) and (max-width: 839px) {
  html[data-look] {
    --fs-display: 2.5rem;
    --fs-body: 1.03125rem;
    --fs-desc: 0.9375rem;
    --fs-heading: 0.78125rem;
    --fs-meta: 0.75rem;
    --fs-tag: 0.71875rem;
    --fs-footer: 0.6875rem;
  }
}

@media (max-width: 640px) {
  html[data-look] {
    --fs-display: 2rem;
    --fs-body: 1rem;
    --fs-desc: 0.90625rem;
    --fs-heading: 0.75rem;
    --fs-meta: 0.71875rem;
    --fs-tag: 0.6875rem;
    --fs-footer: 0.65625rem;
  }

  html[data-look] .page {
    padding-right: max(24px, calc(env(safe-area-inset-right) + 24px));
    padding-left: max(24px, calc(env(safe-area-inset-left) + 24px));
  }

  /* The shared mobile shell adds gutters to every appearance. The 70mm shell
     delegates those gutters to its inner regions so the physical film reaches
     both edges while its content retains the same safe-area clearance. */
  html[data-look="70mm"] .page { padding-inline: 0; }

  html[data-look="70mm"] :is(.film-header-inner, .film-exposure-inner, .film-footer-inner) {
    padding-left: max(24px, calc(env(safe-area-inset-left) + 24px));
    padding-right: max(24px, calc(env(safe-area-inset-right) + 24px));
  }

  html[data-look] .columns { grid-template-columns: minmax(0, 1fr); }

  .appearance-image--portrait { width: 116px; }

  .appearance-blob-track {
    min-width: 0;
    max-width: none;
  }

  html[data-look="blobs"][data-blob-count="3"] .appearance-blob-track:nth-child(4),
  html[data-look="blobs"][data-blob-count="4"] .appearance-blob-track:nth-child(5),
  html[data-look="blobs"][data-blob-count="5"] .appearance-blob-track:nth-child(6),
  html[data-look="blobs"][data-blob-count="3"][data-blob-mobile-extra="2"] .appearance-blob-track:nth-child(5),
  html[data-look="blobs"][data-blob-count="4"][data-blob-mobile-extra="2"] .appearance-blob-track:nth-child(6),
  html[data-look="blobs"][data-blob-count="5"][data-blob-mobile-extra="2"] .appearance-blob-track:nth-child(7) {
    display: block;
  }

  html[data-look="crt"] .crt-mask-grille { opacity: calc(var(--crt-grille) * 0.86); }
  html[data-look="crt"] body::before { opacity: calc(var(--grain-opacity) * 0.72); }
}

@media (prefers-reduced-motion: reduce) {
  .appearance-blob-track { transform: none !important; will-change: auto; }
  .appearance-blob { animation: none !important; will-change: auto; }
  .eno-lightbox { animation: none !important; will-change: auto; }
  .eno-lightbox-state { animation: none !important; will-change: auto; }
  .eno-lightbox-state:nth-child(n + 2) { opacity: 0; }
  html[data-look] body::before,
  html[data-look="70mm"] .film-exposure::after,
  .paper-texture-layer { animation: none !important; will-change: auto; }
  .crt-mask-roll { display: none !important; }
}

/* Compact appearance chooser: shared utility text and a short, responsive choice grid.
   The expandable chooser is used only at phone widths (640px and below). */
.appearance-toggle,
.appearance-label-full,
.appearance-shuffle-label { display: none; }

html[data-look] .appearance-control[data-compact] {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 6px;
  font-size: var(--fs-footer);
  font-weight: 400;
  line-height: 1.35;
  letter-spacing: 0;
  margin-bottom: 18px;
}

.appearance-control[data-compact] .appearance-toggle,
.appearance-control[data-compact] .appearance-reload {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.45em;
  min-width: 0;
  min-height: 32px;
  padding: 0.35em 0.65em;
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: center;
  line-height: inherit;
  color: var(--ink);
}

.appearance-control[data-compact] .appearance-toggle { flex: 1 1 12em; }
.appearance-control[data-compact] .appearance-reload {
  flex: 1 1 6em;
  min-inline-size: max-content;
}
.appearance-shuffle-label { white-space: nowrap; }
.appearance-reload-icon { flex: none; }
.appearance-toggle-label { min-width: 0; }
.appearance-toggle-chevron {
  flex: none;
  width: 0.4em;
  height: 0.4em;
  margin-top: -0.2em;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: rotate(45deg);
}
.appearance-toggle[aria-expanded="true"] .appearance-toggle-chevron {
  margin-top: 0.2em;
  transform: rotate(225deg);
}

.appearance-control[data-compact] .appearance-options {
  display: none;
  order: 3;
  flex: 0 0 100%;
  min-width: 0;
  padding-top: 4px;
}
.appearance-control[data-compact][data-expanded] .appearance-options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 10em), 1fr));
  gap: 6px;
}
.appearance-control[data-compact] .appearance-options .appearance-option {
  display: flex;
  align-items: center;
  gap: 0.7em;
  min-height: 32px;
  padding: 0.35em 0.65em;
  white-space: normal;
  overflow-wrap: anywhere;
  justify-content: center;
  text-align: center;
  line-height: inherit;
  color: var(--ink);
}
.appearance-control[data-compact] .appearance-options .appearance-option::before {
  content: "";
  flex: none;
  width: 0.65em;
  height: 0.65em;
  border: 1px solid currentColor;
  border-radius: 50%;
}
.appearance-control[data-compact] .appearance-option[aria-pressed="true"] {
  color: var(--accent);
  border-color: var(--accent);
}
.appearance-control[data-compact] .appearance-option[aria-pressed="true"]::before {
  background: radial-gradient(circle, currentColor 0 38%, transparent 42%);
}
.appearance-control[data-compact] .appearance-status,
.appearance-control[data-compact] .appearance-label-short,
.appearance-control[data-compact] .appearance-reload-label { display: none; }
.appearance-control[data-compact] .appearance-label-full,
.appearance-control[data-compact] .appearance-shuffle-label { display: inline; }
.appearance-control[data-compact] .appearance-option:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
