/* Messenger prop — design tokens
 *
 * Measured from brief/IMG_2631.PNG, brief/IMG_2632.PNG and the screen recording.
 * Full derivation and confidence notes: ./visual-spec.md (ticket 04).
 *
 * Units: CSS px on the 375x647 stage at DPR 2 (map decision 66, which refined
 * decisions 16/20/21 into one number). Never use vh/dvh/svh/lvh or
 * env(safe-area-inset-*) for layout, and never branch on navigator.standalone.
 *
 * The default palette below is the S1 "themed" look (IMG_2631), which the brief
 * names as ground truth. The default-Messenger look seen in the screen recording
 * is provided as an opt-in class at the bottom — see ticket 18.
 */

:root {
  /* ---------------------------------------------------------------- stage */
  --stage-w: 375px;
  /* 647, NOT 667 — this file said 667 until ticket 18 caught it, which would have
   * put every bottom-pinned element (composer, sheet) 20px wrong in both build
   * tickets. Decision 66: hard-coded 375x647, no viewport units, no shell
   * detection. Decision 67: if the native wrapper (21) ever hides the status bar,
   * hand-edit this ONE line to 667px and re-run the overlay A/B once. */
  --stage-h: 647px;
  --status-bar-h: 20px; /* iOS-owned strip, we only reserve it */

  /* -------------------------------------------------------------- neutral */
  /* Identical across all three sources — safe. */
  --c-thread-bg: #ffffff;
  --c-surface: #f2f4f7; /* incoming bubble, call card, composer field, reaction badge */
  --c-surface-alt: #f1f2f5; /* "ALL" pill; within AA noise of --c-surface */
  --c-text: #080808;
  --c-text-secondary: #65686b;
  --c-text-on-accent: #ffffff;
  --c-call-card-icon: #a1a2a4;
  --c-sheet-close: #e2e5e9;
  --c-scrim: rgba(0, 0, 0, 0.4); /* exact — solved from the dimmed still */

  /* --------------------------------------------------------------- accent */
  --c-header-accent: #326bf6; /* chevron, unread badge, phone, video — flat */
  --c-composer-accent: #0719be; /* all six composer glyphs — flat */

  /* Header background: horizontal, constant down every column, non-linear. */
  --g-header: linear-gradient(
    to right,
    #ffffff 0px,
    #ffffff 80px,
    #fdfeff 100px,
    #f2f8ff 125px,
    #daecfd 150px,
    #c0ddfc 175px,
    #b0d4fc 200px,
    #aad2fb 225px,
    #a9d1fb 240px,
    #a9d1fb 305px,
    #abd3fb 330px,
    #b0d5fc 350px,
    #b7d9fc 375px
  );
  --header-h: 64px; /* gradient runs to y=0, behind the iOS status bar */
  --header-row-h: 36px; /* content row; centred on y=37.75 */

  /* Outgoing bubbles: ONE screen-anchored vertical gradient, masked by the
   * bubbles. Do not set this per bubble — a bubble's colour depends on where it
   * sits and changes as the thread scrolls. The mechanism is confirmed: the ramp
   * runs continuously *through* both sampled bubbles.
   *
   * CLAMPED — ticket 18, decision 71. The previous fitted curve
   *   R = 86.5 - 0.1274*y , G = 187.0 - 0.2610*y , B = 280.8 - 0.1048*y
   * was a two-bubble fit amplified across the whole stage, and demanded
   * blue = 282 at y=0, which is out of gamut. It was inventing colour for most
   * of the 12 outgoing bubbles. Only y 335..419 was ever measured; outside that
   * band the endpoint colours are HELD, never extrapolated.
   *
   * Both stops below are measured directly (ffmpeg rgb24 at x=700), not fitted.
   * Scene bubbles sit ~y480-580, i.e. below the band, so they render the held
   * rgb(34 79 237) — identical to a flat fill. To ship flat instead, collapse
   * this to two identical stops; that is a one-line change (decision 72).
   */
  --g-outgoing: linear-gradient(
    to bottom,
    rgb(45 101 246) 0px,
    rgb(45 101 246) 335px,
    rgb(34 79 237) 419px,
    rgb(34 79 237) 667px
  );

  /* ---------------------------------------------------------------- bubble */
  --bubble-radius: 18px;
  --bubble-radius-run: 4px; /* the two corners at a within-run join */
  --bubble-pad-y: 8px;
  --bubble-pad-x: 12px;
  --bubble-line-h: 22px;
  --bubble-h-1line: 38px;
  --bubble-max-w: 256px; /* tight lower bound, one wrapped sample */
  --bubble-gap-in-run: 2px;
  --bubble-gap-between-runs: 12px;
  --bubble-in-left: 52px; /* = avatar left 12 + avatar 28 + gap 12 */
  --bubble-out-right: 8px; /* inset from the right edge of the stage */

  /* --------------------------------------------------------------- avatars */
  --avatar-sender: 28px; /* thread, bottom-aligned to the run's last bubble */
  --avatar-sender-left: 12px;
  --avatar-header: 24px; /* stacked pair, always exactly two */
  --avatar-header-offset: 12px;
  --avatar-header-cluster: 36px;
  --avatar-call: 70px;
  --avatar-call-offset: 30px;
  --avatar-call-cluster: 100px;
  --avatar-receipt: 16px;
  --avatar-receipt-gap: 4px;
  --avatar-receipt-right: 20px; /* rightmost edge lands on x=355 */
  --avatar-ring: 2px; /* background-coloured ring where the pair overlaps */
  --avatar-sheet-row: 40px;

  /* -------------------------------------------------------------- composer */
  /* RE-MEASURED BY TICKET 08. visual-spec §6 reported the field as 38 tall at
   * y 619.5-657.5; the edges are crisp at DPR 2 and land on source rows
   * 1238..1317, i.e. y 619..659, height **40**. The keyboard-up frames agree
   * independently: field y 359..399, also 40. Both states, both looks, one
   * number. The 8 below it is unchanged, so the band is 48, not 46. */
  --composer-field-h: 40px;
  --composer-field-radius: 20px; /* = h/2; the pill keeps this radius when it grows */
  --composer-field-pad-x: 15px; /* field left 188 -> "A" ink 203.5, less the LSB */
  --composer-field-pad-r: 33.5px; /* text column stops at 285, clear of the in-field glyph */
  --composer-bottom-gap: 8px; /* also the gap above the keyboard when up */
  --composer-icon: 22px;

  /* Resting row (keyboard down), from visual-spec §6's table — left edges.
   * Every glyph is vertically centred on the field's centre, in both states. */
  --composer-plus-left: 15px;
  --composer-camera-left: 58px;
  --composer-gallery-left: 103px;
  --composer-mic-left: 149.5px;
  --composer-chevron-left: 21.5px; /* the collapsed state's only left glyph */
  --composer-chevron-w: 11px;
  --composer-chevron-h: 19px;
  --composer-field-left-down: 188px;
  --composer-field-left-up: 52px; /* collapsed — the only edge that moves */
  --composer-field-right: 56.5px; /* field right edge 318.5, identical in both states */
  --composer-infield-right: 6.5px; /* in-field glyph right edge 312 */
  --composer-trail-right: 15px; /* thumbs-up / send arrow right edge 360 */
  --composer-collapse-ms: 180ms; /* decision 77: the collapse is animated, not instant */

  /* Where the composer sits when the keyboard is up. NOT a viewport reading — a
   * hard-coded constant, exactly like --stage-h (decision 66), so the layout
   * cannot collapse mid-take. The composer's bottom is pinned here and the
   * thread is sized around it. Bottom-pinning to --stage-h alone (which is what
   * ticket 08 was written to do) would let iOS scroll the whole stage up when
   * the keyboard opens and carry the header off the top of the screen.
   *
   * ⚠ 451 is an ESTIMATE. 406 was measured on hero-class hardware WITH the
   * predictive bar, which ticket 03 turned off. Read the real number off the
   * hero phone at ticket 13 — the app logs it, and ?kbd=NNN tries a value
   * without a rebuild — then hand-edit this one line. Same deliberate
   * two-known-values move decision 67 sets up for --stage-h. */
  --keyboard-top: 451px;
  --keyboard-top-with-bar: 406px; /* SE, predictive bar ON — measured, not shipping */

  /* ----------------------------------------------------------- call screen */
  --call-bg-base: #232427;
  --call-btn: 48px;
  --call-btn-pitch: 74px;
  --call-btn-inset: 16px;
  --call-btn-row-cy: 591px;
  --c-call-btn-off: #121212;
  --c-call-btn-on: #ffffff;
  --c-call-end: #d91f2e;
  --call-cancel-btn: 60px;
  --c-call-cancel: #ff3f3a; /* deliberately different from --c-call-end */

  /* --------------------------------------------------------- call-in-thread */
  --callcard-w: 187.5px;
  --callcard-h: 60px;
  --callcard-icon: 36px;
  --callcard-pad-x: 12px;

  /* ------------------------------------------------------- reactions sheet */
  /* The card floats: inset 8 left, right and bottom. Measured in IMG_2632 at
   * x 8..367 and y 325.5..659 of a 667-tall SCREEN — but we own 647 (decision
   * 66), so the card keeps its measured HEIGHT and is pinned 8 above OUR bottom,
   * landing at y 305.5..639. Same rule the composer already follows; decision
   * 67's hand-edit to 667 moves both together. */
  --sheet-inset: 8px; /* left, right and bottom — it floats */
  --sheet-h: 333.5px; /* card top 325.5 -> bottom 659 in the still */
  --sheet-radius: 26px;
  --sheet-close: 28px;
  --sheet-close-inset: 12px; /* from the card's TOP and RIGHT, both 12 */
  --sheet-head-cy: 26px; /* close circle centre = title ink centre = card top + 26 */
  --sheet-row-top: 62px; /* card top -> row avatar top (387.5 - 325.5) */
  --sheet-row-h: 40px;
  --sheet-avatar-left: 16px; /* card left -> avatar left (24 - 8) */
  --sheet-name-left: 68px; /* card left -> name ink (77 - 8); 13 after the avatar */
  --sheet-emoji-right: 16px; /* card right -> emoji ink right (367 - 350.5) */
  --sheet-row-emoji: 28px; /* renders the still's 27 x 26.5 of ink */
  --sheet-pills-bottom: 12px; /* pill bottom 647 -> card bottom 659 */

  /* The pill row has ONE measurable box — the selected ALL pill, x 129..187.
   * The other pill has no fill, so its box is invisible and only its contents
   * can be measured: emoji ink 203.5..222.5, count ink 229..233.5. That is three
   * landmarks for a centred row whose width depends on the pill we cannot see,
   * so the paddings below are SOLVED, not read: they are the values that put all
   * three where the still has them. The asymmetry on the bare pill is the count
   * glyph's trailing bearing, not a design decision. */
  --pill-h: 32px;
  --pill-radius: 16px;
  --pill-pad-x: 16.25px; /* ALL: ink 25 inside a 58 box */
  --pill-emoji-pad-l: 16.5px;
  --pill-emoji-pad-r: 11px;
  --pill-gap: 0px; /* the pills touch; the padding is the whole gap */
  --pill-emoji: 19px; /* ink 19 x 19.5 */
  --pill-emoji-gap: 5.5px; /* emoji ink right 222.5 -> count ink left 229 */

  /* THE BADGE IS A PILL, NOT A CIRCLE — 28 x 22, radius 11. visual-spec §8 read
   * "circle, 28 diameter" from the width alone; both badges in IMG_2632 measure
   * 22 tall (in: y 133..155, out: y 260.5..282.5) against 28 wide. Ticket 09
   * corrected it. Same class of error as decision 91's 38-vs-40 field. */
  --react-badge: 28px; /* width */
  --react-badge-h: 22px;
  --react-badge-radius: 11px; /* = h/2, a true pill: fitted against both badges */
  --react-badge-emoji: 16px; /* ink 15.0 x 15.5 — 17 renders 16.0 x 16.0 */
  --react-badge-inset-x: 16px; /* centre, in from the bubble's right edge */
  --react-badge-offset-y: 6px; /* centre, below the bubble's bottom edge */

  /* MOTION — INVENTED, LIKE 08's SEND SPRING. No source has any of it: both
   * stills are frozen and the recording's only movement is the call-screen
   * transition (decision 31, visual-spec §9.6). These are numbers the director
   * can watch rather than a question in the abstract — parked on ticket 20 with
   * the wrapped field, and tunable at 13. */
  --react-pop-ms: 260ms; /* the badge arriving — the PING the actor reacts to */
  --sheet-rise-ms: 260ms;
  --sheet-fall-ms: 200ms;
  --sheet-ease: cubic-bezier(0.17, 0.84, 0.29, 1); /* iOS sheet: fast out, settle */

  /* ------------------------------------------------- thread vertical rhythm */
  /* FITTED BY TICKET 07's OVERLAY A/B, and box-to-box, not ink-to-ink.
   * visual-spec §5 lists absolute INK positions, which is the right way to state
   * a measurement and the wrong way to write CSS: a 12px/16 line box carries
   * ~3.5px of leading above its ink, so using the ink deltas as margins put every
   * gap out by about that much. These are the box margins that reproduce §5's ink
   * positions — every element in the still now lands within 0.5 CSS px (one
   * device pixel) of the reference, and most land exactly.
   * Tickets 08/09/10 import these rather than re-deriving them. */
  --sp-thread-top: 18px; /* header bottom 64 -> first divider ink 85 */
  --sp-thread-bottom: 19px; /* last ink -> composer field top */
  --sp-divider-to-item: 13.5px; /* divider ink bottom -> next bubble/card top */
  --sp-item-to-divider: 18.5px; /* bubble bottom -> divider ink top */
  --sp-receipt-to-divider: 24px; /* receipt bottom -> divider ink top */
  --sp-divider-to-name: 14px; /* divider ink bottom -> sender name ink top */
  --sp-name-to-bubble: 3px; /* sender name ink bottom -> bubble top (5.5 ink) */
  --sp-bubble-to-receipt: 4px;
  --sp-receipt-to-name: 17.5px; /* receipt bottom -> sender name ink top */
  --sp-receipt-to-run: 20px;
  --sp-system: 18px; /* around a system line (~23 ink) */
  --sp-card-to-system: 20.5px;
  /* MEASURED BY TICKET 09, not inferred. IMG_2632 does show a reacted bubble
   * with a receipt under it — the outgoing one — and the rule falls straight
   * out of the badge's own geometry: the badge's 2px ring bottom sits
   * 11 + 6 + 2 = 19 below the bubble, and the receipt then keeps its ordinary
   * --sp-bubble-to-receipt 4 from that. 265.5 + 19 + 4 = 288.5, which is the
   * receipt's measured top exactly. 07's inferred 18 was one pixel light.
   *   ⚠ the still's OTHER reacted bubble, the incoming one, is followed by a
   * divider 23 further down rather than 19 — see visual-spec §8. Our scenes
   * never put a divider under a reacted bubble, so 19 is the number that ships. */
  --sp-reaction-extra: 19px;

  --composer-band: 48px; /* field 40 + 8 below it — corrected by 08, was 46 */
  --sender-name-left: 68px; /* aligned to the bubble's text, not its edge */
  /* ------------------------------------------------------------------ type */
  /* -apple-system gets SF Pro natively and exactly. No webfont.
   * Sizes are the best joint fit of cap height and ink width; weight and size
   * are NOT separable at DPR 2 — A/B every one of these by overlay.
   *
   * FITTED BY TICKET 07's OVERLAY A/B. 04 asked every build ticket to do this and
   * three values moved. The method: render the still's own strings, scan both
   * images with reference/scan.py, and fit ink width against the cap height of a
   * single capital — not of the whole string, because the dot on an `i` and the
   * ascender on a `d` both sit ABOVE cap height and inflate it. That is almost
   * certainly how the title came to be 17px: `Boiz Boiz` has two i-dots.
   *
   *   message        15/400  ink 95.27 vs 95.5 measured  <- unchanged, and the
   *                          control that validates the whole method (0.24%)
   *   sender name    11/500  unchanged; +tracking
   *   date divider   12/500  cap lands EXACTLY; +tracking for the 1.5px width
   *   header title   17 -> 16, 700 -> 600.  Was 11% too wide and 0.9px too tall.
   *   card title     15/600 (was 700); +tracking for the 6% width
   *   system line    13 -> 12.  Was 7% too wide and 1px too tall.
   *   card subtitle  13 -> 12.  Same, and 12px lands to 0.35%.
   *
   * The bold and all-caps items all needed a little negative tracking, which is
   * what iOS itself applies to SF and a browser does not. Kept as separate
   * --ls-* tokens because the `font:` shorthand cannot carry letter-spacing.
   *
   * Fitted in Chrome on macOS, which matched iOS to 0.24% on the control — but
   * the camera looks at the phone, so re-run the A/B on the device at 13. */
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;

  --t-message: 400 15px/22px var(--font);
  --t-header-title: 600 16px/21px var(--font);
  --t-sender-name: 500 11px/14px var(--font); /* least certain, +/-1 */
  --t-divider: 500 12px/16px var(--font);
  --t-system: 400 12px/16px var(--font);
  --t-callcard-title: 600 15px/20px var(--font);
  /* 20, not 16 — TICKET 10. The subtitle inherited --t-system's 12px/16px box,
   * which is right for a system line and wrong here: the card's two lines are
   * flex-centred as a pair, so the SHORT box pulled the pair down and put the
   * title's ink 2px below the still while the subtitle itself landed exactly.
   * Solved rather than nudged — the two measured baselines (25.4 and 44.5 from
   * the card's top) have one solution, Lt = 20.3, Ls = 20.1, and 20/20 puts both
   * within 0.1. Decision 82's box-to-box rule, applied to the one component it
   * had not reached. */
  --t-callcard-sub: 400 12px/20px var(--font);

  /* Tracking, where the still's ink is tighter than the browser's default. */
  --ls-header-title: -0.2px;
  --ls-sender-name: -0.15px;
  --ls-divider: -0.08px;
  --ls-callcard-title: -0.3px;
  /* The composer's own type — placeholder AND typed text, which share a baseline
   * in the reference (both land on 384.5 in the keyboard-up frames), so they are
   * one size. A/B'd by 08 with dev/type-probe.html at DPR 2, decision 81's way:
   *
   *   "Aa" ink       16.5 measured        15px renders 16.5   EXACT
   *   "W" cap height 11.5 measured        15px renders 11.5   EXACT
   *   34-char line  210.5 measured        15px renders 211.5  0.47%
   *                                       16px renders 224.0  6%, ruled out
   *
   * So 04's 15/400 was right and the SIZE does not move — but the line box
   * does. 22 was inherited from the bubble text; the caret measures 20 tall
   * (369..389), and 20 is what puts the baseline on 384.5 and makes the field
   * exactly 40 with 10 of padding top and bottom.
   *
   * The first pass of this A/B read 16px, because the reference ink run
   * 65.5..278.5 has the CARET on the end of it. Measure text ink to 276.
   *
   * ⚠ 15px is below iOS's 16px focus-zoom threshold. maximum-scale=1 and the
   * standalone shell should both suppress it — 03's probe carried the same
   * viewport meta and chose 17 as a precaution, not from a measurement. First
   * item on 08's on-device checklist. */
  --t-placeholder: 400 15px/20px var(--font);
  --c-caret: var(--c-composer-accent); /* measured #0C56FE in the default look = its accent */
  --t-badge: 600 12px/16px var(--font);
  /* FITTED BY TICKET 09's OVERLAY A/B. All three moved off 04's estimates:
   *   sheet title  16/700 -> 15/600.  "Reactions" ink 69.0 vs 69.5 measured
   *   sheet name   16/600 -> 15/600.  "Michael Day" ink 85.5 — EXACT
   *   pill         13/700 -> 13.5/700. "ALL" ink 25.0 — EXACT, box 58 exact
   *
   * The size is 15 for both sheet items and the weight is what separates them
   * from 04's guesses — the opposite of the composer, where 08 found the size
   * fixed and the line box wrong. A caution about decision 81's method: fit the
   * cap of a capital, but do NOT fit it on ONE capital. The still's `R` in
   * "Reactions" reads 10.5 where its `M` in "Michael Day" reads 11.5, at the
   * same size in the same image — a curved apex antialiases away half a pixel
   * that a flat one keeps. Chasing the `R` would have put the title at 14.3px.
   * Take the flat-topped capital, or take the string width.
   *
   * Residual: the pill's caps land 9.5 against the still's 10. 14px closes that
   * and costs the pill box its exact 58 and the ink its exact 25 — three exact
   * numbers for one, so 13.5 ships. */
  --t-sheet-title: 600 15px/20px var(--font);
  --t-sheet-name: 600 15px/20px var(--font);
  --t-pill: 700 13.5px/17px var(--font);
  --t-call-name: 700 24px/29px var(--font);
  --t-call-status: 400 16px/21px var(--font);
  --t-call-cancel: 500 12px/16px var(--font);
}

/* The default-Messenger look, as seen in the screen recording and IMG_2632.
 *
 * NOT SHIPPING — ticket 18 chose the themed S1 look above (decision 70). Kept as
 * the documented one-line fallback if the director overturns that before 18 Aug.
 * Apply on a stage ancestor to switch. See visual-spec.md section 1. */
.theme-default {
  --g-header: linear-gradient(to right, #ffffff, #ffffff);
  --c-header-accent: #0a55ff;
  --c-composer-accent: #0a55ff;
  --c-surface: #f0f1f6;
  /* Genuinely flat and position-independent — measured rgb(17 96 255) at y=80 and
   * rgb(16 95 255) at y=155, constant across two bubbles at different heights.
   * The old rgb(9 87 255)@667px stop was the same extrapolation error as the
   * themed gradient's (ticket 18, Finding 2) and has been removed. */
  --g-outgoing: linear-gradient(to bottom, #1160ff 0px, #1160ff 667px);
}
