/* MODULE: css/wear.css — the wear / patina system (PHASE3 §4).
   Depends on css/tokens.css (the neutral ramp and the two themes) and on
   css/dial.css §10, which publishes the wear hook this file lands in. Reads
   css/materials.css's --m-tex-* when that sheet is present, with a literal
   fallback for each, so load order can never break it. Pairs with src/wear.js,
   which owns the DOM and the arithmetic. Loads nothing itself — no @import, no
   fonts, no network.

   ── WHO OWNS WHAT ─────────────────────────────────────────────────────
   css/dial.css §10 already implements two of PHASE3 §4's six effects, because
   they live inside the SVG where an outside stylesheet could only reach them by
   hard-coding that file's private class names. It reads ONE property:

       --dial-wear   0…1, set on .dial by src/wear.js

     item 3  knob polish   .dial__knobSpec  stroke-opacity 0.82 → 1.0
     item 4  bezel wear    .dial__bezelSpec opacity        1.0  → 0.82

   THIS file owns the other four — the matte and surface work:

     item 1  crystal micro-scratches, and a rotated second instance past w > 0.5
     item 2  the sweep ghost at the disc edge's own radius
     item 5  the ceramic patina on the body
     item 6  dust and smudge, in the dial well only

   src/dial.js gives us the surface to put them on: an empty, inert
   `<div class="dial__wear">` covering the instrument, and the radii it is drawn
   from as custom properties on .dial (--dial-r-disc and friends, as percentages
   of the dial box's RADIUS). Using them is what stops these two files drifting
   apart when a radius moves.

   ── WHAT THIS FILE IS ALLOWED TO TOUCH ────────────────────────────────
   The hard limit from PHASE3 §4 is absolute: TYPE NEVER WEARS. Not the numerals
   on the dial, not a tick label, not a control label. So the texture stack is
   CONFINED BY GEOMETRY rather than by measurement — it fades to nothing at 66%
   of the dial's radius, and the innermost pixel of the nearest numeral is at
   68.69%. There is no wear over type because the wear cannot physically reach
   it. Derivation in §1.

   Everything else the wear layer touches carries no type at all: the red disc,
   the empty face inside the track, the knob's orbit, and the page's body
   colour. The one token this file redefines is --bg, which the whole app uses
   as a BACKGROUND and never as a foreground (verified: every use in the repo is
   a `background-color` or an --m-base).

   MEASURED, at maximum patina, in a browser, from resolved colours — not
   calculated from the token values and not assumed:

     CERAMIC   --bg  #f2f0eb → #f2efe9, relative luminance −0.805%
               --ink         15.43:1 → 15.31:1
               --ink-2        7.72:1 →  7.66:1
               --ink-3        5.65:1 →  5.60:1   ← the worst pair. AA needs 4.5
               --signal-text  5.47:1 →  5.43:1
               --accent-cool  5.28:1 →  5.24:1
     GRAPHITE  --bg  #0e0e10 → #0e0e10, no change at 8-bit
               every ratio unchanged (--ink 15.46, --ink-2 7.57, --ink-3 5.59)

   Graphite's body sits within a whisker of the floor, so there is no luminance
   left there to give: a 1.4% mix toward a warm near-black quantises back to the
   same triple and the night patina does not render at all. That is the honest
   outcome of PHASE3's own 2% budget rather than a shortfall — a case yellows
   where light falls on it — and the object still visibly ages at night through
   the crystal, the ghost, the grime and the knob, none of which are themed.
   (css/materials.css records the same caveat about its ceramic tile at night.)

   Verified by rendering too, at 2× on a frozen snapshot: the four matte layers
   change ZERO pixels at or beyond 66% of the dial's radius, so no numeral, tick
   or label is touched by so much as one 8-bit level. And the patina reaches
   exactly one resolved colour — the body background. .dial's background, the
   face plate's fill, every ink and every tick stroke are byte-identical before
   and after.

   ── HOW THE MATTES COMPOSITE ──────────────────────────────────────────
   scratches.jpg / dust.jpg / smudge.jpg are white-on-black with NO alpha
   channel, so they are screen-blended: screen(backdrop, black) is exactly the
   backdrop, which is why the black drops out and only the marks land. They are
   NOT tileable — single scaled instance, never repeated.

   That gives .dial__wear a requirement most overlays do not have: the group the
   mattes blend INSIDE has to be black, because black is the identity element of
   `screen`. --wear-void and --wear-mark below are that identity and its unity —
   a monochrome intensity channel for a blend operator, in the same sense that
   css/materials.css's mask gradients are "an alpha channel, not a colour".
   Nothing in this file introduces a hue; the only chromatic value here is
   --wear-warm, and it exists solely to be mixed at 1.4%.

   NOTE FOR THE DIAL AGENT: css/dial.css §10 suggests hanging the mattes
   straight in .dial__wear so each one screens onto the artwork itself, and
   deliberately leaves that box without a stacking context. This file instead
   makes .dial__wear an isolated group over black and screens the GROUP onto the
   artwork. The result is pixel-identical — screen is associative, so
   screen(dial, screen(A,B)) is screen(screen(dial,A),B) — but the arithmetic
   moves: four blends against live, per-frame artwork become one, and the inner
   group is static and therefore cacheable. That is PHASE3 §4's "the whole
   system must cost ONE composited layer" taken literally. The isolation is
   between the mattes and each OTHER, never between the group and the dial;
   .dial's own `isolation: isolate` still stops the composite at the instrument,
   and .dial::after still paints its recess walls above all of it.

   Per-layer strength is the element's own `opacity`, which over a black
   backdrop is exactly a scale factor on the mark: screen(black, m·α) = m·α.
   Per-layer SHAPE is a greyscale gradient multiplied into the layer's own
   background stack — multiply by white keeps the mark, multiply by black
   removes it — so the confinement costs no mask, no filter and no extra
   element. */

/* ============================================================== *
 * 1. LOCAL TOKENS
 *
 *    GEOMETRY. src/dial.js publishes --dial-r-disc / -face / -orbit /
 *    -bezel / -knob on .dial as percentages of the dial box's RADIUS, so
 *    the sweep ghost below is placed on a real feature of the instrument
 *    rather than on a number copied out of another file. Every one is
 *    consumed with the literal it currently resolves to as a fallback, so
 *    this sheet is still correct in front of a dial that predates the hook.
 *
 *    The two radii dial.css does NOT publish are re-derived here from the
 *    same constants, once, and this is the working:
 *      css/dial.css insets .dial__svg by 3.4% at 93.2% size and the SVG's
 *      viewBox is 0 0 200 200, so a viewBox radius R lands at (0.932 × R)%
 *      of the dial's radius.
 *
 *        R_NUM 77.6 → numeral centres at 72.32%
 *        .dial__num is 8px in a 200-unit viewBox with dy 0.33em, so a
 *        glyph reaches inward to about 73.7 units → 68.69%   ← THE CEILING
 *        R_TICK_MIN 87.6 → the printed scale at 81.64%, well outside
 *
 *      Every falloff below stops at or under 66%, leaving 2.7 points of
 *      clear air under the lowest glyph — about 4.5px on a 336px dial.
 * ============================================================== */

:root {
  /* The blend channel. NOT palette colours — see the header. */
  --wear-void: #000; /* screen identity: composites to nothing at all */
  --wear-mark: #fff; /* full-strength mark */
  --wear-mark-72: #b8b8b8;
  --wear-mark-44: #707070;
  --wear-mark-30: #4d4d4d;
  --wear-mark-26: #424242;

  /* Confinement, per §1 above. */
  --wear-r-crystal-in: 46%; /* full strength out to here… */
  --wear-r-crystal-out: 66%; /* …gone by here. Numerals start at 68.69%. */
  --wear-r-grime-in: 34%; /* the well floor, where a real object collects it */
  --wear-r-grime-out: 63%;
  --wear-r-sweep-in: 57.5%;
  --wear-r-sweep-out: 66%;

  /* The second scratch instance is rotated so DENSITY grows past w > 0.5,
     not merely contrast. An odd angle, so no hairline lands back on itself. */
  --wear-crystal-2-rot: -31deg;

  /* ---- the live values -------------------------------------------------
     src/wear.js writes these onto <html> when w crosses a 0.02 threshold,
     and never at any other time. (--dial-wear, which css/dial.css §10 reads
     for the knob and the bezel, is written on each .dial element instead,
     because dial.css declares its own default there.) The values below are
     the FACTORY-FRESH object: with wear.js absent, or disabled, or at w = 0,
     every rule in this file resolves to a no-op and the app renders exactly
     as it does without the sheet. */
  --wear-crystal: 0; /* → 0.13  crystal micro-scratches */
  --wear-crystal-2: 0; /* → 0.085 the rotated second instance, past w > 0.5 */
  --wear-sweep: 0; /* → 0.06  the disc edge's ghost ring */
  --wear-grime: 0; /* → 0.05  dust + smudge, in the well only */
  --wear-patina-mix: 100%; /* → 98.6% of the fresh body colour */
}

/* The 1.4% mix partner for the ceramic patina. Cascade mirrors css/tokens.css
   exactly: ceramic default → dark media query on a root that has not opted in →
   explicit graphite last.

   Ceramic gets a warm ochre, so the body DARKENS and reddens as it ages:
   measured at the 1.4% cap, hue −2.7° and relative luminance −1.3%, both inside
   PHASE3's 3° / 2% budget. It moves toward red, never toward yellow.

   Graphite gets a near-black warm instead. At #0e0e10 there is no luminance
   left to give, so the night patina is a hue shift and nothing else. */
:root,
[data-theme="ceramic"] {
  --wear-warm: #c98a5a;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="ceramic"]):not([data-theme="graphite"]) {
    --wear-warm: #1a1310;
  }
}

[data-theme="graphite"] {
  --wear-warm: #1a1310;
}

/* ============================================================== *
 * 2. THE OVERLAY
 *
 *    ONE composited layer over the dial. src/dial.js already appends the
 *    box — `<div class="dial__wear" aria-hidden="true">`, the last child of
 *    .dial — and css/dial.css positions it. All this file adds is the black
 *    identity backdrop, the isolation that makes the mattes combine inside
 *    it, and the one screen blend that lands the result on the artwork.
 *
 *    .tt-wear is the fallback container src/wear.js builds only when it
 *    meets a dial older than the hook. It carries the positioning too so
 *    that case is not left unstyled.
 *
 *    Everything here is aria-hidden and pointer-events: none, root and
 *    children, and none of it is ever written to per frame: src/dial.js's
 *    render path touches two `d` attributes and two transforms and reads
 *    nothing from this file at all.
 * ============================================================== */

.dial .tt-wear {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  overflow: hidden;
  pointer-events: none;
}

/* data-wear on <html>, written by src/wear.js:
     absent    the patina is off, or the script never ran
     "ready"   on, but nothing earned yet — under about ten minutes of tracked
               focus, where the curve still quantises to 0
     "on"      on, with marks to show

   The blend is only paid for in the last of those three. A factory-fresh object
   therefore costs exactly nothing: .dial__wear stays what css/dial.css made it,
   an empty, inert, uncomposited box, and the dial's frame budget is untouched
   for a user who has not yet finished a session.
   PHASE3 §4 — "Off removes every wear layer instantly." */
:root[data-wear="on"] .dial .dial__wear,
:root[data-wear="on"] .dial .tt-wear {
  background-color: var(--wear-void);
  isolation: isolate;
  mix-blend-mode: screen;
}

:root:not([data-wear="on"]) .dial .dial__wear > *,
:root:not([data-wear="on"]) .dial .tt-wear > * {
  display: none;
}

.dial .dial__wear > *,
.dial .tt-wear > * {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-color: var(--wear-void);
  background-repeat: no-repeat;
  background-position: 50% 50%;
  mix-blend-mode: screen;

  /* A polish is a physical event the user asked for, so it is confirmed rather
     than jump-cut. Nothing here ever animates on its own — the value only moves
     when a session completes or Restore is pressed. */
  transition: opacity var(--dur-slow) var(--ease-out);
}

/* --- 1. CRYSTAL MICRO-SCRATCHES ---------------------------------------
   The first instance. `cover` because the matte is not tileable; the radial
   layer is multiplied into it to confine the marks to the middle of the
   crystal, where a real watch is actually touched — and, not incidentally,
   where there is no type. */
.dial .tt-wear__crystal {
  background-image:
    radial-gradient(
      circle closest-side at 50% 50%,
      var(--wear-mark) 0 var(--wear-r-crystal-in),
      var(--wear-void) var(--wear-r-crystal-out)
    ),
    var(--m-tex-scratches, url("../assets/textures/scratches.jpg"));
  background-size: 100% 100%, cover;
  background-blend-mode: multiply, normal;
  opacity: var(--wear-crystal);
}

/* The second instance, rotated and slightly enlarged so its hairlines fall
   somewhere else entirely. It fades in past w > 0.5, which is what makes the
   crystal get BUSIER rather than merely darker. */
.dial .tt-wear__crystal-2 {
  background-image:
    radial-gradient(
      circle closest-side at 50% 50%,
      var(--wear-mark) 0 var(--wear-r-crystal-in),
      var(--wear-void) var(--wear-r-crystal-out)
    ),
    var(--m-tex-scratches, url("../assets/textures/scratches.jpg"));
  background-size: 100% 100%, cover;
  background-blend-mode: multiply, normal;
  transform: rotate(var(--wear-crystal-2-rot)) scale(1.08);
  opacity: var(--wear-crystal-2);
}

/* --- 2. THE SWEEP GHOST ------------------------------------------------
   A ring at exactly the radius this object's disc edge travels — read from
   src/dial.js's own --dial-r-disc, so it is a record of THIS instrument's use
   and could not have come from anywhere else.

   The conic layer is the honest part: every session sweeps the first few
   degrees past 12 o'clock, and only the long ones ever reach the far side of
   the face, so the ghost is brightest at the start line and decays clockwise.
   Zero bytes — two gradients, no asset. */
.dial .tt-wear__sweep {
  background-image:
    conic-gradient(
      from 0deg at 50% 50%,
      var(--wear-mark) 0deg,
      var(--wear-mark-72) 70deg,
      var(--wear-mark-44) 165deg,
      var(--wear-mark-30) 260deg,
      var(--wear-mark-26) 352deg,
      var(--wear-mark) 360deg
    ),
    radial-gradient(
      circle closest-side at 50% 50%,
      var(--wear-void) 0 var(--wear-r-sweep-in),
      var(--wear-mark) var(--dial-r-disc, 62.44%),
      var(--wear-void) var(--wear-r-sweep-out)
    );
  background-size: 100% 100%;
  background-blend-mode: multiply, normal;
  opacity: var(--wear-sweep);
}

/* --- 6. DUST AND SMUDGE ------------------------------------------------
   Only in the dial well, which is where a real object collects it — and the
   well is also where the knob orbits (--dial-r-orbit, 60.76%), so the grime
   lands on the knob too, on the crystal above it, exactly as it would. Two
   mattes, screened together first, then confined by the same multiply trick. */
.dial .tt-wear__grime {
  background-image:
    radial-gradient(
      circle closest-side at 50% 50%,
      var(--wear-mark) 0 var(--wear-r-grime-in),
      var(--wear-void) var(--wear-r-grime-out)
    ),
    var(--m-tex-dust, url("../assets/textures/dust.jpg")),
    var(--m-tex-smudge, url("../assets/textures/smudge.jpg"));
  background-size: 100% 100%, cover, cover;
  background-blend-mode: multiply, screen, normal;
  opacity: var(--wear-grime);
}

/* ============================================================== *
 * 3. ITEMS 3 AND 4 — knob polish and bezel wear
 *
 *    Implemented in css/dial.css §7 and §4 respectively, off the single
 *    --dial-wear property src/wear.js writes on each .dial element. They are
 *    listed here only so this file is a complete account of PHASE3 §4:
 *
 *      .dial__knobSpec  stroke-opacity  calc(0.82 + 0.18 * var(--dial-wear))
 *      .dial__bezelSpec opacity         calc(1 - 0.18 * var(--dial-wear))
 *
 *    Deliberately NOT restated here. Two files setting the same property on
 *    the same element is how a specificity fight starts, and the dial agent's
 *    version reaches inside the SVG without anybody hard-coding anybody
 *    else's private class names. If those hooks are ever removed, the wear
 *    curve is already on the element and the two rules above are the whole
 *    of what would need to move into this file.
 * ============================================================== */

/* ============================================================== *
 * 4. CERAMIC PATINA
 *
 *    The body warms. At the 1.4% cap that is hue −2.7° and luminance −1.3%
 *    in ceramic — under PHASE3's 3° / 2% budget, and short of the
 *    quantisation of an 8-bit near-white, which is the honest reason it is
 *    "barely nameable": it is smaller than one step of the display's own
 *    grid and only reads as a shift because it moves everything at once.
 *
 *    --bg and NOTHING ELSE. Panels (--surface), raised control faces
 *    (--bg-raised) and the wells (--bg-sunk) do not age: a case yellows
 *    where it is handled and lit, not inside its own recesses. Every ink
 *    token is untouched, which is half of why the contrast table in the
 *    header barely moves.
 *
 *    Written against --ramp-150 rather than --bg because a custom property
 *    cannot reference itself. That couples this rule to css/tokens.css §3's
 *    `--bg: var(--ramp-150)` mapping — if that mapping is ever re-pointed,
 *    re-point this with it.
 * ============================================================== */

:root[data-wear="on"] {
  --bg: color-mix(
    in oklab,
    var(--ramp-150) var(--wear-patina-mix),
    var(--wear-warm)
  );
}

/* ============================================================== *
 * 5. THE SETTINGS SURFACE
 *
 *    src/wear.js builds these rows and mounts them into Settings →
 *    Appearance. They are deliberately assembled from css/settings.css's own
 *    primitives — .set-row, .switch, .set-actions, .set-confirm — so they are
 *    that panel's rows in every respect and inherit its spacing, its focus
 *    rings and its press physics for free. The only thing this section adds
 *    is tabular figures on the one number it prints, because PHASE4 §3 is not
 *    negotiable about that either.
 * ============================================================== */

/* The wrapper exists only so src/wear.js can find and remove its own rows
   again. `display: contents` hands its children straight to .set-section's
   flex column, so they take that panel's gap and rhythm rather than
   introducing a nested box of their own. */
.tt-wear-set {
  display: contents;
}

/* The earned figure. A fact, stated once, in the app's quietest voice — no
   badge, no bar, no congratulation, and no tier that sounds like a rank.
   Tabular figures because this number changes. Pulled up under the row it
   belongs to. */
.tt-wear-set__figure {
  margin-block-start: calc(var(--space-2) * -1);
  font-size: var(--step--1);
  line-height: var(--lh-snug);
  font-variant-numeric: var(--font-num-variant);
  font-feature-settings: var(--font-num-feat);
  color: var(--ink-3);
}

/* ============================================================== *
 * 6. PREFERENCE OVERRIDES
 * ============================================================== */

/* PHASE3 §4, verbatim: "Under prefers-reduced-motion, wear still renders — it
   is a static texture, not motion." So nothing here is switched off. What goes
   is the confirmation fade on Restore, which is the only thing in the file that
   moves at all. */
@media (prefers-reduced-motion: reduce) {
  .dial .dial__wear > *,
  .dial .tt-wear > * {
    transition: none;
  }
}

/* More contrast: the surfaces stop modulating, the same way css/materials.css
   drops its grain and its specular band. The mattes and the patina go. The knob
   polish and the bezel are css/dial.css's, and a brighter highlight on a control
   carries no type, so they are left alone. */
@media (prefers-contrast: more) {
  .dial .dial__wear > *,
  .dial .tt-wear > * {
    display: none;
  }

  :root[data-wear="on"] .dial .dial__wear,
  :root[data-wear="on"] .dial .tt-wear {
    background-color: transparent;
    mix-blend-mode: normal;
  }

  :root[data-wear="on"] {
    --bg: var(--ramp-150);
  }
}

/* Forced colours: the system owns every surface, and a screen-blended matte
   over a system canvas is meaningless. Stand down completely.
   (css/materials.css already hides .m-matte here for the same reason.) */
@media (forced-colors: active) {
  .dial .dial__wear,
  .dial .tt-wear {
    display: none;
  }

  :root[data-wear="on"] {
    --bg: var(--ramp-150);
  }
}
