/* ss-grit.css — THE GRIT LAYER: photocopier, not gradient.
 *
 * The owner, 2026-09-30: "make it visually appealing and grittier, underground." And earlier,
 * on the poster comps: "I need mine to look less polished and more underground."
 *
 * THIS IS NOT A NEW LOOK. It is canon's own vocabulary applied to the one surface that never
 * got it. brand.json § positions.rule: "the [S]² mark, the orange, the halftone and the mono
 * type are what stay fixed across all of them" — halftone is already a FIXED brand element
 * across every printed and posted position, and the website was the position rendering none
 * of it. § visual_systems.sandbox.texture names the target exactly: "Industrial print shop —
 * heavy halftone dots, misaligned stamps, photocopier grain, exposed wires. Looks like a work
 * ticket / shipping manifest."
 *
 * ── THE BAR EVERY RULE BELOW HAS TO CLEAR, and it is canon's, not taste ──────────────────
 * § design_rules.imperfection_standard: "The standard is AUTHORED imperfection, not physical
 * authenticity… An imperfection is acceptable when it appears intentional at final output
 * size, its placement supports the composition, it relates to the mark/type/grid, it does NOT
 * resemble an export failure or broken image, and it preserves the information the asset must
 * communicate. Keep essential event information registered and legible; halftone stays
 * selective rather than blanketing a face. If it doesn't look deliberate, don't use it."
 *
 * Four consequences, and they are why this file is short:
 *   1. RELATES TO THE GRID. The dot pitch is 4px and the scan pitch 3px — both multiples of
 *      the 4px rhythm the pages are already set on, so the field lines up with the type
 *      instead of moiréing against it.
 *   2. SELECTIVE, NEVER BLANKET. Every texture here is opt-in per element. There is no
 *      `body { background-image: … }` and there must not be: a field under the whole page is
 *      the blanket canon forbids, and it is also the thing that reads as a broken render.
 *   3. NEVER OVER INFORMATION. The halftone goes on PLATES AND BANDS — the panel a poster is
 *      pinned to, the strip a count sits in — never over a photograph, a face, or body copy.
 *      A poster's own words are set into the art; a field over it cuts them.
 *   4. LEGIBLE IS NOT NEGOTIABLE. The fields run at 4–6% ink. Measured against `--panel`
 *      (a near-black one step off the ground) that is a texture you can see and not one
 *      that moves any text contrast across a WCAG boundary — the site just spent a round
 *      pulling three spans off a 2.48:1 token, and a decorative layer must not give that
 *      back.
 *
 * ── AND WHERE IT DELIBERATELY DOES NOT GO ───────────────────────────────────────────────
 * /checkout. Grit on the form that takes a card is not underground, it is a trust cost on the
 * one page where trust is the product. The buyer path is allowed to look like a flyer right
 * up to the moment it asks for money, and then it looks like a receipt. That is a decision,
 * recorded here so the next session does not "finish the job" by texturing it.
 *
 * ── NO JS, NO IMAGES, NO DEPS ───────────────────────────────────────────────────────────
 * A stylesheet in `<head>` holds before first paint, which is the same reason the front page's
 * own layout is media queries and not JS ("a layout that snaps into place after hydration is
 * the flash this page spent effort removing"). Every texture is a CSS gradient: zero bytes of
 * image, nothing to 404, nothing to cache-bust. Every colour goes through a token via
 * `color-mix`, so a tenant that repaints `--ink` or `--house` repaints the grit with it —
 * the miss `check:tokens` exists to catch.
 * ─────────────────────────────────────────────────────────────────────────────────────── */

/* ══ THE HALFTONE PLATE ═══════════════════════════════════════════════════════════════════
   A dot field on a 4px grid, two passes offset by half a pitch so the dots sit in a diagonal
   screen rather than a square lattice — a square lattice reads as a CSS demo, a diagonal one
   reads as a print screen, and that is the whole difference between deliberate and broken.
   `background-image` and not a pseudo-element on purpose: a ::before needs position, inset and
   a z-index on the host, and three of the hosts here are grid children whose stacking the
   pages already reason about. A background layer needs nothing from the host at all. */
.grit-halftone {
  background-image:
    radial-gradient(color-mix(in srgb, var(--ink) 6%, transparent) 0.5px, transparent 0.5px),
    radial-gradient(color-mix(in srgb, var(--ink) 4%, transparent) 0.5px, transparent 0.5px);
  background-size: 4px 4px, 4px 4px;
  background-position: 0 0, 2px 2px;
}
/* THE ORANGE PLATE — the same screen in the house colour, for the one or two surfaces that
   are already the accent's. Warmer and slightly hotter than the ink field because a coloured
   plate on a photocopier lays down heavier; 8% of `--house` on `--panel` is a field you read
   as orange without it becoming a tint. */
.grit-halftone-house {
  background-image:
    radial-gradient(color-mix(in srgb, var(--house) 8%, transparent) 0.5px, transparent 0.5px),
    radial-gradient(color-mix(in srgb, var(--house) 5%, transparent) 0.5px, transparent 0.5px);
  background-size: 4px 4px, 4px 4px;
  background-position: 0 0, 2px 2px;
}

/* ══ PHOTOCOPIER SCAN ═════════════════════════════════════════════════════════════════════
   Horizontal banding at a 3px pitch — the drum stripe on a copier that needs servicing. It is
   for LARGE grounds, where a dot field at 4px would tile visibly across a whole column; a
   stripe reads as a machine artefact at any width. 3.5% because a horizontal stripe is far
   more visible to the eye than an equally-inked dot: same ink, louder pattern. */
.grit-scan {
  background-image: repeating-linear-gradient(
    180deg,
    color-mix(in srgb, var(--ink) 3.5%, transparent) 0 1px,
    transparent 1px 3px);
}

/* ══ THE RULE THAT WENT THROUGH A COPIER ══════════════════════════════════════════════════
   A hairline is a design decision; a broken line is a photocopy. `--rule` at 1px is what every
   divider on the site is, and it is correct for the Console. On a flyer the line is dotted
   because the toner did not take.
   IT IS A BORDER-IMAGE, NOT A BACKGROUND, so it drops into any `border-top`/`border-bottom`
   the pages already declare without changing the box: same 1px, same place in the model, no
   height moves. The mobile ratchet measures height in screens and would see a background
   strip as growth; it cannot see this at all, because there is nothing to see.

   40% INK ON A 2/4 PITCH, AND THE FIRST CUT WAS FULL INK ON 3/5, WHICH WAS WRONG ON THE
   PAGE RATHER THAN IN THEORY. Rendered, the heavy dash read as a tear-off perforation and
   was the LOUDEST thing on the front page — louder than the filled GET TICKETS strip, which
   inverts the one hierarchy that page has spent three rounds establishing. A divider that
   out-shouts the buy button is not grit, it is a mistake with texture on it. The finer,
   quieter dash still reads as toner that did not take, and it is still four times more
   present than the `--rule` hairline it replaces, which was the point. */
.grit-rule {
  border-style: solid;
  border-image: repeating-linear-gradient(
    90deg,
    color-mix(in srgb, var(--ink) 40%, transparent) 0 2px,
    transparent 2px 4px) 1;
}
/* The vertical twin, for a column divider. Same pitch, rotated — spelled out rather than
   derived because `border-image` has no per-edge form and a single rule would dot all four. */
.grit-rule-v {
  border-style: solid;
  border-image: repeating-linear-gradient(
    180deg,
    color-mix(in srgb, var(--ink) 40%, transparent) 0 2px,
    transparent 2px 4px) 1;
}

/* ══ THE STAMP ════════════════════════════════════════════════════════════════════════════
   A section kicker is 11px letterspaced text on every page here, which is a caption. A stamp
   is a BOX: hairline, tight padding, and set down a degree off square, because the thing it
   imitates was pressed by hand. § sandbox.texture's "misaligned stamps", literally.
   `transform: rotate()` and not a margin trick: a transform does not reflow, so a crooked
   label cannot push a tap target, wrap a line, or move a single number the height ratchets
   read. It is also why the rotation is under a degree — at 1.5° the box's corners start
   colliding with the line above it at narrow widths, and a label that overlaps its neighbour
   stopped being deliberate.
   NON-INTERACTIVE LABELS ONLY. A rotated hit area is a hit area somebody misses; the buttons
   and the doors on these pages fought for their 44px and do not get this.

   IT COSTS NO HEIGHT, AND THAT IS ENGINEERED RATHER THAN LUCKY. An 11px kicker in the
   pages' 1.6 body leading already occupies a ~17.6px line box, most of which is air above
   and below the caps. The stamp sets `line-height: 1` and spends that air on 2px of padding
   and a 1px rule instead: 11 + 4 + 2 = 17px, marginally SHORTER than the caption it
   replaces. `mobile-buy`'s height ratchet is 0.0074 screens (about 6px) from its ceiling on
   `/` and there are four of these on that page — at the obvious `padding: 3px 7px` they
   would have cost 32px and the guard would have caught it, correctly. A decorative layer
   does not get to spend a budget the nights need.

   `display: block; width: fit-content` AND NOT `inline-block`, WHICH IS THE WHOLE REASON
   THE ARITHMETIC ABOVE IS TRUE IN THE BROWSER AND NOT ONLY ON PAPER. An inline-block sits
   on a text baseline, so its parent still lays out a line box at the PARENT's font-size and
   leading — 14px x 1.6 = 22.4px here — and the 17px stamp inside it changes nothing. That
   is exactly what happened: the first cut measured `/` at 1.8827 against 1.87 and the
   standing-runs fixture at 2.1848 against 2.16, about 22px of pure strut across four
   labels. A block child's height IS its parent's height, and `fit-content` keeps the box
   shrink-wrapped to the words, which is what made it look like a stamp in the first place.
   The budgets were left where they are. A decorative layer that cannot fit in the space the
   thing it decorates already occupied is a layer that should be cheaper, not a budget that
   should be looser — and this one could be.

   ALL-CAPS LABELS ONLY, for the same reason as the leading: at `line-height: 1` a descender
   clips. Every kicker on these pages is caps already; anything with a lowercase g does not
   get this. */
.grit-stamp {
  display: block;
  width: fit-content;
  padding: 2px 7px;
  line-height: 1;
  border: 1px solid color-mix(in srgb, currentColor 45%, transparent);
  transform: rotate(-0.8deg);
  transform-origin: left center;
}
/* The mirror, so two stamps on one page do not lean the same way — a repeated identical tilt
   is a style, and a style reads as polish. This is the one place where the second variant is
   the point rather than a duplicate. */
.grit-stamp-r { transform: rotate(0.7deg); transform-origin: right center; }

/* ══ MISREGISTRATION ══════════════════════════════════════════════════════════════════════
   The second plate landed 2px off. On display type this is the single most underground move
   available in pure CSS, and it is also the easiest one to ruin, so it is fenced three ways:

   1. DISPLAY SIZES ONLY, ≥560px. Below that the front page's descriptor is 15–18px and the
      proof count 11px, and a 2px offset on 15px type is not misregistration, it is blur —
      which is canon's "resembles an export failure". The 560px gate is the same one the
      pages' own desktop layer uses, so the grit arrives exactly where the type got big
      enough to carry it.
   2. ONE PLATE, HOUSE ORANGE, AT 55%. A full-strength second copy is a drop shadow. A
      partial one in the accent is a plate that did not register — and it puts the orange on
      the page at a size somebody can see, which the front page's own stylesheet already
      complains about ("the one accent never appeared at a size anybody could see").
   3. NEVER ON BODY COPY, NEVER ON A LINK. A paragraph with a coloured ghost is unreadable and
      a link with one looks broken. Headlines, counts and the wordmark; nothing that wraps to
      a third line.

   THE INK IS OFFSET, THE TYPE IS NOT: `text-shadow` paints behind the glyph and moves
   nothing, so the layout, the line box and the measured height are identical with this class
   and without it. */
@media (min-width: 560px) {
  .grit-misreg {
    text-shadow: 2px 0 0 color-mix(in srgb, var(--house) 55%, transparent);
  }
  /* The heavier setting, for the one biggest thing on a page: two plates off in opposite
     directions, the second cool so the pair reads as CMYK rather than as a coloured shadow.
     `--venue` is the cool token the palette already owns; it is used here as INK, not as
     "the room", which is why this is a grit class and not a venue one. */
  .grit-misreg-2 {
    text-shadow: 2px 0 0 color-mix(in srgb, var(--house) 55%, transparent),
                 -2px 0 0 color-mix(in srgb, var(--venue) 30%, transparent);
  }
}

/* ══ WHERE ALL OF IT TURNS OFF, and each switch is a different person ══════════════════════
   PAPER. A dot field is toner somebody pays for and a scan stripe on a printed page is just
   dirt. ss-theme.js already flips the tokens for print; this turns the decoration off
   entirely, which is the honest thing to do to a page that has become a document. The rules
   go back to hairlines so a printed divider is a divider.
   MORE CONTRAST. Somebody who asked the OS for more contrast asked for less texture, and a
   misregistered plate is the exact effect they turned on the setting to escape.
   FORCED COLORS. A high-contrast mode repaints backgrounds and ignores `text-shadow`;
   leaving the gradients declared means a system background fighting a gradient it did not
   choose. Off is the whole answer.
   NO REDUCED-MOTION BLOCK, deliberately: nothing in this file animates, transitions or
   transforms on hover. The one `rotate()` is static. There is nothing for that query to
   turn off, and a block that silently does nothing is worse than none. */
@media print {
  .grit-halftone, .grit-halftone-house, .grit-scan, .grit-ticks { background-image: none; }
  /* A filled chip is a solid orange block on paper. Outline it instead: same object, no ink. */
  .grit-chip { background-color: transparent; color: var(--ink); border: 1px solid var(--ink); }
  .grit-rule, .grit-rule-v { border-image: none; border-color: var(--rule); }
  .grit-misreg, .grit-misreg-2 { text-shadow: none; }
  .grit-stamp, .grit-stamp-r { transform: none; }
}
@media (prefers-contrast: more) {
  .grit-halftone, .grit-halftone-house, .grit-scan { background-image: none; }
  .grit-chip, .grit-ticks, .grit-stamp, .grit-stamp-r { transform: none; }
  .grit-misreg, .grit-misreg-2 { text-shadow: none; }
}
@media (forced-colors: active) {
  .grit-halftone, .grit-halftone-house, .grit-scan, .grit-ticks { background-image: none; }
  .grit-rule, .grit-rule-v { border-image: none; }
  .grit-misreg, .grit-misreg-2 { text-shadow: none; }
}

/* ══ THE FILLED CHIP ══════════════════════════════════════════════════════════════════════
   The stamp's opposite number: a solid house-orange rectangle with the ground knocked out of
   it. The direction brief's comps use both and use them for different jobs — an outlined
   `// KICKER` introduces a section, a FILLED chip names an OBJECT inside one (LINEUP, ROOM
   RECORD, HOW THE NIGHT WORKS). Keeping that distinction is what stops the page becoming a
   column of identical labels, which is what one treatment applied everywhere always becomes.

   IT IS ALSO THE BRIEF'S ONE COLOUR RULE HONOURED: "orange used for action and signal, not
   decoration." A filled chip is the loudest thing this palette can do, so it is reserved for
   naming a thing a person is about to read or act on, never for atmosphere. The site carries
   exactly one filled-orange control already — the GET TICKETS strip — and a chip must never
   be mistaken for it, which is why it is small, why it never sits inside a card's action row,
   and why it is not a link.

   `background-color`, NOT the shorthand, so a halftone plate can ride under it if a caller
   wants one — the same trap `.home-poster > span` fell into. Same height arithmetic as the
   stamp and the same all-caps rule. */
.grit-chip {
  display: block;
  width: fit-content;
  padding: 3px 8px;
  line-height: 1;
  background-color: var(--house);
  color: var(--ground);
  font-weight: 700;
  transform: rotate(-0.6deg);
  transform-origin: left center;
}

/* ══ CORNER TICKS ═════════════════════════════════════════════════════════════════════════
   Crop marks at a panel's corners — the registration marks on a press sheet, and the brief's
   "hard grid system with thin borders" read as a frame rather than a box. Four gradients in
   one `background-image` and no pseudo-elements, because the hosts here are grid and flex
   children whose stacking the pages already reason about; a background layer asks nothing of
   the host. 10px arms at 1px: long enough to read as a mark, short enough that it never
   closes into a border somebody thinks they can style.
   `background-repeat: no-repeat` on all four is load-bearing — without it each gradient tiles
   and the panel gets a grid of ticks instead of four corners, which is the difference between
   a press sheet and graph paper. */
.grit-ticks {
  background-image:
    linear-gradient(to right, var(--house) 0 10px, transparent 10px),
    linear-gradient(to bottom, var(--house) 0 10px, transparent 10px),
    linear-gradient(to left, var(--house) 0 10px, transparent 10px),
    linear-gradient(to top, var(--house) 0 10px, transparent 10px);
  background-size: 100% 1px, 1px 100%, 100% 1px, 1px 100%;
  background-position: left top, left top, right bottom, right bottom;
  background-repeat: no-repeat;
}
