/* SavvyVault shared design tokens
 * --------------------------------------------------------------------------
 * Single source of truth for the --savvy-* design-token vocabulary. Moved out
 * of admin.css in 2.123.11 and enqueued (handle 'savvyvault-tokens') alongside
 * every SavvyVault stylesheet on BOTH admin and front-end, so admin and
 * customer-facing styles resolve the same tokens. Consumers keep a shipped-
 * value fallback in every var(), so rendering is unchanged even if this file
 * is absent. Add new tokens HERE, not in admin.css.
 */
:root {
	/* Semantic colour tokens (aliased to shipped admin.css values) */
	--savvy-color-primary:        #2271b1; /* WP admin blue */
	--savvy-color-primary-hover:  #1a4f8f;
	--savvy-color-text-primary:   #1d2327;
	--savvy-color-text-secondary: #646970;
	--savvy-color-text-muted:     #50575e;
	--savvy-color-surface:        #fff;
	--savvy-color-border:         #dcdcde;
	--savvy-color-border-strong:  #c3c4c7;
	--savvy-color-error:          #b32d2e;
	/* W14-24. WP UI Specification 10.2's Error row, read from disk: error border,
	   #FEF2F2 ground, text-primary ink. The token name matches the one 10.2
	   declares at its line 223. An OPAQUE ground is what makes the notice
	   band- and mode-independent -- see savvyvault-forms.css. */
	--savvy-color-error-bg:       #FEF2F2;
	--savvy-color-border-focus:   #1a4f8f; /* focus-visible outline */
	--savvy-color-text-on-primary: #fff;   /* text on the primary blue */
	--savvy-color-surface-subtle: #dcdcde; /* subtle grey fill (chip / progress track) */

	/* Studio palette (2.123.6) - distinct from the admin palette above;
	   shipped-value aliases, adopted in admin-studio.css. Status colours and
	   the grey ramp are deliberately deferred to a later consolidation. */
	--savvy-color-accent:          #2563eb; /* Studio primary blue (NOT --primary) */
	--savvy-color-accent-alt:      #6d4aff; /* Studio violet accent */
	--savvy-color-surface-muted:   #f7f8fb; /* muted panel background */
	--savvy-color-border-subtle:   #e1e3ea; /* dominant panel/divider border */
	--savvy-color-text-subtle:     #8a8f9a; /* lightest label/hint text */
	--savvy-color-surface-inverse: #1d2327; /* dark surface (e.g. video thumb) */

	/* Status palette (2.123.8, Track A1) - spec-named base/bg tokens aliased
	   to shipped values, plus role-accurate -fg tokens for the darker on-light
	   TEXT variants the shipped design needs for contrast (the spec has no -fg).
	   Zero visual change: the two greens / two ambers are fg/solid pairs, not
	   redundant. error already defined above. */
	--savvy-color-success:     #16a34a; /* solid/accent success */
	--savvy-color-success-fg:  #1a7f37; /* success text on light (contrast) */
	--savvy-color-success-bg:  #e7f6ec; /* pale success surface */
	--savvy-color-warning:     #bf7d1a; /* solid/badge warning */
	--savvy-color-warning-fg:  #8a6d1a; /* warning text on light (contrast) */
	--savvy-color-warning-bg:  #fdf6e3; /* pale warning surface */

	/* Neutral grey ramp (2.123.9, Track A2) - primitive divider/surface steps.
	   Studio near-duplicate greys were consolidated onto these; gray-400 shares
	   the border-strong value by coincidence (primitive vs semantic). */
	--savvy-gray-50:  #f3f5f9; /* pale surface  */
	--savvy-gray-200: #e0e3ea; /* light divider */
	--savvy-gray-300: #cfd3dd; /* mid divider   */
	--savvy-gray-400: #c3c4c7; /* strong divider */
	--savvy-gray-500: #8c8f94; /* dark divider  */
	--savvy-gray-700: #3b4658; /* dark surface (slate) */
	--savvy-gray-800: #374151; /* dark surface        */
	--savvy-gray-900: #1f2937; /* darkest surface     */

	/* Primitive spacing (4px base unit; WP UI Specification 5.1 scale, completed
	   in E0/2.123.22). The 0.5 / 1.5 steps escape the dot so the custom-property
	   name stays a valid CSS identifier (an unescaped dot would split the token).
	   The semantic gap aliases below now consume the spacing scale; the other
	   primitive steps remain available for later role bindings. */
	--savvy-space-0:    0;
	--savvy-space-px:   1px;
	--savvy-space-0\.5: 0.125rem; /* 2px  */
	--savvy-space-1:    0.25rem;  /* 4px  */
	--savvy-space-1\.5: 0.375rem; /* 6px  */
	--savvy-space-2:    0.5rem;   /* 8px  */
	--savvy-space-3:    0.75rem;  /* 12px */
	--savvy-space-4:    1rem;     /* 16px */
	--savvy-space-5:    1.25rem;  /* 20px */
	--savvy-space-6:    1.5rem;   /* 24px */
	--savvy-space-8:    2rem;     /* 32px */
	--savvy-space-10:   2.5rem;   /* 40px */
	--savvy-space-12:   3rem;     /* 48px */
	--savvy-space-16:   4rem;     /* 64px */
	--savvy-space-20:   5rem;     /* 80px */
	--savvy-space-24:   6rem;     /* 96px */

	/* Semantic spacing (WP UI Specification 2.1). W14-18 completes the
	   family-token layer that the signup form has read since 2.138.0. The
	   values are aliases, not copies. Hosts bind the public semantic gap names;
	   the primitive mapping below them remains family implementation detail. */
	--savvy-gap-xs:  var(--savvy-space-1);  /* 4px  */
	--savvy-gap-sm:  var(--savvy-space-2);  /* 8px  */
	--savvy-gap-md:  var(--savvy-space-4);  /* 16px */
	--savvy-gap-lg:  var(--savvy-space-6);  /* 24px */
	--savvy-gap-xl:  var(--savvy-space-8);  /* 32px */
	--savvy-gap-2xl: var(--savvy-space-12); /* 48px */
	--savvy-gap-3xl: var(--savvy-space-16); /* 64px */

	/* Breakpoint scale (WP UI Specification 6.1) -- mobile-first, min-width only.
	   -----------------------------------------------------------------------
	   These are the canonical named breakpoints. Values match the spec exactly.

	   IMPORTANT: a CSS custom property CANNOT be used inside a media-query
	   condition. Writing min-width: var(--savvy-bp-md) inside an @media test is
	   invalid per the CSS specification and silently never matches. The UI
	   Specification 6.2 example writes the literal pixel value for this reason.

	   So these tokens are the single place the numbers are DEFINED. Each @media
	   block writes the literal px and names its token in a trailing comment, so
	   the two can never drift apart. Scripts that must branch on the same widths
	   read the token value via getComputedStyle().

	   Track C migrates every stylesheet from the shipped max-width tiers to
	   mobile-first min-width, one file per slice. Nothing consumes these tokens
	   yet -- defining them is a zero-change step. */
	--savvy-bp-xs:   375px; /* standard phones           */
	--savvy-bp-sm:   640px; /* large phones, small tablets */
	--savvy-bp-md:   768px; /* tablets, portrait         */
	--savvy-bp-lg:  1024px; /* tablets landscape, laptops */
	--savvy-bp-xl:  1280px; /* desktops                  */
	--savvy-bp-2xl: 1536px; /* large desktops            */

	/* Layout primitives (WP UI Specification 5.2) - radius / shadow / z-index.
	   The canonical vocabulary, defined here; Track E1-E3 adopt them across the
	   stylesheets. Nothing consumes them yet, so this is a zero-change step.
	   Values match the spec exactly; radius lg/md also equal the portal
	   radius values, which read these tokens directly since W14-2B. */

	/* Border radius */
	--savvy-radius-none: 0;
	--savvy-radius-sm:   0.25rem;  /* 4px  - badges, tags, chips        */
	--savvy-radius-md:   0.375rem; /* 6px  - buttons, inputs, alerts    */
	--savvy-radius-lg:   0.5rem;   /* 8px  - cards, modals, dropdowns   */
	--savvy-radius-xl:   0.75rem;  /* 12px - hero cards, feature panels */
	--savvy-radius-2xl:  1rem;     /* 16px - marketing sections         */
	--savvy-radius-full: 9999px;   /* pills, avatars, circular          */

	/* Shadows */
	--savvy-shadow-sm:    0 1px 2px rgba(0, 0, 0, 0.05);
	--savvy-shadow-md:    0 4px 6px -1px rgba(0, 0, 0, 0.1);
	--savvy-shadow-lg:    0 10px 15px -3px rgba(0, 0, 0, 0.1);
	--savvy-shadow-xl:    0 20px 25px -5px rgba(0, 0, 0, 0.1);
	--savvy-shadow-focus: 0 0 0 3px rgba(31, 111, 235, 0.4);

	/* Z-index scale. Ceiling is --savvy-z-wp-admin-bar (99999); do not exceed. */
	--savvy-z-base:         0;
	--savvy-z-dropdown:     100;
	--savvy-z-sticky:       200;
	--savvy-z-overlay:      300;
	--savvy-z-modal:        400;
	--savvy-z-popover:      500;
	--savvy-z-toast:        600;
	--savvy-z-wp-admin-bar: 99999;

	/* ── Canonical ecosystem role tokens (W14-2B) ────────────────────────
	   Declared under their CANONICAL --af- names, NOT given second --savvy-
	   names. Ruled at ecosystem Wave 13Y section 1.1, and WP UI Specification
	   section 25 states the same reason: a second name for one value is the
	   drift class the naming convention exists to prevent. WordPress family
	   token builds emit these under their canonical names alongside the
	   --savvy- set.

	   VALUES: Public Website Specification section 6.2.5 and 6.2.6, adopted
	   for this family by WP UI Specification section 25.2. Each holds ONE
	   value in BOTH modes and MUST NOT be given a dark variant - that is what
	   makes it a fixed role token. Nothing re-points them in any media block.

	   Read-only for consumers: name and role are contractual, value is not
	   (WP UI Specification section 2.5). */

	/* A border whose presence identifies a control - inputs, selects,
	   textareas, checkboxes, radios, segmented controls, unfilled buttons.
	   4.40:1 light and 3.04:1 dark against the 3:1 SC 1.4.11 floor. The
	   DECORATIVE border token is --savvy-color-border and stays legitimate
	   for dividers, table rules and card edges, which SC 1.4.11 exempts.
	   The test, verbatim: if removing the border would make the control
	   unrecognisable it is border-control, otherwise it is the neutral. */
	--af-color-border-control: #6B7280;

	/* The focus indicator is TWO-TONE and both halves are required. No single
	   fixed value clears 3:1 on every surface: #FFFFFF measures 2.15:1 on
	   dark amber, #0F172A measures 1.07:1 on the dark page background. The
	   two halves separate from each other at 17.85:1. */
	--af-color-focus-ring-fixed-outer: #FFFFFF;
	--af-color-focus-ring-fixed-inner: #0F172A;

	/* Ink on a fill that does NOT invert with the mode. A mode-scoped ink on
	   a non-inverting fill moves while its background stands still and can
	   converge on it - measured at 1.00:1, exactly invisible. */
	--af-color-on-brand-primary: #FFFFFF;
	--af-color-on-brand-primary-muted: #94A3B8;
	--af-color-on-brand-secondary: #111827;
}

/* ==========================================================================
 * BANDS DECLARE THEMSELVES — the base-layer remap (W14-2B)
 * Public Website Specification §6.2.6, adopted for this family by WP UI
 * Specification §25.4.
 *
 * A surface whose fill does NOT invert with the mode must declare itself, so
 * the base layer can remap the interactive roles over it. THIS IS THE ONE
 * RULE. §25.4 is explicit that it is a base-layer remap and NOT a sweep over
 * elements: "a per-element approach re-opens the defect the moment a
 * component is added."
 *
 * WHY THIS SHIPS WITH ONE ELEMENT DECLARING A BAND, AND WHY THAT NUMBER WAS
 * WRONG ONCE. Section 5.20 requires an audit, not an assumption. The first
 * pass enumerated every form control, recorded the fill behind each, and
 * concluded that all of them sit on neutral, mode-inverting surfaces -- and
 * WROTE THAT CONCLUSION HERE AS FACT. The W14-2B design review falsified it:
 * the Preset B / flat banner in assets/css/savvyvault-forms.css fills with
 * linear-gradient(90deg, var(--sv-form-accent), #1e40af), neither stop
 * inverts, and the signup form renders inside it. Measured, border-control
 * #6B7280 on that fill: 1.07:1 on the #2563eb stop, 1.81:1 on the #1e40af
 * stop, against the 3:1 floor of SC 1.4.11.
 *
 * That surface now declares data-af-band="dark", emitted by
 * PlacementEngine::band_attr(). Every OTHER form control in the plugin does
 * sit on a neutral inverting surface, so the token alone discharges
 * SC 1.4.11 there and no further band is owed.
 *
 * THE CORRECTION IS RECORDED RATHER THAN THE SENTENCE SILENTLY REPLACED,
 * because a wrong audit result written into a token file is more durable than
 * the defect it hides -- it is the reason the band would not have shipped.
 *
 * TWO CASES ARE OPEN AND ARE RECORDED RATHER THAN GUESSED, and the second was
 * found by the W14-2B design review after the first was written:
 *   (a) --sv-form-bg on the placement surface, and
 *   (b) card_bg / bg_color on the three standalone subscriber pages
 *       (marketing/class-page-design.php), which pass through
 *       sanitize_hex_color() with no contrast constraint at all — an operator
 *       setting card_bg near #6B7280 takes that control border to ~1:1.
 * Both are ADMINISTRATOR-CHOSEN hexes. Nothing at author time knows whether
 * either is neutral, so nothing at author time can declare its band. Deciding
 * it needs a luminance test at render, which is PHP behaviour and outside this
 * iteration. See docs/2026-09-02-W14-2B-QA.md.
 *
 * BOTH CASES CLOSED AT W14-13 (2026-09-20), by the luminance test this comment
 * said was needed: \Afsavvy\Vault\SurfaceInk, the one definition in the
 * plugin, called from PlacementEngine::surface_decision(). The sentence above
 * that "every OTHER form control in the plugin does sit on a neutral inverting
 * surface" WAS ALSO TOO NARROW — W14-13's census found our own ink over an
 * operator-chosen fill at ELEVEN sites across seven files, including the
 * signup submit button, the portal primary button and premium badge, the
 * portal and chrome navigation fills, the admin calendar's category chips and
 * the three standalone marketing pages. See docs/2026-09-20-W14-13-QA.md for
 * the population and the measurements. The correction is recorded rather than
 * the sentence silently replaced, for the reason the paragraph above gives.
 * ONE SPECIFICITY CAVEAT, stated because it is not obvious and nothing
 * exercises the rule yet. `[data-af-band="dark"]` is (0,1,0), the same weight
 * as `.savvy-vault-portal`, and portal.css is enqueued AFTER this file — so a
 * band declared ON the portal wrapper itself would lose to the wrapper's own
 * token declarations. A band declared on a DESCENDANT works, because the
 * nearer declaration wins for an inherited custom property, and a descendant
 * is what a band actually is: a coloured section or fill inside the page, not
 * the page wrapper. Verified by reading the cascade, NOT by rendering.
 * ========================================================================== */
[data-af-band="dark"] {
	--af-color-border-control: var(--af-color-on-brand-primary);
	--savvy-color-primary: var(--af-color-on-brand-primary);
	--savvy-color-primary-hover: var(--af-color-on-brand-primary-muted);
	/* The portal reads its OWN ink tokens and never reads --savvy-color-primary,
	   so remapping that alone would have left a portal band's links at
	   #1a4f8f on a navy fill — the exact defect §6.2.6 exists to prevent, and
	   it would have been latent rather than visible because nothing declares a
	   band yet. Found by the W14-2B design review. */
	--savvy-portal-accent: var(--af-color-on-brand-primary);
	--savvy-portal-accent-hover: var(--af-color-on-brand-primary-muted);
	--savvy-portal-ink: var(--af-color-on-brand-primary);
	--savvy-portal-text: var(--af-color-on-brand-primary);
	/* The placement surface's own ink token. Measured on the one banded fill
	   in this plugin (the Preset B banner gradient): the default #1f2329 is
	   3.05:1 on the #2563eb stop and 1.81:1 on the #1e40af stop, both under
	   SC 1.4.3's 4.5:1 for body text. on-brand-primary #FFFFFF is 5.17:1 and
	   8.72:1 on the same two stops. */
	--sv-form-ink: var(--af-color-on-brand-primary);
	--sv-form-border: var(--af-color-on-brand-primary);
}

/* ===========================================================================
 * W14-13 — `light`, AND IT IS OURS RATHER THAN THE ECOSYSTEM'S.
 *
 * THE TWO OPEN CASES RECORDED ABOVE ARE NOW CLOSED, and closing them needed a
 * band value that does not exist. Canonical Public Website Specification
 * section 6.2.6 and WP UI Specification section 25.4 close the vocabulary at
 * TWO declarations, and each is defined by a canonical PALETTE FAMILY:
 * `dark` applies to brand-primary-500/-700/-900 fills, `amber` to
 * brand-secondary-500/-700. The design-system export says the same thing at
 * its D23. NEITHER IS DEFINED FOR AN ARBITRARY OPERATOR HEX — and section 25.4
 * still says a surface that does not invert MUST declare itself.
 *
 * Ivy ruled on 2026-09-20 that a light non-amber operator fill declares
 * `light`, carrying EXACTLY the amber row's remap set (all four interactive
 * roles to on-brand-secondary — there is no muted companion on a light band
 * and inventing one is not ours to do), and that the gap goes to the
 * design-system owner in writing rather than being absorbed silently. See
 * docs/2026-09-20-OUTBOUND-DESIGN-SYSTEM-BAND-VOCABULARY.md.
 *
 * THE TWO REJECTED OPTIONS, recorded so they are not re-proposed. Reusing
 * `amber` on a fill that is not amber makes the attribute assert something
 * false, and section 10.4's audit enumerates focusable elements BY BAND, so a
 * false band is a wrong answer rather than a harmless label. Declaring no band
 * leaves section 25.6's machine-checkable half — every non-inverting fill
 * declares data-af-band — with nothing to see on the exact surface it exists
 * for.
 *
 * NOTHING IN THIS PLUGIN EMITTED `light` BEFORE THIS RELEASE, so the rule adds
 * no rendered change to any surface that was not already being decided.
 * ======================================================================== */
[data-af-band="light"],
[data-af-band="amber"] {
	--af-color-border-control: var(--af-color-on-brand-secondary);
	--savvy-color-primary: var(--af-color-on-brand-secondary);
	--savvy-color-primary-hover: var(--af-color-on-brand-secondary);
	--savvy-portal-accent: var(--af-color-on-brand-secondary);
	--savvy-portal-accent-hover: var(--af-color-on-brand-secondary);
	--savvy-portal-ink: var(--af-color-on-brand-secondary);
	--savvy-portal-text: var(--af-color-on-brand-secondary);
	--sv-form-ink: var(--af-color-on-brand-secondary);
	--sv-form-border: var(--af-color-on-brand-secondary);
}

/* ===========================================================================
 * W14-13 — A CONTROL THAT PAINTS ITS OWN OPAQUE GROUND IS NOT ON THE BAND.
 *
 * THIS IS A CORRECTION TO W14-2B AND THE DEFECT IT FIXES HAS BEEN SHIPPING
 * SINCE THAT RELEASE. The block above remaps --af-color-border-control to
 * on-brand-primary #FFFFFF inside a dark band, on the reasoning that a control
 * sitting on a brand fill needs fixed ink. Correct -- for a control that sits
 * on the fill. `.savvy-vault-signup__input` DOES NOT: it declares
 * `background: var(--sv-form-field-bg, Field)` at http/class-rest-signup.php
 * and `#14171b` in dark mode, so its ground is its own and it is white.
 *
 * Measured: the remapped #FFFFFF border against that white ground is
 * 1.0000:1 -- EXACTLY INVISIBLE -- where the canonical #6B7280 it replaced
 * measured 4.8345:1. W14-2B recorded the band as taking this border from
 * 1.07:1 to 5.17:1; that measurement was taken against the BANNER GRADIENT,
 * which is not what the border is adjacent to. The band did not fix the
 * control border on the shipped Preset B banner. It broke it.
 *
 * W14-13 widened banding to every operator-pinned --sv-form-bg surface, which
 * widened this defect with it, which is how it was found. The scope is one
 * rule and it is in the base layer: it does not sweep elements, it states
 * which elements the band's ground is not behind.
 *
 * THE LITERAL IS THE CANONICAL VALUE, restated rather than read from
 * --af-color-border-control, because reading that token is the thing being
 * undone. Public Website Specification 6.2 declares it as ONE value in both
 * modes, so there is no mode branch to keep in step.
 * ======================================================================== */
[data-af-band] .savvy-vault-signup__input {
	--af-color-border-control: #6B7280;
}

/* THE FOCUS RING IS DELIBERATELY NOT REMAPPED HERE, and §6.2.6's band table
   lists it. The table's remap assumes the MODE-SCOPED ring; this plugin adopted
   the same section's other mechanism instead — the two-tone FIXED indicator,
   which §6.2.6 measures on all sixteen band-and-mode combinations and states
   works on any ground. Remapping it to a single fixed ink would reduce a
   two-tone indicator to one tone, which the same section says may not be done:
   "Neither half may be removed." Measured: the outer half is 10.36:1 on a
   brand-primary-500 fill and 3.19:1 on light amber, so no band leaves the
   indicator below the floor. Recorded as a deliberate divergence from a
   normative row, not an omission. */

/* Hover inside a band changes underline thickness, not colour (§6.2.6). On
   amber no second fixed ink is both safe and distinguishable. */
[data-af-band] a:hover {
	text-decoration-thickness: 2px;
}
