/* SCOVR - GLOBAL FORM TOKENS
   Single source of truth. Edited in Dev mode #1 -> Global form settings
   (SCOVR-APP/public/page-settings.html). Never edit a copy by hand.

   Vendored into each repo, because they deploy independently:
     SCOVR-APP   public/tokens/form-tokens.css      <- canonical
     SCOVR-COM   web/tokens/form-tokens.css
     AMVLET-COM  web/tokens/form-tokens.css
     SCOVR-INFO  apps/../public/tokens/form-tokens.css

   Propagate a change:  node tools/sync-form-tokens.mjs
   Detect drift:        node tools/sync-form-tokens.mjs --check

   Plain ASCII on purpose: a box-drawing rule before the closing marker
   left the parser inside the comment and swallowed the :root block below.
*/

:root {
  /* Shape */
  --field-height: 48px;
  --field-size: 16px;
  --field-font: 'Aeonik', system-ui, sans-serif;
  --field-gap: 16px;

  /* Floated label. Legend metrics must match these or the notch mis-sizes. */
  --field-lab-size: 11px;
  --field-lab-weight: 500;
  /* Tracking. An 11px uppercase label needs it - caps set at their natural
     spacing read as one block rather than as words - but it had no token, so
     every page picked its own. Counted across the surfaces: .1em on 38 of 49
     labels, with .03 / .04 / .05 / .06 / .07 / .14em making up the rest. This
     is that majority, named so the outliers can be seen as outliers.

     It belongs to the label, not to the notch, but the notch DEPENDS on it:
     the legend and the label must carry identical metrics or the gap cut in
     the border stops matching the text sitting in it. */
  --field-lab-track: .1em;
  /* The notch: how far the border breaks either side of the floated label.
     It was hardcoded (padding: 0 2px) on every legend span, so the gap could
     not be tuned or even tracked globally - which is why it drifts between
     pages. --field-lab-inset is the distance from the field's left edge to
     where the notch starts. */
  --field-lab-gap: 4px;
  --field-lab-inset: 14px;
  /* Vertical nudge for the floated label, on top of the true centre (-50%) and
     the cap-height correction the label rule already carries. It was a hardcoded
     "+1px" in every rule, which sat the text visibly low in its own notch.
     -1px is what lands the cap-height ON the border line. Kept as a token because
     the optical centre depends on the label font and size, so a future type change
     may want a different fraction back. */
  --field-lab-nudge: -1px;

  /* Focus colour: what the border turns when you click INTO a field. This is a
     neutral #F0F0F0 everywhere - the same white the interface already uses for
     "this is the active thing". It is deliberately NOT a signal colour: green,
     red and gold are verdicts about the VALUE, and a field you have merely
     clicked into has no verdict yet. Reserving the signals for verdicts is what
     makes them mean something when they do appear. */
  --field-focus: #F0F0F0;

  /* Verdict border. Every state is a 1px line: rest, focus, valid and error all
     share one width, so a field NEVER changes size as its state changes - only its
     colour. The weight-as-signal idea was dropped once the resting border became a
     #2A2A2A hairline: green or red against that is already unmistakable, and
     spending weight on top made an accepted field shout as loudly as a broken one.
     Gold was the last hold-out; it is 1px too now. --field-border-verdict is gone
     with it, because nothing was left that wanted a second width. */
  --field-border: 1px;
  /* The RESTING border colour - a field at rest, before you touch it and before
     it carries any verdict. It had no token at all: every field fell back to the
     generic --border, so the one line every form shows on every page could not be
     set or tracked from here.
     It is a HAIRLINE, deliberately darker than --field-focus. The two were both
     #F0F0F0 while --field-border and --field-border-focus were both 1px, which made
     a focused field pixel-identical to a resting one - clicking into a field fed
     back nothing at all. Focus is now carried entirely by the colour change, and a
     field nobody has touched is the quietest thing on the page. */
  --field-border-colour: #2A2A2A;

  /* The field's FILL, in its two states.

     --field-fill        at rest: the field is its own surface, distinct from
                         the page behind it
     --field-fill-float  once the label has floated - on focus, or whenever
                         the field holds a value

     They differ because of where the label ends up. It rides UP onto the
     border line, so half of it hangs over the page and half over the field.
     While the fill is a different colour from the page, the label reads as a
     chip sitting on a filled box with the border running behind the text.
     Matching the page turns it into what it should be: a gap cut in the
     border, with the label sitting in the gap.

     The flip is keyed to the same two classes that float the label
     (.field--active / .field--filled), so the fill and the label can never
     disagree about which state the field is in.

     A surface that puts its form on a raised card points --field-fill-float
     at the CARD colour, not the page - the label has to match whatever is
     actually behind it. */
  --field-fill: #1E1E1E;
  --field-fill-float: #111111;
  /* Border width while the field is FOCUSED. The preview thickened to a
     hardcoded 1.5px while the real pages only changed colour and stayed at 1px,
     so the same click looked different depending on the page. One token now
     settles it - at 1px, because a fractional border does not exist on screen:
     Chrome floors it, so 1.5px painted exactly the same 1px as the resting state.
     Focus is carried by --field-focus (the colour); the weight stays put. */
  --field-border-focus: 1px;
  --field-error: #FF4D4D;

  /* THE VALIDATION MESSAGE - the line under a field that says what went wrong.
     It had no tokens, so every form invented its own: margin-top ran 7px / 8px /
     10px (and one used margin-bottom), size ran 12px / 12.5px, and the colour was
     sometimes var(--red) and sometimes a raw #FF4D4D. One standard now:
       gap   - distance from the field to the message
       size  - message text size
       lh    - line-height, so a message that wraps to two lines still reads
     The colour is not repeated here: a message inherits the verdict it belongs
     to (--field-error / --field-warn / --field-valid), which is what keeps the
     line and the border saying the same thing. */
  --field-msg-gap: 8px;
  --field-msg-size: 12px;
  --field-msg-lh: 1.4;
  /* Tracking, and it is 0 ON PURPOSE rather than for want of a decision. The
     label is uppercase and needs opening up; a validation message is a
     sentence in mixed case, and Aeonik is already spaced for that - adding
     tracking to a line someone reads under pressure slows it down. Named
     anyway so a surface that wants it has one place to say so, and so the
     value is a choice on the record instead of an absence. */
  --field-msg-track: 0;

  /* Signal colours and their border widths. The three verdicts a field can
     carry, each nameable on its own so a surface can tune one without
     dragging the other two with it:
       valid   - signal green, the "this is accepted" verdict
       error   - signal red, the "fix this" verdict
       warn    - gold, the "attention, not a failure" verdict (a pending
                 check, a soft limit) - it had no token at all before, so
                 gold could only be hardcoded per page
     All three are pinned to 1px - the same width as rest and focus - so a verdict is
     carried entirely by the colour and a field never changes size. */
  --field-valid: #00BA77;
  --field-warn: #C59972;
  --field-border-valid: 1px;
  --field-border-error: 1px;
  --field-border-warn: 1px;

  /* How long a field waits before it is allowed to say "wrong".

     A rejection is the one verdict that must not fire on the keystroke.
     "me@me.co" is a complete, rejectable address on the way to "me@me.com",
     and 8 digits is the wrong length on the way to 9 - reddening either
     mid-word accuses someone for a value they are still writing. So the
     field stays neutral until typing settles, and only then judges.

     Acceptance has no such delay: confirming what someone got right the
     instant they finish is never premature, so the valid verdict lands at
     once. The asymmetry is the point - be quick to agree, slow to accuse.

     Read from JS, not CSS - it is a timer, not a paint:
       const ms = parseInt(getComputedStyle(document.documentElement)
                    .getPropertyValue('--field-settle'), 10) || 900;

     Blur bypasses it: leaving a field IS settling, so the verdict lands
     immediately there rather than waiting out a pause already ended. */
  --field-settle: 900ms;

  /* PERSONAL EMAIL DOMAINS - the set a "Personal email" field accepts.

     Anything not on this list is treated as a work address and rejected, so
     the list is the whole rule: a consumer provider missing from it turns
     away an ordinary personal user. That is not hypothetical - gmx.at and
     gmx.ch were missing while gmx.de was present, so real Austrian and Swiss
     users of the SAME free provider were being told to use a personal
     address while holding one.

     It lives here rather than in a page because it is data, not markup, and
     because every surface with a sign-up form needs the same answer. Edited
     in Dev mode #1 -> Global form settings.

     Read from JS, not CSS - it is a list, not a paint:
       const raw = getComputedStyle(document.documentElement)
                     .getPropertyValue('--personal-domains');
       const set = new Set(raw.replace(/"/g, ' ').split(/\s+/).filter(Boolean));

     Written as adjacent quoted strings, which CSS concatenates: it keeps the
     families on their own lines, so a missing country variant is visible in
     the source the way it is in the settings UI. Order is grouping only; the
     parser sorts nothing and cares only about the whitespace.

     DISPOSABLE ADDRESSES ARE DELIBERATELY ABSENT. mailinator.com,
     guerrillamail.com, 10minutemail.com and trashmail.com were on this list
     and are not any more: a throwaway inbox is not a personal one, and
     accepting it lets a signup through that no one can be reached at. */
  --personal-domains:
    /* Google, Apple, Microsoft, Yahoo, AOL */
    "gmail.com googlemail.com "
    "icloud.com me.com mac.com "
    "outlook.com hotmail.com live.com msn.com "
    "yahoo.com yahoo.co.uk yahoo.fr yahoo.de yahoo.es yahoo.it "
    "aol.com "
    /* GMX and web.de run a domain per country - keep the family complete */
    "gmx.com gmx.net gmx.de gmx.at gmx.ch gmx.fr gmx.co.uk gmx.es "
    "web.de web.at t-online.de freenet.de "
    /* Privacy-first and independent mailboxes */
    "proton.me protonmail.com pm.me tutanota.com tutanota.de "
    "fastmail.com zoho.com mail.com "
    /* National ISPs and free providers - the default consumer mailbox in
       their market, so rejecting one turns away ordinary personal users */
    "orange.fr wanadoo.fr free.fr sfr.fr laposte.net "
    "libero.it virgilio.it tiscali.it alice.it "
    "seznam.cz centrum.cz wp.pl o2.pl onet.pl interia.pl "
    "sapo.pt telenet.be skynet.be ziggo.nl kpnmail.nl "
    "telia.com bahnhof.se online.no sol.dk bluewin.ch "
    /* Russia and China */
    "yandex.com yandex.ru mail.ru rambler.ru "
    "qq.com 163.com sina.com";

  /* THE TOGGLE. One switch, taken from the Dev mode #1 control on me.html and
     measured off the live element rather than read from a spec. It had drifted
     into five sizes - 38x22, 40x23, 42x24, 44x26, 46x27 - across 25 rules in 22
     files, under four class names (.toggle .switch .sw .rid-switch), with knobs
     in both #fff and #F0F0F0. Nothing chose those numbers; they accumulated.
     The knob is --field-focus, the same white as the focus ring, the locked
     values and the field text. Pure #fff was the last thing in the interface
     brighter than everything around it. */
  --switch-w: 46px;
  --switch-h: 28px;
  --switch-knob: 22px;
  --switch-inset: 3px;
  /* travel = w - knob - 2*inset, stated so a size change cannot forget it */
  --switch-travel: calc(var(--switch-w) - var(--switch-knob) - var(--switch-inset) * 2);
  --switch-off: #3A3A3A;
  --switch-on: var(--field-valid);
  --switch-knob-colour: var(--field-focus);
  --switch-motion: .18s ease;
}

/* -- THE TOGGLE ------------------------------------------------------------
   Four class names for one control, because they already exist across 22 files
   and renaming markup on every page is a bigger, riskier change than teaching
   one rule the names it will meet. New work should use .switch.

   Knob structures in the wild: most pages draw it with ::after; .ds-loc-toggle
   uses a child .knob. Both are covered.

   A FIFTH draws the thumb as an <i> child (.ftsw, the audit list; .lmsw, the
   light-mode switch - same shape, same drift). It also
   animated `left` rather than transform and tinted its track rgba(0,186,119,.16)
   with a GREEN thumb - dark-green-on-green, the one switch in the app you could
   not read at a glance. It is covered here so it inherits the solid fill and the
   #F0F0F0 thumb; its `left` is pinned to the inset so transform does the travel.

   A THIRD structure wraps a real <input type=checkbox>: the <label> is the box,
   a child .knob is the TRACK, and .knob::before is the thumb. That is the most
   accessible of the three - it is a real checkbox, so it is focusable and
   announced without role/aria bookkeeping - so it is supported rather than
   converted. It is matched by :has(> input), which cannot collide with the
   .ds-loc-toggle pattern where .knob IS the thumb.

   Deliberately NOT covered:
     .swatch, and .sw at 26-30px  - colour swatches, square, not switches
     .alg-share .rid-switch       - a scoped-down 34x20 variant that is meant to
                                    be smaller than the control it sits inside
     .look-toggle                 - NOT a switch at all: a text button with a
                                    chevron and aria-expanded that discloses the
                                    Simon settings panel. The name misleads; it
                                    was in this rule briefly and painted a white
                                    knob over the word "Settings". */
.rid-switch, .devpro-sw, .ds-loc-toggle,
.switch:not(.switch-btn), .toggle, .sw:not(.swatch), .ftsw, .lmsw {
  width: var(--switch-w); height: var(--switch-h);
  border-radius: 999px; border: none; padding: 0;
  position: relative; flex-shrink: 0; cursor: pointer;
  /* appearance:none is not cosmetic here. Some of these switches are <button>
     (.lmsw, .ftsw) and some are <span> or <div>. A button keeps appearance:auto
     by default, and the native control paints its OWN background over any author
     colour - so the ON state applied, matched, and won the cascade while the
     track stayed grey. Every other toggle worked because it was not a button,
     which is exactly what made this look like a CSS bug rather than a widget
     one: the rule was right, the element was rendering itself. */
  -webkit-appearance: none; appearance: none;
  background: var(--switch-off);
  transition: background var(--switch-motion);
}
.rid-switch::after, .devpro-sw::after,
.switch:not(.switch-btn)::after, .toggle::after, .sw:not(.swatch)::after,
.ds-loc-toggle .knob, .ftsw > i, .lmsw > i {
  content: ''; position: absolute;
  top: var(--switch-inset); left: var(--switch-inset);
  width: var(--switch-knob); height: var(--switch-knob);
  border-radius: 50%;
  background: var(--switch-knob-colour);
  transition: transform var(--switch-motion);
}
/* ON. Both spellings, because the app sets one or the other depending on whether
   the control is a <button role="switch"> or a plain element with a class. */
.rid-switch.on, .devpro-sw.on,
.switch:not(.switch-btn).on, .toggle.on, .sw:not(.swatch).on, .ftsw.on,
[aria-checked="true"] > .devpro-sw,
[aria-checked="true"].rid-switch, [aria-checked="true"].toggle,
[aria-checked="true"].switch:not(.switch-btn), [aria-checked="true"].ds-loc-toggle, [aria-checked="true"].lmsw {
  background: var(--switch-on);
}
.rid-switch.on::after, .devpro-sw.on::after,
.switch:not(.switch-btn).on::after, .toggle.on::after, .sw:not(.swatch).on::after,
.ftsw.on > i,
[aria-checked="true"] > .devpro-sw::after,
[aria-checked="true"].rid-switch::after, [aria-checked="true"].toggle::after,
[aria-checked="true"].switch:not(.switch-btn)::after,
[aria-checked="true"].ds-loc-toggle .knob, [aria-checked="true"].lmsw > i {
  transform: translateX(var(--switch-travel));
}
/* -- Checkbox-backed variant: <label class=switch><input><span class=knob> --
   The label itself carries no paint; .knob is the track and ::before the thumb. */
.switch:has(> input[type="checkbox"]) {
  background: none; border: none; padding: 0;
  width: var(--switch-w); height: var(--switch-h);
  display: inline-block; position: relative; flex-shrink: 0; cursor: pointer;
}
.switch:has(> input[type="checkbox"]) > input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch > input[type="checkbox"] + .knob {
  position: absolute; inset: 0; border-radius: 999px;
  background: var(--switch-off);
  transition: background var(--switch-motion);
}
.switch > input[type="checkbox"] + .knob::before {
  content: ""; position: absolute;
  top: var(--switch-inset); left: var(--switch-inset);
  width: var(--switch-knob); height: var(--switch-knob);
  border-radius: 50%;
  background: var(--switch-knob-colour);
  transition: transform var(--switch-motion);
}
.switch > input[type="checkbox"]:checked + .knob { background: var(--switch-on); }
.switch > input[type="checkbox"]:checked + .knob::before { transform: translateX(var(--switch-travel)); }
.switch > input[type="checkbox"]:focus-visible + .knob {
  outline: 2px solid var(--switch-on); outline-offset: 2px;
}

/* SIGNAL VARIANTS. The on-colour carries meaning in three places, so it is the
   one thing a page may still set: amber says the app is in a non-production
   state, and the Her Zone / His Zone accents say WHICH entrance a staff toggle
   opens. Geometry is still the standard's; only the fill differs. */
.rid-switch.devmode.on, .devmode .rid-switch.on { background: #FFB800; }
.switch.on:not(.men) { background: var(--switch-on); }
.switch.women.on, .switch.on.women { background: #C77DA6; }
.switch.men.on, .switch.on.men { background: #6E9BD6; }

/* The marketing surfaces used to focus gold (--field-focus: #C59972) while the
   app focused green. Focus is now one neutral #F0F0F0 EVERYWHERE, so that
   override is gone on purpose: a brand accent on focus made the same gesture
   mean two different things depending on which site you were on, and it spent
   gold - now the "attention" verdict (--field-warn) - on a state that carries
   no verdict at all. Brand still shows through the CTA and the accents; it no
   longer shows through the caret. */

/* -- LIGHT MODE ------------------------------------------------------------
   Only the two fill tokens flip. Everything above is either a measurement
   (sizes, widths, timings) or a signal colour that keeps its brand value in
   both themes - green stays green on white.

   Values match the [data-theme="light"] blocks the pages already carry, so a
   field paints the same whether the colour reaches it from here or from the
   page's own override. Without this block the fills would stay dark on a
   light page, which is what makes them safe to hardcode nowhere else. */
:root[data-theme="light"] {
  --field-fill: #F0F0EE;
  --field-fill-float: #FFFFFF;
}
