/* ============================================================================
   DYNAMITE — an opt-in theme for reddy.world.

   After the opening titles of Napoleon Dynamite (2004), designed by Aaron
   Ruell: locked-off overhead tabletop shots, flat shadowless light, credits
   written in ketchup on food or printed on school ephemera, surfaces made of
   carpet samples and gingham vinyl. Nothing looks designed. It looks found.

   ---------------------------------------------------------------------------
   HOW THIS FILE WORKS

   Every rule is scoped under :root[data-theme="dynamite"], so this file is
   completely inert until the attribute is set. It is linked unconditionally
   from index.html — costing ~3KB — because being render-blocking is the whole
   point: it is present before first paint, so switching themes never flashes.

   SPECIFICITY: :root[data-theme="dynamite"] is (0,2,0). Plain :root is
   (0,1,0), and media queries contribute NO specificity, so the
   @media (prefers-color-scheme: light) block in mainCSS.css is also only
   (0,1,0) and loses to us regardless of source order. Dynamite therefore
   wins in both system colour schemes.

   ⚠ THE COROLLARY THAT BITES: specificity only protects tokens we actually
   REDECLARE. Any token omitted here still flips underneath us with the OS
   light-mode block in mainCSS.css. So the flipping set below must stay
   COMPLETE: --bg, --surface, --fg, --fg-muted, --gold, --gold-hover, --silver.
   Add a token to that block in mainCSS.css? Add it here too.

   mainCSS.css remains the untouched source of truth for the default
   identity. This file is explicitly the override layer.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   SCHOOLSCHRIFT — Dutch school handwriting, used on /bio.

   "Schoolschrift" is literally a school exercise book, which is why this and
   the ruled paper belong together.

   Font by Liesbeth Flobbe and Bart Voorzanger, https://syboor.eu/fonts/schoolschrift03/
   Licence: free for non-commercial use; commercial use requires crediting the
   origin with a reference to that URL. reddy.world carries AdSense, so we
   treat it as commercial and credit it — on the /bio page where it is used,
   and in humans.txt. Do not remove those credits.

   This is SchoolschriftLG, subsetted to Latin and converted to woff2
   (215KB TTF -> 27KB). The `calt` and `liga` features are deliberately
   retained: they are what joins the letters into cursive. Dropping them
   leaves the text legible but disconnected, which is the whole point lost.
   The 12 stylistic sets (ss01-ss12) were dropped — we use none of them, and
   they are why schoolschrijver.js exists. SchoolschriftLG needs no JS in a
   modern browser.

   Declared at document level, not in a shadow root: @font-face inside a
   shadow root does not apply. It is unscoped rather than nested under the
   theme because @font-face is lazy — the file is only fetched when something
   actually renders in it, which only dynamite's /bio ever does.
   ------------------------------------------------------------------------ */
@font-face {
  font-family: 'Schoolschrift LG';
  src: url('/fonts/schoolschrift-lg.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root[data-theme="dynamite"] {
  /* The film's opening is flat, bright and daylit. There is no dark version
     of it, so the theme is scheme-locked and authors no dark variant. */
  color-scheme: light;

  /* --- the COMPLETE flipping set (see warning above) --------------------- */
  --bg: #f3ead8;        /* cream — tablecloth white */
  --surface: #e6d5b8;   /* manila card */
  --fg: #2b2622;        /* ink */
  --fg-muted: #6b6560;  /* pencil */
  --gold: #7a4e24;      /* formica brown — replaces gold as the accent */
  --gold-hover: #a32b1c;/* ketchup */

  /* --silver is a "fixed palette" token in mainCSS.css that never flips with
     the scheme — a cool blue-grey. It is the hover background for the header
     and footer nav, where the text colour is var(--bg). On cream that means
     --silver must be DARK or the hover state becomes unreadable.
     Cream on this brown measures 9.01:1. */
  --silver: #4a3b2a;

  /* --- dynamite-only accents -------------------------------------------- */
  --ketchup: #a32b1c;
  --mustard: #d9a404;
  --avocado: #5f6b27;
  --plate: #fbf8f2;     /* diner crockery / photo-print white */
  --paper: #fbf7ec;     /* a sheet laid on the cloth */

  /* ======================================================================
     MEASURED PALETTE — sampled pixel-by-pixel from the title sequence.

     Everything above this block was invented by eye. Everything below was
     read out of the actual frames with System.Drawing, verified against the
     letterbox bars reading #000000 exactly.

     These are ADDITIVE for now. The semantic tokens above are deliberately
     left alone until the specimen page exists and we have chosen where each
     surface goes — "make the styles and see how we apply them".

     ⚠ THE CALIBRATION FINDING, which is the whole reason this block exists:
     the sequence is graded warm and desaturated, and NOTHING in it is true
     white or true black except the letterbox. The plate that reads as white
     is #c9cbcf. The mustard that reads as bright yellow is #d49768. Pick
     colours the way your eye reads them off a screenshot and you overshoot
     saturation badly — which is precisely why the first pass came out clean
     and tasteful when the film is worn and slightly ugly. Sample, never
     eyeball.
     ====================================================================== */

  /* -- grounds ---------------------------------------------------------- */
  --nd-oxblood: #481818;   /* shag carpet — frame 1 of the film */
  --nd-oyster: #a89888;    /* mottled marbled laminate desktop */
  --nd-ochre: #a88848;     /* lunch table — the warmest ground */
  --nd-sage: #787858;      /* painted surface under the foil tray */
  --nd-egg-blue: #6898a8;  /* pale carpet under the terracotta egg plate */
  --nd-teal: #8898a8;      /* defocused backdrop behind the library cards */

  /* -- objects ---------------------------------------------------------- */
  --nd-mustard: #e8b868;   /* the Preston High ID card stock */
  --nd-cyan: #98b8d8;      /* the VALID sticker — the deliberately ugly one */
  --nd-cornflower: #8898c8;/* the ID photo backdrop */
  --nd-pencil: #d88868;    /* the #2 pencils */
  --nd-eraser: #c88898;    /* the pink block eraser */
  --nd-terracotta: #a88878;/* the redware egg plate */
  --nd-crockery: #c9cbcf;  /* the "white" plate — note it is NOT white */
  --nd-skin: #a87868;      /* remarkably consistent across every hands shot */

  /* -- ink -------------------------------------------------------------- */
  --nd-ink: #1f1813;       /* body text: 6.27:1 on oyster, 9.59:1 on mustard */
  --nd-ink-muted: #332b24; /* secondary text: 4.97:1 on oyster */
  --nd-rust: #46200a;      /* links/accent: 5.11:1 on oyster, 7.81:1 on mustard */
  --nd-hover: #3e2f22;     /* nav hover ground: carries oyster text at 4.60:1 */
  --nd-cream: #e8e0d0;     /* text for dark grounds: 11.28:1 on oxblood */

  /* ⚠ CONTRAST RULES discovered by measuring the whole matrix, not guessed.
     These are constraints, not suggestions:

     1. --nd-marker is the film's real lettering colour, and it FAILS AA at
        body size on every ground (2.97:1 on oyster). It is for LARGE display
        text only — which is authentic, since in the film it only ever appears
        as big hand-drawn capitals.
     2. --nd-sage is a mid-tone trap: it fails against dark ink (3.86:1) AND
        against light text (3.46:1). Decorative only. Never put text on it.
     3. --nd-oxblood needs light text; every other ground here needs dark. */
  --nd-marker: #783f22;    /* hand-drawn marker capitals — LARGE TEXT ONLY */

  /* Handwriting, and the ruled paper it sits on. Colours and the 43.83px
     rule pitch were sampled from public/lined-paper.jpg — the rules are
     generated rather than using that JPEG so the line-height and the ruling
     stay locked together at every font size and viewport width. A bitmap
     would drift out of register the moment the text reflowed. */
  --theme-font-hand: 'Schoolschrift LG', 'Segoe Script', 'Bradley Hand', cursive;
  --rule-ink: #426489;    /* sampled from the scan */
  --rule-margin: #a83c28; /* the red vertical margin */
  --rule-pitch: 2.125rem; /* 34px — the scan's pitch scaled to a ~1000px sheet */

  /* --- handles consumed by the :host block in css/mainCSS-refined.ts ------
     Those properties are declared on :host, and a declared value always
     beats an inherited one, so they can only be reached through the
     var(--theme-*, default) indirection set up in that file. */
  /* Headings are TYPED, not set. The film's institutional lettering is
     typewriter and rubber stamp, and a monospace stack gets there with no
     webfont at all — Cormorant Garamond's opposite in every way. */
  --theme-font-display: 'Courier New', Courier, 'Nimbus Mono PS', monospace;

  /* School stationery is square-ish; gilt-frame rounding is wrong here. */
  --theme-radius-sm: 1px;
  --theme-radius-base: 2px;
  --theme-radius-lg: 3px;

  /* The gold gallery mat with its sepia wash is the single most visible
     thing a pure colour swap would miss. Here every image becomes a
     snapshot print lying on the table: white border, no tint. */
  --theme-img-frame-width: 6px;
  --theme-img-frame-color: var(--plate);
  --theme-img-frame-padding: 0px;
  --theme-img-filter: none;

  /* menu-header inverts its hamburger icon per colour scheme via a
     hardcoded filter. Tokenised so dynamite gets the light-surface value
     even when the OS is in dark mode. */
  --icon-invert: 0%;
}

/* ---------------------------------------------------------------------------
   DOCUMENT LAYER
   Only reaches the light DOM of index.html — body, #app, header-menu,
   footer-menu. Component internals are styled from their own shadow roots.
   ------------------------------------------------------------------------ */

/* body is `display: grid; height: 100vh` in mainCSS.css, which would crop a
   full-bleed surface to exactly one viewport. min-height + fixed attachment
   keeps the cloth under the whole scroll. */
:root[data-theme="dynamite"] body {
  height: auto;
  min-height: 100svh;
  background-color: var(--bg);
  background-image: var(--surface-cloth, var(--surface-cloth-drawn));
  background-attachment: fixed;
  background-size: var(--surface-cloth-size, auto);
  background-position: center;
  /* no-repeat matters once the ground is a cover-sized photograph: without it
     a viewport taller or wider than the image's aspect tiles the edge back in.
     It is a token because the LIGHT variant below is a small tile and does
     need to repeat. */
  background-repeat: var(--surface-cloth-repeat, no-repeat);
}

/* <amp-auto-ads> is the first child of body and takes a grid row whether or
   not an ad ever fills it, leaving a stripe of tablecloth above the nav. It
   is invisible on the default theme's black. Collapse it only while empty,
   so a real ad still gets its space. */
:root[data-theme="dynamite"] amp-auto-ads:empty {
  display: none;
}

/* body is `display: grid` with no explicit rows, so its three children size to
   content and anything left over collects at the BOTTOM — which is why a strip
   of tablecloth was showing under the footer. Naming the rows pins the footer
   down and gives the middle row the slack instead. */
:root[data-theme="dynamite"] body {
  grid-template-rows: auto 1fr auto;
}

/* The nav is chrome, not an object on the table — it gets its own band of
   paper so it never has to compete with the check behind it.
   The default theme's 1rem padding + 2rem margin left ~130px of empty cream
   under the nav, which read as a blank slab rather than a header. */
:root[data-theme="dynamite"] header-menu {
  padding-bottom: 0.35rem;
  margin-bottom: 0;
  /* mainCSS.css never gives header-menu a display, so it is inline — and an
     inline box only paints its background behind its line boxes, which left
     a ragged strip of tablecloth above the nav. Block makes it a proper band.
     Scoped to dynamite, so the default theme's layout is untouched. */
  display: block;
  background: var(--paper);
  box-shadow: 0 2px 12px rgba(58, 47, 30, 0.2);
  border-bottom-style: dashed;
  border-bottom-color: color-mix(in srgb, var(--ketchup) 45%, transparent);
}

/* The footer is chrome too — its links were brown-on-gingham and hard to read. */
:root[data-theme="dynamite"] footer-menu {
  display: block;
  background: var(--paper);
  box-shadow: 0 -2px 12px rgba(58, 47, 30, 0.2);
  border-top: 1px dashed color-mix(in srgb, var(--ketchup) 45%, transparent);
}

/* Every page EXCEPT the landing gets a sheet of paper laid on the cloth.
   Running body copy directly over gingham is unreadable, and it is also
   wrong: in the film there is always an object between the camera and the
   tablecloth. Never text straight on the cloth.

   The landing opts out via :has() — its whole point is the bare table with
   objects arranged on it. */
/* /portfolio opts out for the same reason the landing does: its projects are
   prints laid ON the table, and a sheet of paper under them is one surface too
   many — the same "box around boxes" Ryan objected to on /bio. */
:root[data-theme="dynamite"] main:not(:has(landing-element)):not(:has(bio-element)):not(:has(cv-linkedin-element)):not(:has(portfolio-element)) {
  max-width: 1000px;
  margin: 0 auto 3.5rem;
  padding: 0.5rem 1.5rem 3rem;
  background: var(--paper);
  box-shadow: 0 10px 30px rgba(58, 47, 30, 0.26);
  border-radius: 2px;
}

@media (max-width: 700px) {
  :root[data-theme="dynamite"] main:not(:has(landing-element)):not(:has(bio-element)):not(:has(cv-linkedin-element)):not(:has(portfolio-element)) {
    margin: 0 0.75rem 2rem;
    padding: 0.5rem 1rem 2rem;
  }
}

/* /bio opts out too, for the opposite reason to the landing: it brings its
   own ground — rec-room wood panelling — which has to run full-bleed rather
   than sit framed inside a sheet of paper. */
:root[data-theme="dynamite"] main:has(bio-element) {
  max-width: none;
  margin: 0;
  padding: 0;
  background: none;
  box-shadow: none;
}

/* ---------------------------------------------------------------------------
   OBJECT MANIFEST

   Every object on the table renders as:

       background-image: var(--obj-plate, var(--obj-plate-drawn));

   so the drawn SVG shows until a photograph exists. To add a photograph,
   uncomment its --obj-* line. That is the entire swap — no markup change,
   no code change. If it ever needs a code change, this design has failed.

   ⚠ THREE WAYS TO BREAK THIS, ALL OF THEM SILENT:

   1. To un-set a photo, DELETE the line or write `--obj-plate: initial;`.
      `initial` is the guaranteed-invalid value and correctly triggers the
      fallback. Writing `--obj-plate: ;` does NOT — that is a valid empty
      value, background-image becomes invalid at computed-value time, and
      you get NEITHER the photo nor the drawing.

   2. Do NOT use image-set() with type(). If a browser cannot parse the
      declaration it is still a DEFINED one, so the var() fallback does not
      fire and again you get nothing. Plain url() is safest.

   3. Do NOT declare any --obj-* inside a component's :host — same trap as
      the --theme-* properties above. Declare them only here.

   Each object carries an -aspect alongside it so the layout is identical
   before and after a photograph lands, and nothing shifts when one does.
   ------------------------------------------------------------------------ */

:root[data-theme="dynamite"] {
  /* Drawn placeholders — always present, checked into git. */
  /* Gingham: two half-opacity stripe sets crossing. Where they overlap you
     get the dark square, where one runs alone the mid square, and where
     neither does the cloth. A tight check (20px) reads as vinyl tablecloth;
     anything much larger reads as a picnic blanket.
     Kept as the fallback only — the ground is the cafeteria lino below. */
  --surface-cloth-drawn:
    repeating-linear-gradient(0deg,  color-mix(in srgb, var(--ketchup) 42%, transparent) 0 20px, transparent 20px 40px),
    repeating-linear-gradient(90deg, color-mix(in srgb, var(--ketchup) 42%, transparent) 0 20px, transparent 20px 40px);

  /* THE GROUND. Ryan: "not picnic table pls make it cafeteria table or carpet
     at least". Gingham was always the weakest thing on the page — a picnic
     blanket is a leisure surface, and nothing in this film happens at leisure.
     Everything in the title sequence sits on an INSTITUTIONAL surface: school
     lino, classroom carpet, a formica desk.

     PHOTOGRAPHED now, not generated. These are frames lifted straight from the
     title sequence at 1920x1040 — real pile, real grain, real lens falloff into
     the corners — where the old set was scripts/make-surfaces.py approximating
     that from measured statistics. The generated tiles are still there and
     still work; they are what the light variant below uses.

     Swap the ground by changing this one line. Ready and sized:
       /dynamite/surfaces/olive-lino.webp      school lino, tile seams visible
       /dynamite/surfaces/maroon-shag.webp     the surface the film OPENS on
       /dynamite/surfaces/striped-berber.webp  banded loop, has a red and a
                                               pale-blue line already in it
       /dynamite/surfaces/orange-loop.webp     chunky rust loop, boldest colour
       /dynamite/surfaces/ochre-weave.webp     the only directional one, warm

     Also photographed, same treatment: cafeteria-lino (14), berber-carpet
     (26), desk-formica (28). Regenerate any of them with
     scripts/make-surfaces-from-frames.py — it finds a frame by number, so it
     does not care which HD-snaps subfolder the frame is filed under.

     blue-carpet.webp is an orphan: a generated tile left over from before
     the unsuffixed names meant "photograph". Nothing points at it.

     ⚠ Nothing readable sits directly on this. Measured worst-case contrast
     against the lino's own extremes: --fg 2.12:1, --paper 4.25:1. That is not
     a flaw in the texture, it is what a mid-tone ground IS — the same trap
     --nd-sage falls into. The rule below ("Never text straight on the cloth")
     is what keeps it honest, and the landing obeys it by putting every word
     on an object. */
  --surface-cloth: url("/dynamite/surfaces/olive-lino.webp");

  /* The wall on /bio, tokenised for the same reason: bio.ts is a shadow root
     and cannot see this block's other declarations, but custom properties do
     inherit through the boundary. */
  --wall: url("/dynamite/surfaces/wood-panel.webp");
  --wall-size: cover;
  --wall-repeat: no-repeat;
  --wall-attachment: fixed;
  /* cover, not a tile size. These are WHOLE FRAMES now, used as one image
     sized to the viewport and held still by background-attachment: fixed
     above — so the ground is a single continuous surface rather than a
     repeating pattern. Tiling was tried and Ryan's verdict was "somehow it
     tiled funny": joining a photograph's right edge to its left cannot be
     done for free, and the eye finds the repeat regardless. */
  --surface-cloth-size: cover;

  --obj-plate-drawn: url("/dynamite/drawn/plate.svg");
  --obj-plate-aspect: 1 / 1;

  --obj-id-card-drawn: url("/dynamite/drawn/id-card.svg");
  --obj-id-card-aspect: 85 / 54;

  --obj-library-card-drawn: url("/dynamite/drawn/library-card.svg");
  /* library-card.svg is 300x480 — PORTRAIT. This said 5 / 3 and every other
     object here matches its own artwork, so the box was landscape while the
     drawing letterboxed itself to a narrow strip inside it. The card's text is
     sized against the BOX, so "I design future antiques" was laid out 237px
     wide across a card only 120px wide and spilled onto the bare ground on
     both sides. An aspect-ratio that disagrees with its artwork does not
     misplace the picture — background-size: contain still fits it — it
     misplaces everything positioned against the element. */
  --obj-library-card-aspect: 5 / 8;

  --obj-trapper-drawn: url("/dynamite/drawn/trapper.svg");
  --obj-trapper-aspect: 4 / 3;

  --obj-name-tag-drawn: url("/dynamite/drawn/name-tag.svg");
  --obj-name-tag-aspect: 3 / 2;

  --obj-hall-pass-drawn: url("/dynamite/drawn/hall-pass.svg");
  --obj-hall-pass-aspect: 5 / 2;

  /* No --obj-hand-drawn. The drawn hand was deleted: a flat vector hand has
     no fingernails, knuckle shadows or skin, so it never read as a hand
     entering frame. Until a photograph exists, the objects carry the gesture
     themselves by rising into shot. Uncomment --obj-hand below and the hand
     comes back with no code change. */
  --obj-hand-aspect: 2 / 3;

  /* ---- PHOTOGRAPHS ------------------------------------------------------
     Uncomment a line as each shot lands in public/dynamite/.
     Filenames match the token: --obj-plate -> /dynamite/plate.webp

  --surface-cloth: url("/dynamite/cloth.webp");
  --surface-cloth-size: 900px;
  --obj-plate: url("/dynamite/plate.webp");
  --obj-id-card: url("/dynamite/id-card.webp");
  --obj-library-card: url("/dynamite/library-card.webp");
  --obj-trapper: url("/dynamite/trapper.webp");
  --obj-name-tag: url("/dynamite/name-tag.webp");
  --obj-hall-pass: url("/dynamite/hall-pass.webp");
  --obj-hand: url("/dynamite/hand.webp");
  ---------------------------------------------------------------------- */
}


/* ============================================================================
   LIGHT TEXTURES — the low-bandwidth fallback.

   Ryan: "those other ones can be a fallback if load speed drops below a
   certain something? or i dunno? offer a light version somehow in the labels?"

   Both. The grounds are whole 1920x1040 photographs, which is a few hundred KB
   each. On a metered or slow connection that is the wrong trade, so the boot
   script in index.html sets data-textures="light" when the browser says so —
   navigator.connection.saveData, or an effectiveType of 2g/3g — and the footer
   toggle lets anyone choose it deliberately. A stored choice always wins over
   the sniff, because someone who has picked has picked.

   The light set is the GENERATED tiles from scripts/make-surfaces.py: no film
   pixels, a fraction of the weight, and they tile, so a small image still
   covers any viewport. Roughly 14-77KB each against a few hundred.

   ⚠ Specificity: :root[data-theme="dynamite"][data-textures="light"] is
   (0,3,0), which beats the (0,2,0) block above it regardless of source order.
   Do not "simplify" this to a single attribute selector.
   ========================================================================== */
:root[data-theme="dynamite"][data-textures="light"] {
  /* Pairs with the photographed ground above: same surface, tiled. If you swap
     --surface-cloth up there, swap this to the matching -light file or the two
     variants stop being the same room.

     ⚠ One pair is not the same room: berber-carpet-light is the BLUE carpet.
     make-surfaces.py writes it that way on purpose — "the real berber could
     not be synthesised" — which was a fair stand-in when berber-carpet was
     itself a generated tile, and is a visible lie now that it is a photograph
     of a striped loop pile. Do not pair those two without looking.

     Weights, full -> light: wood-panel 205->14KB, maroon-shag 179->32,
     olive-lino 46->8, desk-formica 97->38, cafeteria-lino 62->30,
     berber-carpet 179->69. Every one is a real saving now; before the grounds
     were regenerated from frames, four of them were not. */
  --surface-cloth: url("/dynamite/surfaces/olive-lino-light.webp");
  --surface-cloth-size: 512px;
  --surface-cloth-repeat: repeat;

  --wall: url("/dynamite/surfaces/wood-panel-light.webp");
  --wall-size: 512px 512px;
  --wall-repeat: repeat;
  /* Not fixed: a repeating tile scrolling with the page is cheaper to paint
     and looks identical, and fixed backgrounds are a known jank source on
     mobile — which is exactly who this variant is for. */
  --wall-attachment: scroll;
}
