/* =====================================================================
   Fujairah Web Components - Design Tokens
   Unified design tokens for the Department of Finance in the Emirate of Fujairah

   Every value here can be changed from one place, so the look of all the
   components changes with it. The components read these variables via
   var(--fj-...) and pierce the Shadow DOM automatically, so this is the
   adopted way to unify the identity across all the sites.

   The style: flat and simple (Flat / Minimalism). No shadows at all.
   Separation between surfaces is by tone difference first: the page background
   is light gray (#F4F4F5) and the cards are white (#FFFFFF). Hairline borders
   are only for documented exceptions such as inputs, floating panels, and
   separators.

   Sources of the values:
   - The base colors (the terracotta red ramp + the dark background #161616
     + the light text color #CDCDCE): the official adopted Fujairah identity
     palette.
   - Neutrals, surfaces, sizes, and spacing: derived from the adopted
     reference site images (the UAE government style: generous spaces and
     large sizes).
   - Dark mode values: built on research into dark mode best practices, not
     on inverting the light mode values. The applied principles:
       1) Avoid pure black (a soft black #161616 prevents text halation).
       2) Elevation by tone not by shadow: higher surfaces are lighter in
          graded steps.
       3) Lower color saturation in the dark (saturated colors vibrate on
          dark).
       4) Text is not pure white (#CDCDCE) to reduce visual bleed.
   First principle: accessibility. Every (text/background) pair adopted here
   is tested against the WCAG contrast ratio. The reference ratios are noted
   next to the color roles.
   ===================================================================== */

:root {
  /* -------------------------------------------------------------------
     1) Base colors (Primitives) - a fixed palette that does not change by theme
     ------------------------------------------------------------------- */

  /* The primary color: the terracotta red (Fujairah identity).
     The 700 shade (#A4312A) is the brand anchor. White text over it is
     6.9:1 (passes AA). 500 has white text over it at 4.45:1 (fails), so it is
     not used as a fill with white text. */
  --fj-color-primary-100: #f8e7e2;
  --fj-color-primary-200: #eeccc4;
  --fj-color-primary-300: #dda79d;
  --fj-color-primary-400: #cc7d71;
  --fj-color-primary-500: #c25647;
  --fj-color-primary-600: #b24034;
  --fj-color-primary-700: #a4312a; /* brand anchor */
  --fj-color-primary-800: #7d2921;
  --fj-color-primary-900: #571d19;

  /* Neutrals. Pinned to the two identity colors:
     300 = the light text (#CDCDCE), 900 = the dark background (#161616).
     50 = the page background in light (#F4F4F5). The rest is derived for a
     consistent ramp. */
  --fj-color-neutral-0: #ffffff;
  --fj-color-neutral-50: #f4f4f5;  /* page background (light) */
  --fj-color-neutral-100: #ececee;
  --fj-color-neutral-200: #dedee0;
  --fj-color-neutral-300: #cdcdce; /* the light text color from the identity */
  --fj-color-neutral-400: #a0a0a2;
  --fj-color-neutral-500: #6e6e70;
  --fj-color-neutral-600: #525254;
  --fj-color-neutral-700: #3a3a3c;
  --fj-color-neutral-800: #262627;
  --fj-color-neutral-900: #161616; /* the dark background from the identity */
  --fj-color-white: #ffffff;
  --fj-color-black: #161616;

  /* A sand/cream color: background for the featured panels and the engraved
     graphics. */
  --fj-color-sand: #f8f4ec;

  /* -------------------------------------------------------------------
     2) Color roles - light mode (Light roles)
        Explicit values for each role. Dark mode redefines them in full at the
        end of the file.
     ------------------------------------------------------------------- */

  /* Surfaces: separation by tone first, not by shadow. Borders are only for
     documented exceptions. background = page background (gray), surface = the
     card (white). */
  --fj-color-background: #f4f4f5;       /* page background (canvas) */
  --fj-color-surface: #ffffff;          /* the card */
  --fj-color-surface-raised: #ffffff;   /* dropdown/window, separated by the border */
  --fj-color-surface-sunken: #ececee;   /* sunken areas, code blocks */
  --fj-color-surface-sand: var(--fj-color-sand); /* featured panels */

  /* Background scale: named emphasis steps for section / block backgrounds,
     from the lightest default up to the inverted dark. A surface picks a level
     by name instead of a one-off hex, so the page reads as one system. */
  --fj-color-bg-default: #ffffff;       /* the base, lightest background */
  --fj-color-bg-subtle: #fafafa;        /* one hair off white */
  --fj-color-bg-muted: #f4f4f5;         /* the quiet grey canvas step */
  --fj-color-bg-emphasized: #e4e4e7;    /* a stronger grey band */
  --fj-color-bg-inverted: #161616;      /* dark band (pairs with light text) */

  /* The UA colour scheme for native controls (the OS-rendered parts our CSS
     cannot reach: the fj-select option popup, number spinners, autofill). It
     tracks the design-system theme, not the OS, so a forced-dark stage gets a
     dark native dropdown instead of a white one. Re-declared in the dark block. */
  --fj-color-scheme: light;

  /* Transparent glass surfaces over images and the neutral canvas. The material
     is intentionally quiet: the component sits directly on the backdrop, with
     separation from frost, a soft fill, and a light rim, not from a white card
     or a heavy shadow. */
  /* Fill: a faint COOL tint (recipe D's hue), not white, and translucent enough
     that the backdrop blur still reads as FROST over a photo. The cool hue is the
     key: white-on-white vanishes, but any non-white tint reads on a white / grey
     canvas, so the glass keeps presence on flat surfaces (recipe D's body) AND
     stays see-through for the frost over media. The old opaque white (0.62) +
     sheen stacked to ~93% and painted over the blur (a "normal layer"); a pure
     transparent white (0.46) read faint on white. This cool middle is the
     best-of-both. */
  /* Fill stays at the AA-safe translucency: recipe D's body wants a touch more
     opacity for presence on white, but pushing --fj-color-glass darker drops the
     muted-text-on-glass pair (dropdown filler #6e6e70) below the 4.5:1 floor
     (rule 7). D's presence is carried instead by the bright specular rim, the
     bevel gleam, and the single clean edge (the doubled-edge geometry bug fixed
     in fj-button), so the fill keeps this contrast-proven value. Lever if more
     body is ever wanted: darken --fj-color-glass-text-muted on glass first. */
  --fj-color-glass: rgba(235, 239, 247, 0.54);        /* pill/bar over an image */
  --fj-color-glass-strong: rgba(238, 242, 250, 0.7);  /* active element */
  --fj-color-glass-soft: rgba(234, 238, 247, 0.46);   /* softer glass: search panel container */
  /* Floating dropdown / language menu panel. A transient menu floats over
     arbitrary page content (a hero photo, a busy section), so unlike the quiet
     bar fill it must read as a clearly bounded surface, not let the backdrop
     bleed through and muddy the labels. More opaque than --fj-color-glass for
     that figure-ground separation while still frosting (separation by tone, no
     shadow - rule 11). Its items use full-strength --fj-color-glass-text, so the
     extra opacity only lifts legibility (the 0.54 contrast pin is a muted-text
     concern that does not apply here). Dark already reads at 0.76, so its dark
     value is held identical (light-only fix). */
  --fj-color-glass-panel: rgba(238, 242, 250, 0.74);
  --fj-glass-surface:
    linear-gradient(
      var(--fj-glass-light-angle, 225deg),
      rgba(248, 250, 254, 0.32) 0%,
      rgba(240, 243, 250, 0.16) 48%,
      rgba(228, 232, 242, 0.1) 100%
    );
  --fj-glass-surface-soft:
    linear-gradient(
      var(--fj-glass-light-angle, 225deg),
      rgba(255, 255, 255, 0.78) 0%,
      rgba(255, 255, 255, 0.6) 46%,
      rgba(222, 222, 224, 0.34) 100%
    );
  --fj-color-glass-text: var(--fj-color-text);
  --fj-color-glass-text-muted: var(--fj-color-text-muted);
  --fj-color-glass-control-hover: var(--fj-color-glass-strong);
  --fj-color-glass-control-active: var(--fj-color-glass-strong);
  --fj-color-glass-control-active-text: var(--fj-color-heading);
  /* Glass list item hover (dropdown / mega / nav links): a neutral grey wash, a
     solid step darker than the white panel so it clearly reads. Design-system
     rule: hover feedback is grey on the background (or the icon / border),
     never a pink or translucent-red brand wash. Paired with the heading colour
     for crisp neutral text. Both are re-declared in the dark block (an
     indirection token resolves at its declaring scope) and gated in
     tokens.contrast.test.ts. */
  --fj-color-glass-item-hover: #e0e0e3;
  --fj-color-glass-item-hover-text: var(--fj-color-heading);
  /* Icon button "selected" toggle (the white highlight chip in the floating
     footer's active accessibility tool). A solid light chip with the brand-fill
     red as its foreground, so the 24px icon clears the WCAG UI threshold on the
     chip in both themes (a4312a on white = 6.9:1; b24034 on the dark chip
     #ececee = 4.8:1). It is a solid pair, so it is gated in
     tokens.contrast.test.ts. */
  --fj-color-glass-control-selected: var(--fj-color-white);
  --fj-color-glass-control-selected-fg: var(--fj-color-brand);
  --fj-gradient-header: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.5) 0%,
    rgba(255, 255, 255, 0) 100%
  );
  --fj-gradient-header-scroll: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.32) 0%,
    rgba(255, 255, 255, 0.2) 56%,
    rgba(255, 255, 255, 0) 100%
  );
  --fj-gradient-header-blur-mask: linear-gradient(
    180deg,
    #000 0%,
    rgba(0, 0, 0, 0.6) 56%,
    rgba(0, 0, 0, 0) 100%
  );
  --fj-header-scroll-blur: 30px;
  /* The frosted glass blur: blurs whatever is behind the pill so it looks like
     glass. The essence of the glass identity. Applied via backdrop-filter on
     the pills. */
  --fj-blur-glass: 20px;
  /* Glass saturation: raises the vividness of the colors behind the pill so it
     reads as glass not as fog. */
  --fj-glass-saturate: 1.6;
  /* Glass lighting (Light in Figma): makes the glass look like glass even on a
     white background. It is not a shadow (box-shadow forbidden) but the glass
     material itself:
       - rim: a luminous lens edge at -45 degrees (light from the top start),
         applied to the border only via a mask so the top edge glows and the
         opposite one dims.
       - sheen: a soft surface gleam flowing from the top start (a light
         reflection).
     Refraction/dispersion over images is added by an SVG filter; this lighting
     works in all browsers and on any background. */
  /* Gradient stroke (the recipe D identity, owner-approved 2026-06-16 to apply
     recipe D in full for light mode): a bright specular highlight at the
     light-angle start that sweeps to a defined cool blue-grey at the far end.
     This is D's literal stroke (white 0.95 -> cool 0.45 -> deeper cool 0.6).
     An earlier build muted the bright end to a light-cool to stop it washing out
     on a flat white canvas, but that read greyer/flatter than D and was not what
     the owner approved. The doubled-edge the owner flagged was a GEOMETRY bug
     (the button's transparent border displaced this masked rim 1px inside the
     fill edge), fixed in fj-button.styles.ts, NOT a reason to dull the stroke.
     The white start carries the glass over a photo; the cool mid / end stops
     keep the edge defined on flat white. Stops only: the direction stays on
     --fj-glass-light-angle so RTL flips automatically. */
  --fj-glass-rim:
    linear-gradient(
      var(--fj-glass-light-angle, 225deg),
      rgba(255, 255, 255, 0.95) 0%,
      rgba(190, 200, 220, 0.45) 50%,
      rgba(150, 160, 185, 0.6) 100%
    );
  /* Inner bevel: a thin inner top gleam that fakes the thickness of a glass pane
     (the Figma inner highlight) without box-shadow (rule 11). Vertical (physical
     top), so it is RTL-safe. Layered above the sheen on .fj-glass::before. A
     surface that must not carry a bright top edge (the floating footer) sets this
     to none. */
  --fj-glass-bevel:
    linear-gradient(to bottom, rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0) 50%);
  --fj-glass-sheen: var(--fj-glass-surface);
  --fj-glass-rim-width: 1px;

  /* Search field "listening" state: a red glow bleeds in from the action-button
     edge while the voice input is active. The angle (90deg = toward the inline
     start in RTL, behind the mic) is flipped for LTR in the component. The
     stops use theme roles so it adapts to dark automatically. */
  --fj-gradient-field-listening: linear-gradient(
    90deg,
    var(--fj-color-surface) 88%,
    var(--fj-color-brand) 100%
  );

  /* Hero slider glass: values taken from the approved hero slider Figma slice. */
  --fj-hero-glass-blur: 5px;

  /* Floating footer (fj-floating-footer): a persistent quick-access bar that
     floats at the viewport bottom (it reveals after the hero and hides near the
     real page footer). The band is a soft wash with a progressive backdrop blur
     that is strongest at the bottom edge and fades up, the same masked-backdrop
     technique the header uses, inverted. The pill itself reuses the shared glass
     material (.fj-glass). Values trace the approved Figma slice, aligned to the
     system scale. */
  /* The MAX backdrop blur radius, at the very bottom edge of the screen. The
     frost is an EVEN gradient: this full radius at the bottom, easing smoothly and
     naturally up to 0 (sharp) at the top of the zone, spread across the whole band
     (not held flat then dropped). Kept light on purpose so it reads as a gentle
     frost, not a heavy haze. It samples the real backdrop, so it never adds white:
     dark content frosts dark, light content frosts light. */
  --fj-footer-blur: 14px;
  /* The height of the progressive-blur BACKGROUND zone, measured up from the very
     bottom of the screen. The frost is glued to the viewport bottom and fills this
     zone independently of the floating bar (the bar floats over it). Kept tight so
     it reads as a frosted band hugging the footer, NOT a tall haze: the bar height
     (space-12) + the float gap (space-3) + a short fade above the pill (space-5) =
     80px. This is the shortest zone that still shows the progressive gradient (at
     ~64px the gradation collapses into a flat blur), so it is the floor that
     reconciles "as short as the footer" with "keep the gradient". */
  --fj-footer-blur-zone: calc(
    var(--fj-space-12) + var(--fj-space-3) + var(--fj-space-5)
  );
  --fj-footer-bar-inline-size: 600px;
  /* The floating bar's own fill. Footer-scoped (not the shared --fj-color-glass)
     so it can be a touch more transparent than the shared glass: over busy media
     the bar read as a heavy white veil, so the white is dialed back here while
     staying opaque enough that the red tool icons keep clearing the 3:1 icon
     contrast on the worst-case (dark, busy) backdrop. Dark mode keeps the shared
     dark glass tone (re-declared in the dark block). Light only is lightened. */
  /* Raised from 0.58: at 0.58 the pill went translucent grey over a dark backdrop,
     dropping the red tool icons to ~3:1 (the bare floor) so the content read as
     low-contrast. 0.82 keeps a clear glass feel while giving the pill a solid
     enough surface that the icons and labels clear contrast on any backdrop. */
  --fj-footer-bar-glass: rgba(255, 255, 255, 0.82);
  /* The band has NO painted colour wash and NO brightness: a painted gradient
     composites source-over (background-blind, always lightens) and a
     multiplicative brightness reads as a white lift on dark. The band's whole
     effect is a PROGRESSIVE backdrop blur (the .blur-stack layers in the
     component): each layer carries a fraction of --fj-footer-blur revealed in a
     feathered band, so the blur RADIUS gradates from strongest just above the
     pill to nothing at the top. It samples the real backdrop and adds no colour,
     so it frosts dark content dark and light content light, never white. The
     per-layer radii and bands live in the component styles (the technique's
     internal structure); --fj-footer-blur is the single max radius they scale
     from. Theme-agnostic, so nothing here is re-declared in the dark block. */
  /* The floating bar is lit FROM BELOW so it reads as rising from the bottom of
     the screen, which is what sets it apart from the side-lit button / nav glass
     (that diagonal rim is what made the pill look "the same as the others"). Both
     the sheen and the rim are bottom-weighted "to top" gradients: brightest along
     the bottom edge and fading up. RTL is automatic (a vertical gradient has no
     inline side). */
  --fj-footer-bar-glass-sheen: linear-gradient(
    to top,
    rgba(255, 255, 255, 0.55) 0%,
    rgba(255, 255, 255, 0.22) 48%,
    rgba(228, 228, 230, 0.08) 100%
  );
  /* The pill's own rim, replacing the shared diagonal rim: a bright bottom edge
     fading up to a faint top, so the glass reads as lit from below. */
  --fj-footer-bar-glass-rim: linear-gradient(
    to top,
    rgba(255, 255, 255, 0.95) 0%,
    rgba(255, 255, 255, 0.34) 42%,
    rgba(190, 200, 220, 0.12) 100%
  );

  /* Header brand pill: the .brand link at the top of the floating header
     (homepage) reads as a glass pill matching the floating footer's material,
     but lit from ABOVE (the brand sits at the top of the page, so the light
     pools along the top edge and fades down). Mirrors the footer-bar trio so
     a future change to one is mirrored on the other. */
  --fj-header-bar-glass: rgba(255, 255, 255, 0.82);
  --fj-header-bar-glass-sheen: linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.55) 0%,
    rgba(255, 255, 255, 0.22) 52%,
    rgba(228, 228, 230, 0.08) 100%
  );
  --fj-header-bar-glass-rim: linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.95) 0%,
    rgba(255, 255, 255, 0.34) 42%,
    rgba(190, 200, 220, 0.12) 100%
  );

  /* Service cards (fj-service-card). Two variants, both approved from the user's
     Figma:
       - glass: a frosted panel (translucent fill + backdrop blur) for surfaces
         over hero media. The glass exception: separation by frost, never a
         border. The whole card is a link; rest is frosted white, hover/focus
         turns frosted red with white content.
       - solid: a flat white card for sections on a light background. No blur (no
         GPU cost), no border, no shadow, and no lift. On hover/focus the neutral
         background and action cue change together. Solid reuses the semantic
         tokens (surface, text, text-muted, brand, brand-hover) directly. */
  --fj-service-card-blur: 10px;
  --fj-service-card-radius: var(--fj-radius-sm);
  --fj-service-card-padding: var(--fj-space-5);
  /* Tile variant (link tile for a section of partner or destination links): a
     stacked card with the icon in a soft square, the title, a muted
     description, and an external-link action. Sizes are in tokens; the square
     tint is a quiet brand wash over the surface. */
  --fj-service-card-tile-padding: var(--fj-space-6);
  --fj-service-card-tile-gap: var(--fj-space-4);
  --fj-service-card-tile-icon-box: var(--fj-space-12);
  --fj-service-card-tile-icon-size: var(--fj-space-5);
  --fj-service-card-tile-icon-radius: var(--fj-radius-sm);
  --fj-color-service-card-tile-icon-bg: color-mix(in srgb, var(--fj-color-brand-soft) 40%, var(--fj-color-surface));
  /* Glass rest: frosted white. */
  --fj-color-service-card: rgba(255, 255, 255, 0.74);
  --fj-color-service-card-text: var(--fj-color-text);
  --fj-color-service-card-text-muted: #404040;              /* Figma description color */
  --fj-color-service-card-solid-accent: var(--fj-color-brand);
  --fj-color-service-card-solid-hover-accent: var(--fj-color-brand-hover);
  --fj-color-service-card-accent: var(--fj-color-brand);    /* icon + action cue */
  /* Glass hover / focus: the frost intensifies to a brighter neutral (no red
     wash - design-system hover rule: feedback on the background, never a brand
     fill and never a lift). The text and icon stay at their rest colours. */
  --fj-color-service-card-hover: rgba(255, 255, 255, 0.9);
  --fj-color-service-card-hover-text: var(--fj-color-service-card-text);
  --fj-color-service-card-hover-text-muted: var(--fj-color-service-card-text-muted);
  --fj-color-service-card-hover-accent: var(--fj-color-service-card-accent);
  /* Shared interactive frame: kept only for documented exceptions that still
     need a drawn frame. Basic cards and logo tiles do not use it. */
  --fj-color-interactive-frame-hover: var(--fj-color-primary-500);
  --fj-color-interactive-frame-focus: var(--fj-color-primary-500);
  --fj-gradient-hero-title-overlay: linear-gradient(
    0deg,
    rgba(255, 255, 255, 0.14) 0%,
    rgba(255, 255, 255, 0.12) 70.67%,
    rgba(255, 255, 255, 0) 100%
  );
  --fj-hero-title-overlay-blur: 14px;
  --fj-gradient-hero-title-contrast: linear-gradient(
    0deg,
    rgba(0, 0, 0, 0.74) 0%,
    rgba(0, 0, 0, 0.52) 42%,
    rgba(0, 0, 0, 0.18) 74%,
    rgba(0, 0, 0, 0) 100%
  );
  --fj-color-hero-image-overlay: rgba(0, 0, 0, 0.4);
  /* Cover card progressive blur: the MAX radius of the layered .blur-stack in the
     component. Each layer is masked with a smooth linear gradient that runs the
     full veil zone (see .blur-layer in fj-cover-card.styles.ts), so the blur
     RADIUS gradates from strongest just behind the caption to nothing by the
     upper edge of the veil zone - a true progressive frost that fades up like
     the veil, instead of one flat blur or a thin feather strip. The blur is
     decorative: when backdrop-filter is unsupported the caption veil still
     carries the AA contrast (rule 12). Tuned from 14px down to 8px on 2026-06-30
     after the owner flagged the upper image as too blurred; the smooth linear
     masks keep the soft falloff while the veil zone now extends well above the
     caption (see --_cover-card-veil-block-size in the component styles) so the
     falloff covers a real slice of the image area, not a hairline feather. */
  --fj-cover-card-blur: 8px;
  --fj-gradient-hero-title-blur-mask: linear-gradient(
    0deg,
    #000 0%,
    rgba(0, 0, 0, 0.86) 35%,
    rgba(0, 0, 0, 0.46) 66%,
    rgba(0, 0, 0, 0) 100%
  );
  --fj-hero-title-overlay-block-size: 32svh;
  --fj-hero-title-overlay-padding: var(--fj-space-10);
  --fj-hero-title-overlay-gap: var(--fj-space-10);
  /* Hero section mobile cards carousel: on phones the service-card row turns
     into a single horizontal free-scroll carousel. Each card takes this share
     of the row so the next card peeks (~20%), which signals the horizontal
     scroll instead of a tall vertical stack that overflows the viewport. The
     single-card-with-peek pattern follows Material 3 carousel guidance and
     Chrome's default carousel scroll step (~85% of the row); tuned to 80% for
     a clearer peek on Arabic RTL phones. */
  --fj-hero-cards-carousel-item-size: 80%;
  --fj-hero-indicator-size: 5px;
  --fj-hero-indicator-active-block-size: 20px;
  --fj-hero-indicator-offset-inline: var(--fj-space-10);
  --fj-hero-indicator-offset-block: var(--fj-space-10);
  /* The optional autoplay pause/play button. It stacks below the dots on the
     same vertical axis, so it shares the dots' 24px hit-target footprint and
     stays a comfortable WCAG 2.5.8 target. The gap is the vertical breathing
     room between the last dot and the button. */
  --fj-hero-indicator-control-size: var(--fj-space-6);
  --fj-hero-indicator-control-gap: var(--fj-space-3);
  --fj-color-hero-indicator-idle: rgba(255, 255, 255, 0.42);
  --fj-hero-dots-contrast-inline-size: min(12rem, 24%);
  --fj-hero-dots-contrast-block-size: min(16rem, 38%);
  --fj-gradient-hero-dots-contrast-inline-end: linear-gradient(
    270deg,
    rgba(0, 0, 0, 0.24) 0%,
    rgba(0, 0, 0, 0.12) 48%,
    rgba(0, 0, 0, 0) 100%
  );
  --fj-gradient-hero-dots-contrast-inline-start: linear-gradient(
    90deg,
    rgba(0, 0, 0, 0.24) 0%,
    rgba(0, 0, 0, 0.12) 48%,
    rgba(0, 0, 0, 0) 100%
  );
  --fj-gradient-hero-indicator-active: linear-gradient(
    180deg,
    rgba(179, 32, 36, 0.5) 0%,
    #b32024 91.67%
  );
  --fj-hero-slider-block-size: 760px;
  --fj-hero-slider-min-block-size: 560px;
  --fj-hero-slider-content-max-inline-size: 720px;
  --fj-hero-slider-image-scale: 1.08;
  --fj-hero-slider-motion-duration: 700ms;
  --fj-hero-slider-slide-shift: var(--fj-space-8);
  --fj-ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --fj-ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  /* Symmetric ease-in-out (easeInOutCubic). The ease-out curves above are
     front-loaded (most of the motion happens in the first third), which reads as
     a fast pop. This curve spends the most time in the visible mid-range, so a
     grow / collapse reveal moves legibly across its whole length and reads
     equally clearly opening and closing. Used by the corner-morph panel reveal. */
  --fj-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);

  /* Magic-move slide (the active indicator that glides between items). Shared by
     fj-side-nav (the vertical red pill) and fj-nav (the horizontal selected wash):
     a quick CSS transition on the system ease (--fj-ease-out-quart), kept short so
     the move reads as fast and snappy, not draggy. It is duration only (it pairs
     with the ease token), so do not append another ease to it. */
  --fj-magic-move-duration: 170ms;

  /* Footer (fj-footer / fj-footer-column / fj-newsletter). The footer is a
     distinct band: a quiet surface a step different from the page, carrying a
     faint engraved geometric motif (decorative SVG strokes) under a near-opaque
     tint of the surface color. The newsletter field reuses the soft-glass +
     brand-pill control language, so its translucent fill flips with the theme
     for free. Dark values are re-declared in the dark scope below. */
  --fj-color-footer-surface: #e4e4e7;        /* the footer band (light) */
  /* Muted text on the footer band. The page-level --fj-color-text-muted (#6e6e70)
     is tuned for the white card and drops to 4.0:1 on the darker footer surface
     (fails AA), so the footer uses a darker muted that clears 4.5:1 on #e4e4e7
     (6.1:1). Gated in tokens.contrast.test.ts. */
  --fj-color-footer-text-muted: #52525b;     /* footer body / links / copyright (light) */
  --fj-footer-pattern-opacity: 0.4;          /* how visible the engraved motif is */
  /* The motif fades IN from the top: barely there along the footer's upper edge
     (behind the link columns, so the headings stay clean) and growing to full
     presence toward the lower edge. The absolute light/dark balance is set by the
     opacity token above; the mask is re-declared in the dark scope so each theme
     can carry its own fade. */
  --fj-footer-pattern-mask: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0) 0%,
    rgba(0, 0, 0, 0) 18%,
    #000 75%,
    #000 100%
  );
  --fj-newsletter-field-block-size: 64px;    /* the newsletter field height */
  --fj-newsletter-button-block-size: 48px;   /* the inner subscribe pill (also a 48px touch target) */

  /* Section header (fj-section-header). The composition is a small brand
     eyebrow, a large title, a signature decorative separator, and an optional
     bottom row (filter tabs + a trailing action). Most values map to the shared
     tokens; the values below that have no match on the scales live here as named
     component tokens, sourced from the approved Figma slice:
       - the two diamond markers on the separator (3px and 5px) are smaller than
         --fj-space-1 (4px), so they are off the spacing scale.
       - the fixed grey separator segment is 36px, between space-8 (32) and
         space-10 (40).
     The separator colours (grey -> --fj-color-border, red -> --fj-color-brand)
     and the rule thicknesses (--fj-border-width / --fj-border-width-thick) reuse
     the shared tokens, so the separator flips with the theme automatically.
     The title size is fluid but capped at the exact Figma value (32px = 2rem):
     it sits at 32px on tablet and desktop (it reaches the cap around 530px wide)
     and scales down toward 2xl (28px) on the narrowest phones, so the header is
     responsive while matching the spec at the design width. 2rem is the Figma
     32px itself, not an invented value; there is no 32px step on the size scale
     (2xl is 28, 3xl is 36), so the cap is a literal here. */
  --fj-section-header-marker-size: 3px;
  --fj-section-header-marker-accent-size: 5px;
  --fj-section-header-divider-segment: 2.25rem; /* 36px */
  /* The separator runs a fixed amount past the title's inline-end, so the rule
     reads a touch wider than the heading (a small typographic flourish) instead
     of being clipped to the exact text width. A fixed amount, so it stays
     consistent under short and long titles. */
  --fj-section-header-divider-overshoot: var(--fj-space-6); /* 24px past the title end */
  /* The eyebrow marks. The built-in line icon sits on the icon step (24px); a
     custom image or logo dropped into the eyebrow-icon slot is a small square
     thumbnail one step larger (32px) so an uploaded photo or wordmark reads. */
  --fj-section-header-icon-size: var(--fj-space-6); /* 24px built-in line mark */
  --fj-section-header-mark-size: var(--fj-space-8); /* 32px custom image thumbnail */
  --fj-section-header-title-size: clamp(
    var(--fj-font-size-2xl),
    1.4rem + 2.2vw,
    2rem
  );
  --fj-section-header-subtitle-size: clamp(
    var(--fj-font-size-xl),
    1.15rem + 1.1vw,
    1.5rem
  );
  --fj-section-header-action-inline-size: 18rem;

  /* Partners section (fj-partners-section). The card size and logo height come
     from the supplied partnership reference: each logo tile is 292px by 170px,
     with the logo capped at 70px block size so wide and tall marks stay visually
     balanced. The cloud values are the same system, relaxed for the open logo
     layout shown in the second reference. */
  --fj-partners-section-padding-block: var(--fj-space-24);
  --fj-partners-section-padding-inline: var(--fj-space-10);
  --fj-partners-section-gap: var(--fj-space-10);
  --fj-partners-track-gap: var(--fj-space-3);
  --fj-partners-edge-fade-size: var(--fj-space-12);
  --fj-partners-card-inline-size: 18.25rem; /* 292px */
  --fj-partners-card-block-size: 10.625rem; /* 170px */
  --fj-partners-card-padding-block: 3.125rem; /* 50px */
  --fj-partners-card-padding-inline: var(--fj-space-10); /* 40px */
  --fj-partners-logo-max-inline-size: 13.75rem; /* 220px */
  --fj-partners-logo-max-block-size: 4.375rem; /* 70px */
  --fj-partners-cloud-item-inline-size: 10rem;
  --fj-partners-cloud-item-block-size: var(--fj-space-24);
  --fj-partners-cloud-column-gap: var(--fj-space-8);
  --fj-partners-cloud-row-gap: var(--fj-space-4);
  --fj-partners-copy-inline-size: 22rem;
  --fj-partners-pattern-opacity: 0.16;

  /* Mega panel (fj-mega-panel / fj-mega-card). The card preview art is built only
     from brand tokens, so it flips with the theme and ships no raster asset. No
     cream: the previews are a brand family plus a cool neutral, never the warm
     sand wash.
       - sand: a soft brand-soft -> surface fade, the quietest brand preview.
       - brand: a diagonal brand-soft -> brand-rim wash, the strongest preview.
       - sky: a quiet surface -> sunken gradient for a calm documentation feel.
     The media opacity sets how present the preview reads as a featured-card
     backdrop (the value the earlier story markup used as a literal). The carousel
     item size aliases the hero value so a phone card row shows one card with the
     next peeking (rule #10). All three patterns reference role tokens that are
     re-declared in the dark block, so the previews re-resolve in dark for free. */
  --fj-mega-card-pattern-sand: radial-gradient(
    120% 100% at 100% 0%,
    var(--fj-color-brand-soft) 0%,
    var(--fj-color-surface) 100%
  );
  --fj-mega-card-pattern-brand: linear-gradient(
    135deg,
    var(--fj-color-brand-soft) 0%,
    var(--fj-color-brand-rim) 130%
  );
  --fj-mega-card-pattern-sky: linear-gradient(
    160deg,
    var(--fj-color-surface) 0%,
    var(--fj-color-surface-sunken) 100%
  );
  --fj-mega-card-media-opacity: 0.42;
  --fj-mega-cards-carousel-item-size: var(--fj-hero-cards-carousel-item-size);

  /* Mega nav (fj-mega-nav). The shared viewport morphs (width, height, and the
     horizontal slide under the active trigger) over this duration, paired with
     the system ease (--fj-ease-out-quart) so the move reads as one smooth gesture
     rather than a close-and-reopen. It is duration only (it pairs with the ease
     token), so do not append another ease to it. A touch longer than the base so
     the size morph has room to read; the opacity fade keeps the base timing. */
  --fj-mega-nav-morph-duration: 260ms;

  /* Tabs (fj-tabs). The row is bounded and scrolls horizontally on overflow.
       - max-inline-size caps a standalone row so it scrolls instead of growing.
         In the section header the row is already bounded by its flex cell, so
         this stays 100%; set it on a standalone fj-tabs to cap it and let the
         tabs scroll once they exceed the cap.
       - the scroll fade is the soft edge mask that signals more tabs lie past a
         clipped edge. It is scroll-state-aware (only an edge with content
         scrolled past it fades), so the first tab is never dimmed at rest. The
         size below is the fade width and is the knob for how soft the edge is. */
  --fj-tabs-max-inline-size: 100%;
  --fj-tabs-scroll-fade-size: var(--fj-space-6); /* 24px soft edge */

  /* Breadcrumb (fj-breadcrumb). The trail itself reuses the role tokens: in the
     default `surface` tone the links are --fj-color-text with the identity red on
     hover, the current page is --fj-color-text-muted, and the divider is the
     subtle text colour (decorative + aria-hidden, so WCAG-exempt). Those all flip
     with the theme for free, so they live on the shared scales, not here.
     The one value with no role match is the divider colour in the `media` tone
     (the trail placed over the hero image): a soft white that stays a touch
     quieter than the white crumbs beside it. It is theme-independent (white over
     a darkened band in both themes), and it is decorative + aria-hidden so it is
     not a contrast-gated pair. It mirrors the existing muted-white precedent
     (--fj-color-hero-indicator-idle). */
  --fj-color-breadcrumb-on-media-subtle: rgba(255, 255, 255, 0.6);

  /* Badge (fj-badge). A small, non-interactive informational pill that pairs a
     medium-weight label with a lighter value. The surface is a quiet neutral chip:
       - light: a grey one step darker than the page (#f4f4f5), so the pill reads
         as a filled chip. This is the value the approved Figma uses (it matches
         the footer band) but it carries its own name, so the badge is not coupled
         to the footer.
       - dark: a grey one step LIGHTER than the dark page (#161616), so the chip
         still reads as a raised pill, not a sunken hole (a sunken token would go
         darker than the page in dark and disappear). It sits at the
         raised-surface level. Re-declared in the dark block so the contrast gate
         resolves the dark value.
     The pair is gated in tokens.contrast.test.ts: full-strength --fj-color-text
     clears AA on it in both themes (about 13.7:1 light, 9.5:1 dark). The label and
     the value are told apart by WEIGHT only (medium vs regular), both at full text
     colour, so neither needs a muted token (a muted value would drop near 4:1 on
     this surface, the trap the footer band already hit).
     The box metrics trace the Figma frame and fall off the 4px spacing grid (5px
     and 10px), so they live here as named literals, the same way the section
     header carries its off-scale marker sizes. */
  --fj-color-badge-surface: #e4e4e7;
  --fj-badge-padding-block: 5px;
  --fj-badge-padding-inline: 10px;
  --fj-badge-gap: 5px;

  /* Form controls (fj-input, fj-textarea, fj-select, fj-checkbox, fj-radio,
     fj-switch). Only structural sizes live here; the colours are
     read straight from the existing role tokens in each component (white surface
     = --fj-color-surface, rest border = --fj-color-border-strong, literally the
     "interactive borders (inputs)" token already gated as "input border on
     card", hover darken = --fj-color-text-muted, focus = --fj-color-brand,
     invalid = --fj-color-danger, placeholder = --fj-color-text-subtle). Reading
     the role tokens directly keeps the colours theme-correct without re-declaring
     an indirection token per theme (a :root var(--role) bakes in the light value
     and inherits it unchanged into dark). These size tokens are theme-stable, so
     they stay :root-only. The text controls share one comfortable 48px height so
     they line up with the unified control bar (fj-search-bar). The two off-scale
     values (the switch track is 44px, between space-10=40 and space-12=48) carry
     their own named tokens with the source noted, the same way the badge and
     section-header tokens carry their off-grid literals. */
  --fj-field-block-size: var(--fj-space-12);          /* 48px text-control height */
  --fj-field-padding-inline: var(--fj-space-4);       /* 16px inline padding */
  --fj-field-gap: var(--fj-space-3);                  /* gap to a leading / trailing icon */
  --fj-field-radius: var(--fj-radius-md);             /* 10px rounded rectangle */
  /* Checkbox / radio drawn indicator: a 20px box (= space-5). The interactive
     target is widened to >= 24px (WCAG 2.5.8) by the surrounding label row, never
     by growing the box. Checked = brand fill + a white glyph (white-on-brand is
     already contrast-gated). */
  --fj-control-indicator-size: var(--fj-space-5);     /* 20px box / circle */
  --fj-control-indicator-radius: var(--fj-radius-sm); /* 6px checkbox corner */
  /* Switch (fj-switch): a 44x24 track with a 20px thumb inset 2px, so the thumb
     travels 20px. 44px has no match on the 4px spacing scale, so it is a named
     literal here; the 24px track and 20px thumb map to space-6 / space-5. Off =
     a neutral grey track (--fj-color-border-strong) so the white thumb keeps a
     >= 3:1 boundary in both themes (white on #767679 = 4.5:1 light, on #6e6e70 =
     4.2:1 dark, gated in tokens.contrast.test.ts); on = the brand fill. The
     on / off difference is carried by the thumb POSITION first, not colour. */
  --fj-switch-track-inline-size: 2.75rem;             /* 44px (off the 4px scale) */
  --fj-switch-track-block-size: var(--fj-space-6);    /* 24px */
  --fj-switch-thumb-size: var(--fj-space-5);          /* 20px (2px inset each side) */
  --fj-switch-thumb-inset: 2px;                       /* gap between thumb and track edge */
  /* The thumb's elastic "stretch" on toggle: it elongates toward the direction of
     travel in the middle of the slide, then snaps back to a circle. Pure CSS, no
     JS: the thumb is anchored by BOTH inline edges, so its width is the gap
     between them; the LEADING edge eases out (rushes ahead) while the TRAILING
     edge eases in (lags), so that gap peaks mid-slide. The two easings swap
     between the off-target and on-target rules, so it stretches the same way
     turning on and off. Tune the rubberiness with the two easings: closer curves
     are subtler, more divergent curves stretch further. A touch longer than the
     base so the stretch has room to read; under reduced motion the thumb just
     jumps (the transition is dropped). */
  --fj-switch-thumb-duration: 280ms;
  --fj-switch-thumb-ease-lead: cubic-bezier(0.3, 0.7, 0.4, 1);     /* rushes ahead, settles */
  --fj-switch-thumb-ease-trail: cubic-bezier(0.6, 0, 0.7, 0.3);    /* lags, then catches up */

  /* Selectable-card list (fj-choice-cards): the leading option icon sits a step
     above the section-header line mark so it carries real presence on the card,
     balancing the label + description column. The selection indicator stays on
     the shared control size and is pinned to the top inline-end corner of the
     card (top-left in the Arabic RTL default). */
  --fj-choice-cards-icon-size: var(--fj-space-8); /* 32px leading option icon */

  /* Button reveal hover (fj-button, opt-in via the `reveal` attribute). The
     standard button keeps its quiet glass hover (motion only: the sheen glides,
     the edge icon rotates). Adding `reveal` upgrades hover to a brand circle
     wiped open from the cursor: on pointer enter the brand fill's clip-path
     circle grows from the entry point to flood the whole button, and on pointer
     leave it collapses back toward the exit point, so the fill starts and ends
     where the pointer actually crossed the edge rather than blooming from the
     centre. While the fill opens, the original edge icon glides outward and a
     fresh duplicate of the same icon glides in from the opposite side, so the
     hover reads as "one leaves, one arrives" rather than the icon simply
     vanishing. The label and the icon turn to on-brand white in step with the
     fill so they stay readable over it; the white-on-brand pair is already
     contrast-gated in tokens.contrast.test.ts. The duration and easing are
     shared by every layer (the fill, the icon glide, the colour flip) so the
     whole hover lands on one calm, symmetric beat - the open and the close take
     the same time and follow the same ease, the way a government surface
     should. The fill colour reads --fj-color-brand, which re-resolves in the
     dark subtree at use time, so the fill tracks the dark brand ramp. */
  --fj-button-reveal-fill: var(--fj-color-brand);
  /* Two-layer reveal: a darker brand circle leads a touch ahead of the main
     fill, so the wipe opens as a quiet two-tone sweep (a darker leading band,
     the main brand flooding behind it). The lead reads --fj-color-brand-hover,
     which re-resolves per theme on the html element, so dark gets its own ramp.
     White text stays readable: the lead is DARKER than the body, so the
     transient leading band only raises white contrast, and the body (the main
     brand) is the final flooded colour the label sits on. */
  --fj-button-reveal-fill-lead: var(--fj-color-brand-hover);
  --fj-button-reveal-text: var(--fj-color-on-brand);
  /* Reveal rim: when the brand fill floods the button, the resting cool blue-grey
     glass rim (--fj-glass-rim, tuned for a light backdrop) reads as a grey halo
     around the red. While flooded, the rim switches to this neutral glass edge -
     a soft white top highlight fading to a dark shade at the far corner - which
     reads as a glass lens edge ON the brand fill (white and a dark shade both
     work on red, in light and dark), not a clashing grey ring. Theme-independent
     (white-on-brand and a dark shade read the same either way); the light angle
     still flips via --fj-glass-light-angle. */
  --fj-button-reveal-rim: linear-gradient(
    var(--fj-glass-light-angle, 225deg),
    rgba(255, 255, 255, 0.5) 0%,
    rgba(255, 255, 255, 0.12) 38%,
    rgba(0, 0, 0, 0.1) 70%,
    rgba(0, 0, 0, 0.22) 100%
  );
  /* Reveal motion tokens. The duration is a calm, unhurried 480ms (slower than
     the system slow): at 320ms the wipe flooded too fast to read the two-tone
     sweep, so it is eased out to a smooth, deliberate gesture. The in and the
     out land on the same beat. The easing is the system ease-out-quart (a soft
     settle without overshoot). With the cover radius kept near the minimum
     (see --fj-button-reveal-cover-size below), the gentle tail of the ease is
     visible on the way IN too, not just on the way out, so the open reads as
     smooth as the close. The reveal icon glide and the colour flip bind to this
     SAME duration token (not the faster default-pulse token), so the icon and
     the white text move in step with the fill instead of racing ahead of it.
     Under reduced motion the fill appears at full size in place. */
  --fj-button-reveal-duration: 480ms;
  --fj-button-reveal-ease: var(--fj-ease-out-quart);
  /* The icon swap runs as a continuous cross-over: the outgoing icon leaves
     while the incoming copy arrives in the same window, so an icon is visible
     at every instant (the swap never leaves an empty gap). The delay is 0 so
     the two halves move in lockstep rather than handing off with a pause. */
  --fj-button-reveal-swap-delay: 0s;
  /* The reveal body fill's full clip-path circle radius (the open state). As a
     percentage it is relative to sqrt(width^2 + height^2) / sqrt(2), so the
     worst case to clear the diagonal from a corner entry is sqrt(2) ~= 141%
     (independent of the pill's aspect ratio). Kept at 146% - just past that
     minimum - on purpose: with little overshoot, the circle finishes covering
     the button near the END of the transition, so the gentle slow tail of the
     ease is VISIBLE on the way in (not flooded away off-screen), which is what
     made the old 150% open feel abrupt next to the close. Still clears every
     corner with a small safety margin. */
  --fj-button-reveal-cover-size: 146%;
  /* The leading layer's full radius: noticeably past the body so the darker
     lead band reads as a clear leading edge sweeping ahead of the body (a small
     gap was too close to see). Both grow on the same beat from the same origin,
     so the in and out stay symmetric; the lead floods first, then the body
     covers it, leaving the main brand as the resting colour. */
  --fj-button-reveal-cover-size-lead: 188%;

  /* Edge-icon hover, shared by every variant. The current motion is the pulse
     (see --fj-button-icon-pulse-* below): the icon presses into the
     label-to-icon gap on hover and pulls back, on the system base duration
     with the system ease-out-quart, symmetric in both directions. The earlier
     launch motion (a wide percent-of-icon-width glide) is retired; only the
     legacy --fj-button-icon-launch-distance-half token survives below for
     backward compatibility, pinned by tokens.button-motion.test.ts. */
  /* The edge-icon pulse (the default hover motion). The earlier half-of-
     icon-width value (60%) read as a launch: it was wider than the gap
     between the label and the icon (8px), so the icon visibly left its
     slot and read as a separate object, not as a pulse. The pulse is now
     a fixed 4px - just under half the label-to-icon gap - so the icon
     presses into the gap and pulls back, never reaching the rounded edge
     of the .base. A small absolute px value, not a percent of the icon,
     is what keeps the pulse inside the gap regardless of icon size: a
     16px icon and a 24px icon get the same 4px shift, so the visual feel
     is stable across all button sizes. The companion audit test
     (tokens.button-motion.test.ts) pins the relationship
     2 * pulseDistance <= --fj-space-2 so the pulse can never grow past
     half the gap and re-introduce the launch-feel regression. */
  --fj-button-icon-pulse-distance: 4px;
  /* Pulse motion. A calm 280ms - between the system fast (120ms) and
     base (200ms) - so the pulse reads as a deliberate gesture, not a
     jump. Material 3 Standard easing (cubic-bezier(0.2, 0, 0, 1)) is a
     soft settle without overshoot: a government surface should not
     bounce. The rise and the retract are symmetric - same duration,
     same ease - so the in and the out land on the same beat. Under
     reduced motion the pulse is dropped, the icon stays at rest, and
     the hover gets a non-motion cue via the rim glow + the background
     colour change instead. */
  --fj-button-icon-pulse-duration: 280ms;
  --fj-button-icon-pulse-ease: cubic-bezier(0.2, 0, 0, 1);
  /* The previous half-distance token is kept for backward compatibility
     (any consumer that referenced it directly still gets the same value),
     but it is no longer used on the default hover. The audit test pins
     the relationship so the two cannot drift. */
  --fj-button-icon-launch-distance-half: 60%;
  /* Glass rim on hover: the resting rim is a 1px hairline (subtle, never a
     hard edge). On hover + focus-visible the rim thickens to 2px and shifts
     to a brighter gradient (the top edge glows more, the opposite dims),
     so the hover reads as the surface "lighting up" at the edge, not as a
     stroke being added. The transition is on the system fast token, so the
     rim rise and the rim retract are symmetric and land on the same beat as
     the icon pulse. Under reduced motion the transition drops and the rim
     still rises, so the hover still gives a clear non-motion cue. */
  --fj-glass-rim-width-hover: 2px;
  --fj-button-hover-rim-opacity: 0.7;
  --fj-glass-rim-hover:
    linear-gradient(
      var(--fj-glass-light-angle, 225deg),
      rgba(255, 255, 255, 1) 0%,
      rgba(200, 210, 228, 0.55) 50%,
      rgba(160, 172, 196, 0.72) 100%
    );
  /* Rim width on active (pressed): one more step wider than hover, so the
     press reads as a deeper, more confident interaction. A 1px step from the
     hover value keeps the rim calm and never reads as a heavy stroke. */
  --fj-glass-rim-width-active: 3px;

  /* Plain (text) button: no surface, brand-coloured text with a hover underline.
     The underline reuses --fj-border-width-thick for its weight and currentColor
     for its colour (so it tracks the brand text in light and dark with no extra
     token). Its duration is the system base so the wipe-in and the wipe-out
     are symmetric and land on the same beat as every other hover motion. It
     carries a slight skew so its moving edge reads as a diagonal stroke. The
     offset is the gap between the label and its underline. */
  --fj-button-plain-underline-offset: var(--fj-space-1);
  --fj-button-plain-underline-duration: var(--fj-duration-base);
  --fj-button-plain-underline-skew: -16deg;

  /* -------------------------------------------------------------------
     Feedback - loading states (fj-spinner / fj-skeleton / fj-progress)
     These are theme-stable metrics only (sizes, ratios, durations). The
     colours are read straight from the role tokens inside each component
     (surface-sunken, surface, brand, border), so they flip with the theme
     for free with no dark re-declaration and add no contrast-gated pair.
     Each component still exposes its colour as a @cssprop with the role
     token as the fallback, so a consumer can retint without touching these.
     ------------------------------------------------------------------- */

  /* Spinner (fj-spinner). The three sizes map straight onto the spacing scale
     (sm 16, md 24, lg 40). The arc thickness is a share of the diameter (a
     unitless ratio) so it scales with the size (md 24 * 0.13 = ~3px). The spin
     is the loading indicator itself (essential motion), so under reduced motion
     it slows to the calmer duration rather than freezing into a stuck arc. */
  --fj-spinner-size-sm: var(--fj-space-4);    /* 16px */
  --fj-spinner-size-md: var(--fj-space-6);    /* 24px */
  --fj-spinner-size-lg: var(--fj-space-10);   /* 40px */
  --fj-spinner-thickness: 0.13;               /* arc width as a share of the diameter */
  --fj-spinner-duration: 0.8s;
  --fj-spinner-duration-reduced: 2.4s;

  /* Skeleton (fj-skeleton). A placeholder surface that mirrors content while it
     loads. The shapes reuse the radius scale in the component (text = sm); only
     the shimmer / pulse cadence lives here. Both animations go fully static under
     reduced motion (the placeholder shape already says "loading"). */
  --fj-skeleton-radius: var(--fj-radius-sm);
  --fj-skeleton-shimmer-duration: 1.6s;
  --fj-skeleton-pulse-duration: 1.6s;

  /* Progress (fj-progress). The bar height is 6px, between space-1 (4px) and
     space-2 (8px) - a sleek rule off the 4px grid, a named literal here the same
     way the badge box metrics are. The indeterminate sweep is the only motion
     and stops (a static segment) under reduced motion. */
  --fj-progress-track-size: 6px;
  --fj-progress-indeterminate-duration: 1.5s;

  /* Text */
  --fj-color-text: #1a1a1a;             /* on the white card 17.4:1 */
  --fj-color-heading: #161616;          /* on white 18:1 */
  --fj-color-text-muted: #6e6e70;       /* secondary text on white 5.1:1 */
  --fj-color-text-subtle: #a0a0a2;      /* disabled/placeholder, for non-primary text */
  --fj-color-text-on-primary: #ffffff;

  /* Borders for documented exceptions such as inputs, panels, and separators. */
  --fj-color-border: #dedee0;           /* hairline */
  --fj-color-border-strong: #767679;    /* interactive borders (inputs) 4.5:1 */

  /* Brand roles. Fill = 700. Hover darkens (800/900), it does not lighten. */
  --fj-color-brand: #a4312a;            /* brand fill = primary-700 */
  --fj-color-brand-hover: #7d2921;      /* = primary-800 */
  --fj-color-brand-active: #571d19;     /* = primary-900 */
  /* Red as text/icon (foreground), separate from the fill role. On white 6.9:1.
     Dark mode raises it up the ramp so it stays readable. */
  --fj-color-brand-text: #a4312a;       /* = primary-700, brand text over light */
  --fj-color-brand-soft: #f8e7e2;       /* tag/badge background = primary-100 */
  --fj-color-on-brand: #ffffff;
  --fj-color-brand-rim: #dda79d;

  /* Links and the focus ring. The link = red 700 over white (6.9:1). */
  --fj-color-link: #a4312a;
  --fj-color-link-hover: #7d2921;
  --fj-color-focus-ring: #a4312a;

  /* Status colors (tuned to WCAG AA).
     accent = a solid color for icons/borders, soft = a soft background,
     text = text over white/the soft background.
     The error red is cooler and distinct from the terracotta identity red.
     Success/error/info: white text over accent passes (5.1, 5.6, and 5.8:1).
     Warning is an exception: white text over it is 3.5:1, so dark text is used
     (5.0:1). */
  --fj-color-success: #2e7d32;
  --fj-color-success-soft: #e8f5e9;
  --fj-color-success-text: #1b5e20;
  --fj-color-warning: #c77700;
  --fj-color-warning-soft: #fff4e0;
  --fj-color-warning-text: #8a5a00;
  --fj-color-danger: #c62828;
  --fj-color-danger-soft: #fdecea;
  --fj-color-danger-text: #b71c1c;
  --fj-color-info: #1565c0;
  --fj-color-info-soft: #e3f0fb;
  --fj-color-info-text: #0d4c92;

  /* -------------------------------------------------------------------
     3) Typography
        The official font: Noto Kufi Arabic (weights 100-900). Tajawal is the
        fallback.
        No positive letter spacing on Arabic (it breaks the joining of the
        letters).
     ------------------------------------------------------------------- */
  --fj-font-family-base: "Noto Kufi Arabic", "Tajawal", "Segoe UI", system-ui, -apple-system, sans-serif;
  --fj-font-family-heading: var(--fj-font-family-base);
  --fj-font-family-mono: "Cascadia Code", ui-monospace, "Courier New", monospace;

  /* A large size scale that suits the government style. */
  --fj-font-size-xs: 0.75rem;   /* 12px */
  --fj-font-size-sm: 0.875rem;  /* 14px */
  --fj-font-size-md: 1rem;      /* 16px - the base */
  --fj-font-size-lg: 1.125rem;  /* 18px */
  --fj-font-size-xl: 1.375rem;  /* 22px */
  --fj-font-size-2xl: 1.75rem;  /* 28px */
  --fj-font-size-3xl: 2.25rem;  /* 36px */
  --fj-font-size-4xl: 2.75rem;  /* 44px */
  --fj-font-size-5xl: 3.5rem;   /* 56px */
  --fj-font-size-6xl: 4.5rem;   /* 72px - hero headings */

  --fj-font-weight-regular: 400;
  --fj-font-weight-medium: 500;
  --fj-font-weight-semibold: 600;
  --fj-font-weight-bold: 700;
  --fj-font-weight-extrabold: 800;

  --fj-line-height-tight: 1.3;
  --fj-line-height-normal: 1.7;
  --fj-line-height-relaxed: 1.9;

  --fj-letter-spacing-tight: -0.01em;
  --fj-letter-spacing-normal: 0;

  /* -------------------------------------------------------------------
     4) Spacing scale  (4px grid, generous spaces)
     ------------------------------------------------------------------- */
  --fj-space-0: 0;
  --fj-space-1: 0.25rem;  /* 4px */
  --fj-space-2: 0.5rem;   /* 8px */
  --fj-space-3: 0.75rem;  /* 12px */
  --fj-space-4: 1rem;     /* 16px */
  --fj-space-5: 1.25rem;  /* 20px */
  --fj-space-6: 1.5rem;   /* 24px */
  --fj-space-8: 2rem;     /* 32px */
  --fj-space-10: 2.5rem;  /* 40px */
  --fj-space-11: 2.75rem; /* 44px - minimum comfortable touch target (WCAG enhanced) */
  --fj-space-12: 3rem;    /* 48px */
  --fj-space-16: 4rem;    /* 64px */
  --fj-space-20: 5rem;    /* 80px */
  --fj-space-24: 6rem;    /* 96px */
  --fj-space-32: 8rem;    /* 128px - padding for the large sections */

  /* Helper layout tokens. */
  --fj-container-max: 1280px;
  --fj-section-spacing: var(--fj-space-24);
  --fj-gutter: var(--fj-space-6);

  /* Shared media ratios. Use these for platform image cards instead of local
     aspect values, so galleries, calendars, and news cards stay manageable. */
  --fj-media-aspect-standard: 3 / 2;
  --fj-media-aspect-wide: 16 / 9;
  --fj-media-aspect-square: 1 / 1;

  /* -------------------------------------------------------------------
     5) Border radius  (clearly rounded cards)
     ------------------------------------------------------------------- */
  --fj-radius-none: 0;
  --fj-radius-sm: 6px;
  --fj-radius-md: 10px;
  --fj-radius-lg: 16px;
  --fj-radius-xl: 24px;   /* cards */
  --fj-radius-2xl: 32px;  /* large panels and cards */
  /* The glass floating bar (nav, header actions) and the standalone glass
     controls (button, open search field, dropdown text trigger): a rounded
     rectangle, not a full pill. Inner items sit concentric with it: an item
     radius of calc(--fj-radius-bar - the bar padding) keeps the inner corners
     following the outer curve. Small icon buttons stay a full pill (circle). */
  --fj-radius-bar: 20px;
  /* The search field bar and its results panel: a rounded rectangle that sits
     concentric inside the glass container (radius-bar minus the container
     padding). */
  --fj-radius-field: 15px;
  --fj-radius-pill: 999px;
  --fj-radius-circle: 50%;

  /* No shadows. The style is flat: elevation is expressed by surface tone.
     Hairline borders are only for documented exceptions. Do not add box-shadow
     in the components. */

  /* -------------------------------------------------------------------
     6) Borders
     ------------------------------------------------------------------- */
  --fj-border-width: 1px;
  --fj-border-width-thick: 2px;

  /* -------------------------------------------------------------------
     7) Motion
     ------------------------------------------------------------------- */
  /* Duration-only tokens (no easing). Use these whenever a transition pairs a
     duration with an explicit easing token, e.g.
       transition: transform var(--fj-duration-base) var(--fj-ease-out-quart);
     The bundled --fj-transition-* tokens below carry their own `ease`, so
     combining them with a second easing token produces two timing functions in
     one transition, which is invalid CSS and silently drops the whole
     declaration (the animation snaps). Pair an explicit easing with the
     duration-only token, never with the bundled one. */
  --fj-duration-fast: 120ms;
  --fj-duration-base: 200ms;
  --fj-duration-slow: 320ms;
  /* Bundled duration + default ease, for transitions that do not need a custom
     easing curve. Built from the duration-only tokens so the numbers live in
     one place. */
  --fj-transition-fast: var(--fj-duration-fast) ease;
  --fj-transition-base: var(--fj-duration-base) ease;
  --fj-transition-slow: var(--fj-duration-slow) ease;
  /* Retired from the button hover: the rotation read as marketing-style
     flourish on a government surface. Kept as a token for any future
     component that needs a calm subtle rotation (no consumer right now). */
  --fj-rotation-subtle: 10deg;

  /* -------------------------------------------------------------------
     8) Layers - z-index
     ------------------------------------------------------------------- */
  --fj-z-base: 1;
  --fj-z-dropdown: 1000;
  --fj-z-sticky: 1100;
  --fj-z-overlay: 1200;
  --fj-z-modal: 1300;
  --fj-z-toast: 1400;
  /* A tooltip / popover hint sits above everything else in the normal stacking
     context (it can point at a toast or a control inside an overlay), so it is the
     top z layer. Note: a native <dialog> (fj-dialog / fj-drawer) renders in the
     browser top layer, which is above any z-index, so a z-stacked hint shown over
     an open dialog is the documented exception, not this token's job. */
  --fj-z-tooltip: 1500;

  /* -------------------------------------------------------------------
     9) Breakpoints
     ------------------------------------------------------------------- */
  --fj-breakpoint-sm: 640px;
  --fj-breakpoint-md: 768px;
  --fj-breakpoint-lg: 1024px;
  --fj-breakpoint-xl: 1280px;

  /* -------------------------------------------------------------------
     10) Overlays - the shared modal scrim, dialog, and drawer
        fj-dialog and fj-drawer are built on the native <dialog>, so the
        backdrop is the ::backdrop pseudo-element (top layer) tinted with the
        scrim below. The scrim is re-declared in the dark block (a darker veil
        suits the dark canvas); the blur and the sizes are theme-stable. Motion
        reuses --fj-transition-slow and the stacking reuses the native top
        layer, so no new motion or z tokens. The bottom sheet and the dialog
        height cap use svh so they track the real mobile viewport (rule 10).
     ------------------------------------------------------------------- */
  --fj-color-scrim: rgba(0, 0, 0, 0.5);   /* the dimming veil behind a modal */
  --fj-scrim-blur: 2px;                    /* a subtle backdrop blur behind the scrim */
  /* The header search dims the WHOLE screen (page + header) on open so only the
     search field and its results read, command-palette style. It is a lighter
     veil than the modal scrim: the search is a quick lookup, not a blocking task,
     so it stays inviting rather than heavy. Reuses --fj-scrim-blur for the frost. */
  --fj-color-search-scrim: rgba(0, 0, 0, 0.3);
  /* fj-dialog: a centered modal for short, focused tasks (confirm, consent).
     The width presets size it; the height is capped so a dialog never fills the
     screen - long, scrollable content belongs in fj-drawer. */
  --fj-dialog-size-sm: 24rem;   /* 384px - a tight confirm */
  --fj-dialog-size-md: 32rem;   /* 512px - the default */
  --fj-dialog-size-lg: 42rem;   /* 672px - a richer dialog */
  --fj-dialog-max-block: min(40rem, 90svh);
  /* One inline padding for every region so the left / right rhythm is uniform.
     Header and body share the same block padding; the footer is deliberately
     tighter (the action buttons already carry their own height, so an equal
     block padding makes the actions row read far heavier than the header). */
  --fj-dialog-padding-inline: var(--fj-space-6);        /* 24px - all regions */
  --fj-dialog-section-padding-block: var(--fj-space-5); /* 20px - header / body */
  --fj-dialog-footer-padding-block: var(--fj-space-3);  /* 12px - tighter actions row */
  /* fj-drawer: an edge sheet for longer, scrollable content (filters, a record,
     a dashboard panel, a mobile menu). Side placements (start/end) take the
     inline size; the bottom sheet takes the block size, capped in svh so it
     tracks the real mobile viewport. */
  --fj-drawer-size-sm: 20rem;   /* 320px */
  --fj-drawer-size-md: 28rem;   /* 448px - the default */
  --fj-drawer-size-lg: 36rem;   /* 576px */
  --fj-drawer-bottom-size-sm: 40svh;
  --fj-drawer-bottom-size-md: 60svh;
  --fj-drawer-bottom-size-lg: 85svh;

  /* Tooltip / Popover (fj-tooltip / fj-popover). Non-modal floating hints placed
     next to a trigger with manual JS positioning (cross-browser, no CSS anchor
     positioning, so they work in Safari / Firefox too). Both are arrow-less
     bordered surfaces, consistent with fj-dropdown - the whole library separates
     a floating panel by a hairline border, never a beak or a shadow (shadows are
     banned). The colours read the role tokens inside each component
     (surface-raised, border, text), so they flip with the theme; only the metrics
     live here. The tooltip opens after a short hover delay so a pointer passing
     over a control does not flash it. */
  --fj-tooltip-gap: var(--fj-space-2);             /* offset between trigger and hint */
  --fj-tooltip-max-inline-size: 18rem;             /* cap a long hint so it wraps */
  --fj-tooltip-open-delay: 250ms;
  --fj-popover-gap: var(--fj-space-2);
  --fj-popover-inline-size: 20rem;                 /* the popover panel width */
  --fj-popover-max-block-size: min(24rem, 70svh);  /* cap height; content scrolls past it */
}

/* =======================================================================
   Color roles - dark mode (Dark roles)
   Full explicit values, built on research not on inverting the light values
   (see the head of the file).
   Enable it by adding data-fj-theme="dark" on <html> or any container.

   Surfaces: elevation by tone not by shadow. The background is darker, the
   card is lighter than it, and the dropdown is lighter than the card (graded
   lighting steps).
       background #161616  ->  surface #1e1e1f  ->  raised #262628

   Red as text/link fails over the dark background (700 on #161616 = 2.6:1),
   so the link moves up the ramp (400 = 5.8:1). The brand button fill goes down
   to 600 (its edge shows over the dark, and white text over it is 5.7:1).

   Status colors in the dark are lower in saturation and lighter (to read
   without vibration), their soft backgrounds are dark, and their text is
   light.
   ======================================================================= */
[data-fj-theme="dark"] {
  /* Surfaces (elevation by tone) */
  --fj-color-background: #161616;       /* the background, a soft black that prevents halation */
  --fj-color-surface: #1e1e1f;          /* the card, lighter than the background */
  --fj-color-surface-raised: #262628;   /* menu/window, lighter than the card */
  --fj-color-surface-sunken: #101011;   /* sunken areas, darker than the background */
  --fj-color-surface-sand: #241f1c;     /* a warm dark alternative to the cream */

  /* Background scale (dark): the same named steps, flipped so "default" is the
     dark base and the scale lightens with emphasis; inverted becomes light. */
  --fj-color-bg-default: #161616;
  --fj-color-bg-subtle: #1b1b1c;
  --fj-color-bg-muted: #1e1e1f;
  --fj-color-bg-emphasized: #262628;
  --fj-color-bg-inverted: #fafafa;

  /* Native controls follow the dark theme (the fj-select popup, spinners). */
  --fj-color-scheme: dark;

  /* Modal scrim: a deeper veil on the dark canvas so the dimmed page reads as
     "behind" the dialog (the light 0.5 veil is too faint over a near-black
     page). The blur and the dialog/drawer sizes are theme-stable, so only the
     scrim is re-declared here. */
  --fj-color-scrim: rgba(0, 0, 0, 0.66);
  /* Search scrim on the dark canvas: a touch deeper than the light veil so the
     dim still reads over a near-black page, but kept lighter than the modal
     scrim (the search is a quick lookup, not a blocking task). */
  --fj-color-search-scrim: rgba(0, 0, 0, 0.45);

  /* Badge chip surface in dark: a step LIGHTER than the dark page so the pill
     reads as raised, not as a sunken hole (a sunken token would vanish on the
     dark canvas). Sits at the raised-surface level. */
  --fj-color-badge-surface: #262628;

  /* Dark glass surfaces use the same quiet material, lifted just enough to read
     on the dark canvas and on bright images. */
  --fj-color-glass: rgba(44, 44, 46, 0.76);
  --fj-color-glass-strong: rgba(82, 82, 84, 0.86);
  --fj-color-glass-soft: rgba(38, 38, 40, 0.76);    /* softer glass: search panel container */
  /* Dropdown / menu panel: dark glass already reads clearly over images at 0.76,
     so the panel holds the same tone here (this is a light-mode legibility fix;
     bumping dark would only deaden its glass). */
  --fj-color-glass-panel: rgba(44, 44, 46, 0.76);
  --fj-glass-surface:
    linear-gradient(
      var(--fj-glass-light-angle, 135deg),
      rgba(82, 82, 84, 0.76) 0%,
      rgba(44, 44, 46, 0.86) 48%,
      rgba(22, 22, 22, 0.5) 100%
    );
  --fj-glass-surface-soft: var(--fj-glass-surface);
  --fj-color-glass-text: var(--fj-color-text);
  --fj-color-glass-text-muted: var(--fj-color-text-muted);
  --fj-color-glass-control-hover: var(--fj-color-glass-strong);
  --fj-color-glass-control-active: var(--fj-color-glass-strong);
  --fj-color-glass-control-active-text: var(--fj-color-heading);
  /* Glass list item hover: a neutral light-grey wash (no brand / pink tint), a
     solid step up from the dark glass so it reads, with the normal light text. */
  --fj-color-glass-item-hover: #3f3f46;
  --fj-color-glass-item-hover-text: var(--fj-color-glass-text);
  /* Selected toggle chip: a light chip (#ececee) that pops on the dark glass
     bar, with the brand-fill red (#b24034 in dark) as its foreground (4.8:1).
     Re-declared here (not only in :root) for the same reason as the glass text
     tokens below: an indirection token resolves at its declaring scope, so the
     dark value must be set in the dark block too. */
  /* A medium grey selected chip from the neutral scale, not a near-white block:
     white reads as unserious on the dark glass bar. neutral-400 sits in the bar's
     own cool-grey family yet is clearly lighter than it and than the hover wash
     (neutral-600), so the selected shape is perceivable (chip-vs-bar and
     vs-hover both clear 3:1) and stays distinct from hover. */
  --fj-color-glass-control-selected: var(--fj-color-neutral-400);
  /* Dark ink on the grey selected chip, not brand red: red text is intentionally
     avoided on the dark theme (low contrast and off-brand for body text there).
     Dark ink on neutral-400 clears AA for the label and 3:1 for the chip icon. */
  --fj-color-glass-control-selected-fg: var(--fj-color-neutral-900);
  /* Floating footer band: no theme-keyed wash and no brightness. The band's
     whole effect is the masked backdrop blur (see the light block), which samples
     the real backdrop and adds no colour in either theme, so there is no
     dark-mode token to re-declare here. The bar fill below is still theme-keyed. */
  /* The bar fill in dark keeps the shared dark glass tone (the white-veil concern
     is a light-mode issue; dark is already a dark translucent glass). Re-declared
     here so the footer-scoped token resolves to the dark value in this scope. */
  --fj-footer-bar-glass: rgba(44, 44, 46, 0.88);
  /* Lit from below in the dark too: a quiet light gleam pools along the bottom
     edge and fades up to a grounded dark top, so the pill reads as rising from
     the bottom of the dark page (the same bottom-to-top read as light mode, just
     a fainter gleam, never a white band). Both the sheen and the rim are
     bottom-weighted "to top" gradients. */
  --fj-footer-bar-glass-sheen: linear-gradient(
    to top,
    rgba(74, 74, 78, 0.6) 0%,
    rgba(42, 42, 44, 0.6) 52%,
    rgba(24, 24, 26, 0.55) 100%
  );
  /* The pill's own rim in the dark: a faint light gleam on the bottom edge fading
     up, replacing the shared diagonal rim. */
  --fj-footer-bar-glass-rim: linear-gradient(
    to top,
    rgba(255, 255, 255, 0.5) 0%,
    rgba(255, 255, 255, 0.14) 42%,
    rgba(255, 255, 255, 0.03) 100%
  );

  /* Header brand pill in the dark: same lit-from-above read as light, but
     mirrored for the dark canvas - a quiet light gleam pools along the top edge
     and fades down to a grounded dark bottom, so the pill reads as part of the
     top of the dark page. Mirrors the dark footer-bar trio. */
  --fj-header-bar-glass: rgba(44, 44, 46, 0.88);
  --fj-header-bar-glass-sheen: linear-gradient(
    to bottom,
    rgba(74, 74, 78, 0.6) 0%,
    rgba(42, 42, 44, 0.6) 52%,
    rgba(24, 24, 26, 0.55) 100%
  );
  --fj-header-bar-glass-rim: linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.5) 0%,
    rgba(255, 255, 255, 0.14) 42%,
    rgba(255, 255, 255, 0.03) 100%
  );

  /* Dark service cards: a darkened frost at rest, a dark-red frost on hover.
     The text, muted, and accent tokens must be RE-declared here (not only in
     :root): a custom property defined as var(--fj-color-text) resolves at its
     declaring scope, so a :root-only definition bakes in the light value and
     inherits it unchanged into the dark subtree. Re-declaring them here makes
     them re-resolve to the dark theme colors (the glass title was #1a1a1a on a
     dark card otherwise). The hover-* tokens resolve to --fj-color-on-brand
     (white in both themes), so they are theme-stable and need no re-declaration.
     The solid hover shadow is deepened for the dark canvas (still hover-only,
     never at rest). */
  --fj-color-service-card: rgba(38, 38, 40, 0.72);
  --fj-color-service-card-text: var(--fj-color-text);
  --fj-color-service-card-text-muted: var(--fj-color-text-muted);
  --fj-color-service-card-solid-accent: var(--fj-color-brand-text);
  --fj-color-service-card-solid-hover-accent: var(--fj-color-brand-text);
  --fj-color-service-card-accent: var(--fj-color-brand-text);
  /* Glass hover: the dark frost intensifies (no red wash); text and icon stay
     at rest. The hover roles are re-declared here so the indirection resolves
     to the dark values, not the light ones. */
  --fj-color-service-card-hover: rgba(38, 38, 40, 0.9);
  --fj-color-service-card-hover-text: var(--fj-color-service-card-text);
  --fj-color-service-card-hover-text-muted: var(--fj-color-service-card-text-muted);
  --fj-color-service-card-hover-accent: var(--fj-color-service-card-accent);

  /* Footer dark band: darker than the card so it still reads as a base surface,
     with the engraved motif (white strokes) dialed down so it stays a whisper
     instead of a grid. The field/button block sizes are theme-stable, so they
     are not re-declared here. */
  --fj-color-footer-surface: #1a1a1a;
  /* On the dark footer band the page muted (#a0a0a2) clears AA (6.7:1 on #1a1a1a),
     so the footer muted matches it here. */
  --fj-color-footer-text-muted: #a0a0a2;
  --fj-footer-pattern-opacity: 0.07;
  --fj-partners-pattern-opacity: 0.04;
  /* Same top-down fade-in as light, kept a faint whisper in the dark: the white
     strokes pop against the near-black band, so the motif is held well below the
     light value and the fade-in starts later (only the lower third carries it)
     to keep the band reading as a quiet surface, not a grid. */
  --fj-footer-pattern-mask: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0) 0%,
    rgba(0, 0, 0, 0) 35%,
    #000 80%,
    #000 100%
  );

  /* The angle the dark glass light travels along (sheen + rim). The gradients
     read this so the bright corner can be flipped for direction. 135deg puts the
     light at the top-start in LTR; in RTL the same physical angle lands at the
     top-END, so the dark + RTL rule below flips it to 225deg (its horizontal
     mirror) to keep the light at the top-start. Only the dark theme reads this
     token; the light scope keeps its own literal angle, so light is untouched. */
  --fj-glass-light-angle: 135deg;
  /* Glass lighting in the dark: the same principle as light with a fainter light (a gleam, not a shadow). */
  --fj-glass-rim: linear-gradient(
    var(--fj-glass-light-angle, 135deg),
    rgba(255, 255, 255, 0.5) 0%,
    rgba(255, 255, 255, 0.08) 36%,
    rgba(255, 255, 255, 0) 60%,
    rgba(255, 255, 255, 0.22) 100%
  );
  /* Dark keeps its original quiet rim and no inner bevel. The bevel token is
     defined in :root (light), so dark must explicitly null it back to none,
     otherwise dark glass would inherit the light white top gleam. The glassier
     light material is the only theme being tuned in this pass. */
  --fj-glass-bevel: none;
  --fj-glass-sheen: var(--fj-glass-surface);
  --fj-glass-rim-width: 1px;
  /* Rim hover (dark): the dark rest rim is a faint gleam; on hover it lifts
     to a brighter top-edge glow and a softer lower gleam, so the hover still
     reads as "lighting up" the way the light theme does, never as a hard
     stroke. */
  --fj-glass-rim-width-hover: 2px;
  --fj-glass-rim-hover: linear-gradient(
    var(--fj-glass-light-angle, 135deg),
    rgba(255, 255, 255, 0.78) 0%,
    rgba(255, 255, 255, 0.16) 36%,
    rgba(255, 255, 255, 0) 60%,
    rgba(255, 255, 255, 0.36) 100%
  );
  /* The header veil darkens the top in the dark theme (it lightened it in light).
     The light token used white here, which laid a white haze over the dark header
     band; use the dark canvas tone (#161616) so the band reads as part of the
     dark UI, not a bright strip. */
  --fj-gradient-header: linear-gradient(
    180deg,
    rgba(22, 22, 22, 0.5) 0%,
    rgba(22, 22, 22, 0) 100%
  );
  --fj-gradient-header-scroll: linear-gradient(
    180deg,
    rgba(22, 22, 22, 0.32) 0%,
    rgba(22, 22, 22, 0.2) 56%,
    rgba(22, 22, 22, 0) 100%
  );
  --fj-gradient-header-blur-mask: linear-gradient(
    180deg,
    #000 0%,
    rgba(0, 0, 0, 0.6) 56%,
    rgba(0, 0, 0, 0) 100%
  );

  /* Text */
  --fj-color-text: #cdcdce;             /* on #161616 = 11:1 */
  --fj-color-heading: #f2f2f2;
  --fj-color-text-muted: #a0a0a2;       /* on #161616 = 7:1 */
  --fj-color-text-subtle: #6e6e70;
  --fj-color-text-on-primary: #ffffff;

  /* Borders (lighter than the surface so they show) */
  --fj-color-border: #2c2c2e;           /* hairline */
  --fj-color-border-strong: #6e6e70;    /* interactive borders 3:1 over the card */

  /* Brand: the fill is 600 (its edge shows over the dark) */
  --fj-color-brand: #b24034;            /* fill = primary-600 */
  --fj-color-brand-hover: #a4312a;      /* = primary-700 */
  --fj-color-brand-active: #7d2921;     /* = primary-800 */
  /* Red as text in the dark: the 600 fill fails on #161616 (3.17:1, fails),
     so the text rises to 400 (#cc7d71) = 5.8:1, matching the link (red consistency). */
  --fj-color-brand-text: #cc7d71;       /* = primary-400, 5.8:1 on #161616 */
  --fj-color-brand-soft: #2a1f1d;
  --fj-color-on-brand: #ffffff;
  --fj-color-brand-rim: #cc7d71;

  /* Button reveal fill in dark: the fill token is the bare brand colour
     (re-declared in the dark block above as #cc7d71 = primary-400), so the
     three scatter dots and the merged fill both auto-resolve to the dark
     brand ramp. A :root-only var() definition would bake in the light red
     and inherit it unchanged here, the same reason the glass and
     service-card tokens are re-declared in this block. White-on-brand
     stays AA in dark (5.8:1 on the 400 ramp). The reveal motion tokens
     (duration, easing, dot positions, swap delay) are theme-stable, so
     they are not repeated. */

  /* Links: move up the ramp (naturally lower in saturation) */
  --fj-color-link: #cc7d71;             /* = primary-400, 5.8:1 on #161616 */
  --fj-color-link-hover: #dda79d;       /* = primary-300, lighter */
  --fj-color-focus-ring: #cc7d71;

  /* Status in the dark: accent lower in saturation and lighter, soft dark, text light */
  --fj-color-success: #6cb96f;
  --fj-color-success-soft: #18271b;
  --fj-color-success-text: #8bd08f;
  --fj-color-warning: #d99a3a;
  --fj-color-warning-soft: #2a2110;
  --fj-color-warning-text: #e7b35e;
  --fj-color-danger: #d66a64;
  --fj-color-danger-soft: #2a1717;
  --fj-color-danger-text: #ef9a93;
  --fj-color-info: #5e9fd8;
  --fj-color-info-soft: #15212e;
  --fj-color-info-text: #82b8ef;
}

/* Dark + RTL only: flip the glass light to the top inline-start. The shared dark
   glass gradients (sheen + rim) travel along --fj-glass-light-angle; the physical
   135deg lands at the top-END in RTL, so mirror it to 225deg here (its horizontal
   mirror, top-start). Scoped with :dir(rtl) - not [dir="rtl"] - so it also matches
   a dark surface that inherits its direction from an ancestor (for example a
   forced-dark stage whose dir comes from <html>). Light and LTR never match this
   rule, so they are untouched; dark + LTR keeps the 135deg default. The token
   parser only reads the bare [data-fj-theme="dark"] { } block, so this rule does
   not affect tokens.json or the contrast test. */
[data-fj-theme="dark"]:dir(rtl) {
  --fj-glass-light-angle: 225deg;
}

/* Light theme glass light angle: ONE direction for every glass surface, so the
   whole platform is lit from a single source. The bright edge sits at the top
   inline-END corner, a soft corner gleam that reads the same on a compact pill
   and on a wide bar: LTR 225deg (top-right), RTL 135deg (top-left, its exact
   horizontal mirror). This is the direction the owner approved on the button
   and asked to apply everywhere (2026-06-15); the former per-component override
   that flipped only fj-button / fj-side-nav is gone, so nav, header, search,
   side-nav, button and every other glass surface now light from this one angle.
   (The previous inline-START default - LTR 90deg / RTL 225deg - lit the bars
   from the opposite corner to the button, which is the split the owner flagged.)
   Scoped with [dir="ltr"] / [dir="rtl"] (not :dir) so the html-level :not([dir])
   { direction: rtl } default in base.css does not silently flip every story; the
   html.fj-light carriers cover Storybook's dark-mode addon (it sets the theme as
   a class, not an attribute). A consumer that forgets dir falls back to the
   gradient's literal angle, a sensible LTR default. Dark and its RTL flip above
   are untouched (the owner kept dark lighting as it is, lit from inline-START).
   The token parser only reads the bare [data-fj-theme="dark"] { } block, so
   these rules do not affect tokens.json or the contrast test. */
[data-fj-theme="light"][dir="ltr"] {
  --fj-glass-light-angle: 225deg;
}
[data-fj-theme="light"][dir="rtl"] {
  --fj-glass-light-angle: 135deg;
}
html.fj-light[dir="ltr"],
html.fj-light:not([dir])[lang="en"] {
  --fj-glass-light-angle: 225deg;
}
html.fj-light[dir="rtl"],
html.fj-light:not([dir]):not([lang="en"]) {
  --fj-glass-light-angle: 135deg;
}

/* No per-component glass-light override lives here any more. fj-button,
   fj-side-nav, fj-nav, fj-header, fj-search and every other glass surface read
   the single shared --fj-glass-light-angle above, so the whole platform is lit
   from one source (inline-END in light, the owner's approved button direction;
   inline-START in dark, unchanged). A component must NOT re-declare the glass
   gradients to flip its own lighting: that is exactly the split that was removed
   on 2026-06-15. If a future surface genuinely needs a different light, change
   the shared angle (and reconsider whether the whole system should move with
   it), do not add a one-off here. */
