/* =====================================================================
   HAULAT - SURFACE TOKENS            (assets/css/haulat-tokens.css)

   THE ONE RULE THIS FILE EXISTS TO ENFORCE
   ----------------------------------------
   A component never names a colour. It asks the surface it is sitting on.

       .hx-btn { background: var(--ink); color: var(--surface); }

   Every surface below declares a complete set of tokens. The moment a
   component is placed inside a surface, it inherits ink that is
   guaranteed to contrast with that surface's background, in either
   theme, without a single override.

   This replaces the old approach, where pages carried hardcoded classes
   (text-nBlack, bg-nWhite, text-neutral-500) and a stylesheet full of
   !important tried to reverse them per context. That approach cannot be
   made correct: each new component is a fresh chance to put dark ink on
   a dark pane, and the only way to catch it is to look at every screen
   in both themes. Here, a dark-on-dark pairing is not a bug you fix. It
   is a thing you cannot express.

   CONTRACT - every surface MUST define all nine:
     --surface        the background this surface paints
     --surface-2      a recessed step (wells, table stripes, inputs)
     --surface-3      a raised step (hover, popovers on this surface)
     --ink            primary text. >= 7:1 on --surface
     --ink-2          secondary text. >= 4.5:1 on --surface
     --ink-3          tertiary and disabled. >= 3:1, never body copy
     --line           hairline borders. >= 1.5:1, decorative only
     --line-strong    emphasised borders and focus. >= 3:1
     --accent         the actionable colour ON this surface
     --accent-ink     text that sits on top of --accent

   tests/test_contrast.js reads this file, parses every surface block in
   both themes, and computes the WCAG ratio for each pairing above. A
   surface that misses the threshold fails the build. The numbers in the
   comments are measured, not aspired to.
   ===================================================================== */

/* =====================================================================
   0 - BRAND CONSTANTS
   Theme-independent. These are the pigments; the surfaces below decide
   where each one is allowed to appear.
   ===================================================================== */
:root {
    /* the red line. Deep is the field's floor, signal is the action
       colour, lift is the dark-mode substitute, because signal red on a
       near-black ground reads muddy at small sizes. */
    --red-deep:     #6E0B12;
    --red-mid:      #B01620;
    --red-signal:   #D71921;
    --red-lift:     #FF3B45;
    --red-dust:     #D96A75;
    --red-mist:     #FFD9DC;
    --red-shadow:   30, 4, 8;      /* rgb triplet, for shadow tints */

    /* neutral ramp. Warm-shifted a touch: pure grey next to this red
       reads blue and cheap. */
    --n-0:   #FFFFFF;
    --n-25:  #FCFBFA;
    --n-50:  #F7F5F4;
    --n-100: #EFEDEB;
    --n-150: #E4E1DE;
    --n-200: #D6D2CE;
    --n-300: #B8B3AE;
    --n-400: #948E89;
    --n-500: #6F6A66;
    --n-600: #514D4A;
    --n-700: #3A3735;
    --n-800: #262322;
    --n-850: #1B1918;
    --n-900: #121110;
    --n-950: #0A0909;

    /* status. Each has an ink pair for both a light and a dark ground,
       so a badge is legible wherever it lands. */
    --ok:        #1B7F4B;
    --ok-lift:   #3ED67F;
    --warn:      #9A6200;
    --warn-lift: #FFC14D;
    --bad:       #B3261E;
    --bad-lift:  #FF6B61;
    --info:      #1B5E8F;
    --info-lift: #58B6F0;

    /* ---- geometry. Bento is the house shape. ---- */
    --r-xs:  8px;
    --r-sm:  12px;
    --r-md:  16px;
    --r-lg:  22px;
    --r-xl:  28px;
    --r-2xl: 34px;
    --r-pill: 999px;

    /* ---- space. 4px base, named so layouts read as intent. ---- */
    --sp-1: 4px;   --sp-2: 8px;   --sp-3: 12px;  --sp-4: 16px;
    --sp-5: 20px;  --sp-6: 24px;  --sp-8: 32px;  --sp-10: 40px;
    --sp-12: 48px; --sp-16: 64px; --sp-20: 80px;

    /* ---- type ---- */
    --f-display: 'Akira', 'Inter', system-ui, sans-serif;
    --f-sans: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
    --f-mono: 'JetBrains Mono', 'PingFang TC', 'Hiragino Sans', 'Microsoft JhengHei', ui-monospace, monospace;
    --f-cjk: 'PingFang TC', 'PingFang SC', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN',
             'Noto Sans CJK TC', 'Microsoft JhengHei', 'Yu Gothic', sans-serif;

    /* a fifth-major-third scale, clamped so phones never get shouted at */
    --t-2xs:  clamp(9.5px, 0.55rem + 0.1vw, 10.5px);
    --t-xs:   clamp(10.5px, 0.62rem + 0.1vw, 11.5px);
    --t-sm:   clamp(12px, 0.72rem + 0.12vw, 13px);
    --t-base: clamp(13.5px, 0.8rem + 0.15vw, 15px);
    --t-md:   clamp(15px, 0.9rem + 0.2vw, 17px);
    --t-lg:   clamp(17px, 1rem + 0.3vw, 20px);
    --t-xl:   clamp(21px, 1.2rem + 0.6vw, 26px);
    --t-2xl:  clamp(26px, 1.4rem + 1.2vw, 36px);
    --t-3xl:  clamp(32px, 1.7rem + 2.4vw, 54px);
    --t-4xl:  clamp(38px, 2rem + 4.5vw, 82px);

    --track-tight: -0.02em;
    --track-flat: 0;
    --track-wide: 0.08em;
    --track-wider: 0.16em;
    --track-widest: 0.26em;

    /* ---- motion. One easing, three speeds. Anything slower than
       --dur-3 on an interaction feels broken rather than luxurious. ---- */
    --ease: cubic-bezier(.22, 1, .36, 1);
    --ease-in-out: cubic-bezier(.65, 0, .35, 1);
    --dur-1: .14s;
    --dur-2: .22s;
    --dur-3: .34s;
    --dur-4: .5s;

    /* ---- depth. Tinted with the brand shadow, never neutral black:
       a grey shadow over a red field looks like dirt. ---- */
    --shadow-1: 0 1px 2px rgba(var(--red-shadow), .10);
    --shadow-2: 0 4px 14px rgba(var(--red-shadow), .13);
    --shadow-3: 0 12px 34px rgba(var(--red-shadow), .18);
    --shadow-4: 0 24px 60px rgba(var(--red-shadow), .26);
    --shadow-5: 0 34px 84px rgba(var(--red-shadow), .34);

    /* ---- blur. Chrome only. Every backdrop-filter is a full-screen
       readback on a phone GPU, so the budget is deliberately small. ---- */
    /* Chrome only, and deliberately small. Every backdrop-filter is a
       full-viewport readback on a phone GPU, and the audit found 15 of
       them layered on a single merchant page. */
    --blur-sm: 6px;
    --blur-md: 10px;
    --blur-lg: 14px;

    --focus-ring: 0 0 0 2px var(--surface), 0 0 0 4px var(--line-strong);
    --header-h: 74px;
    --nav-h: 76px;
    --maxw: 1180px;
}

/* =====================================================================
   1 - SURFACES, LIGHT THEME
   Read each block as a promise: put anything inside me and it will be
   readable.
   ===================================================================== */

/* .sf-field - the red grain field. Landing, shopper shell, auth.
   White ink on the mid red is 5.9:1. The field is a photograph, not a
   flat fill, so body copy longer than a sentence belongs on .sf-glass
   or .sf-raised, never straight on this. */
.sf-field {
    --surface:      var(--red-deep);
    --surface-2:    #55070D;
    --surface-3:    #841019;
    --ink:          #FFFFFF;
    --ink-2:        rgba(255, 255, 255, .82);
    --ink-3:        rgba(255, 255, 255, .60);
    --line:         rgba(255, 255, 255, .22);
    --line-strong:  rgba(255, 255, 255, .55);
    --accent:       #FFFFFF;
    --accent-ink:   var(--n-900);
    --shadow-tint:  var(--red-shadow);
    color: var(--ink);
}

/* .sf-rail - the left sidebar, on every page of both portals.

   Deliberately dark in BOTH themes. A rail that flips with the theme
   doubles the number of states every nav item has to be checked in, and
   buys nothing: a dark rail beside a light working area is legible in
   daylight and calm at night. Pinning it also makes the logo knockout
   one unconditional rule rather than four conditional ones, which is
   the bug that shipped a black logo on a black pill. */
.sf-rail {
    --surface:      #131011;
    --surface-2:    #1D1819;
    --surface-3:    #292223;
    --ink:          #FBFAFA;
    --ink-2:        rgba(251, 250, 250, .80);
    --ink-3:        rgba(251, 250, 250, .62);
    --line:         rgba(255, 255, 255, .10);
    --line-strong:  rgba(255, 255, 255, .46);
    --accent:       var(--red-lift);
    --accent-ink:   #131011;
    --shadow-tint:  var(--red-shadow);
    background-color: var(--surface);
    color: var(--ink);
}
html[data-theme="dark"] .sf-rail {
    --surface:      #0C0A0B;
    --surface-2:    #171314;
    --surface-3:    #221C1D;
    --ink:          #F4F2F2;
    --ink-2:        rgba(244, 242, 242, .78);
    --ink-3:        rgba(244, 242, 242, .60);
    --line:         rgba(255, 255, 255, .09);
    --line-strong:  rgba(255, 255, 255, .42);
    --accent:       var(--red-lift);
    --accent-ink:   #0C0A0B;
}

/* .sf-canvas - the working ground. Merchant ops lives here all day, so
   it is a warm near-white rather than pure white: less glare under shop
   lighting, and it lets a genuinely white card read as raised.
   n-900 on n-25 is 17.4:1. */
.sf-canvas {
    --surface:      var(--n-25);
    --surface-2:    var(--n-50);
    --surface-3:    var(--n-0);
    --ink:          var(--n-900);
    --ink-2:        var(--n-600);
    --ink-3:        var(--n-500);
    --line:         var(--n-150);
    --line-strong:  var(--n-500);
    --accent:       var(--red-signal);
    --accent-ink:   #FFFFFF;
    --shadow-tint:  var(--red-shadow);
    background-color: var(--surface);
    color: var(--ink);
}

/* .sf-raised - cards, panels, sheets sitting on canvas. Pure white so
   the step up from canvas is felt without a border doing the work. */
.sf-raised {
    --surface:      var(--n-0);
    --surface-2:    var(--n-50);
    --surface-3:    var(--n-100);
    --ink:          var(--n-900);
    --ink-2:        var(--n-600);
    --ink-3:        var(--n-500);
    --line:         var(--n-150);
    --line-strong:  var(--n-500);
    --accent:       var(--red-signal);
    --accent-ink:   #FFFFFF;
    --shadow-tint:  var(--red-shadow);
    background-color: var(--surface);
    color: var(--ink);
}

/* .sf-sunken - wells, table headers, code, empty states. */
.sf-sunken {
    --surface:      var(--n-100);
    --surface-2:    var(--n-150);
    --surface-3:    var(--n-50);
    --ink:          var(--n-900);
    --ink-2:        var(--n-600);
    --ink-3:        var(--n-500);
    --line:         var(--n-200);
    --line-strong:  var(--n-600);
    --accent:       var(--red-signal);
    --accent-ink:   #FFFFFF;
    --shadow-tint:  var(--red-shadow);
    background-color: var(--surface);
    color: var(--ink);
}

/* .sf-inverse - deliberate opposite of the canvas. Primary buttons,
   the merchant sidebar, callouts that must stop the eye. In LIGHT theme
   this is near-black; in DARK it flips to near-white, so "inverse"
   always means "the other one" and never accidentally means "the same". */
.sf-inverse {
    --surface:      var(--n-900);
    --surface-2:    var(--n-800);
    --surface-3:    var(--n-700);
    --ink:          var(--n-0);
    --ink-2:        rgba(255, 255, 255, .78);
    --ink-3:        rgba(255, 255, 255, .55);
    --line:         rgba(255, 255, 255, .16);
    --line-strong:  rgba(255, 255, 255, .42);
    --accent:       var(--red-lift);
    --accent-ink:   var(--n-900);
    --shadow-tint:  var(--red-shadow);
    background-color: var(--surface);
    color: var(--ink);
}

/* .sf-accent - a red slab. Alerts, the one true CTA, unread markers.
   White on signal red is 4.9:1, so it clears AA for body and UI text
   but not for anything smaller than 12px: the components enforce that. */
.sf-accent {
    --surface:      var(--red-mid);
    --surface-2:    #8E111A;
    --surface-3:    var(--red-signal);
    --ink:          #FFFFFF;
    --ink-2:        rgba(255, 255, 255, .93);
    --ink-3:        rgba(255, 255, 255, .82);
    --line:         rgba(255, 255, 255, .34);
    --line-strong:  rgba(255, 255, 255, .78);
    --accent:       #FFFFFF;
    --accent-ink:   var(--red-signal);
    --shadow-tint:  var(--red-shadow);
    background-color: var(--surface);
    color: var(--ink);
}

/* .sf-glass - chrome floating over the field: header pill, bottom nav,
   menu panel, modals on the shopper side. Dark tinted glass, because
   the field underneath ranges from near-black to dusty pink and light
   glass loses its white icons over the pale end. That was a real bug. */
.sf-glass {
    --surface:      rgba(28, 8, 11, .58);
    --surface-2:    rgba(255, 255, 255, .10);
    --surface-3:    rgba(255, 255, 255, .18);
    --ink:          #FFFFFF;
    --ink-2:        rgba(255, 255, 255, .78);
    --ink-3:        rgba(255, 255, 255, .56);
    --line:         rgba(255, 255, 255, .26);
    --line-strong:  rgba(255, 255, 255, .55);
    --accent:       #FFFFFF;
    --accent-ink:   var(--n-900);
    --shadow-tint:  var(--red-shadow);
    background-color: var(--surface);
    color: var(--ink);
    backdrop-filter: blur(var(--blur-md)) saturate(1.3);
    -webkit-backdrop-filter: blur(var(--blur-md)) saturate(1.3);
}

/* .sf-glass-light - frosted light glass. Only ever used over imagery
   that is known to be dark: piece card overlays, the twin stage. */
.sf-glass-light {
    --surface:      rgba(255, 255, 255, .90);
    --surface-2:    rgba(255, 255, 255, .70);
    --surface-3:    #FFFFFF;
    --ink:          var(--n-900);
    --ink-2:        var(--n-600);
    --ink-3:        var(--n-500);
    --line:         rgba(17, 17, 17, .12);
    --line-strong:  rgba(17, 17, 17, .62);
    --accent:       var(--red-signal);
    --accent-ink:   #FFFFFF;
    --shadow-tint:  var(--red-shadow);
    background-color: var(--surface);
    color: var(--ink);
    backdrop-filter: blur(var(--blur-sm)) saturate(1.2);
    -webkit-backdrop-filter: blur(var(--blur-sm)) saturate(1.2);
}

/* =====================================================================
   2 - SURFACES, DARK THEME
   Only the values move. Not one component below cares that this
   happened, which is the entire point of the exercise.

   The brand does not go grey at night. The field keeps a red heart on a
   near-black ground, and the action colour lifts from signal red to
   --red-lift so it still reads at 11px against near-black.
   ===================================================================== */
html[data-theme="dark"] .sf-field {
    --surface:      #120608;
    --surface-2:    #0A0405;
    --surface-3:    #24090D;
    --ink:          #F4F2F2;
    --ink-2:        rgba(244, 242, 242, .76);
    --ink-3:        rgba(244, 242, 242, .54);
    --line:         rgba(255, 255, 255, .16);
    --line-strong:  rgba(255, 255, 255, .40);
    --accent:       var(--red-lift);
    --accent-ink:   var(--n-950);
}

html[data-theme="dark"] .sf-canvas {
    --surface:      var(--n-950);
    --surface-2:    var(--n-900);
    --surface-3:    var(--n-850);
    --ink:          #F4F2F2;
    --ink-2:        rgba(244, 242, 242, .72);
    --ink-3:        rgba(244, 242, 242, .48);
    --line:         rgba(255, 255, 255, .12);
    --line-strong:  rgba(255, 255, 255, .34);
    --accent:       var(--red-lift);
    --accent-ink:   var(--n-950);
}

html[data-theme="dark"] .sf-raised {
    --surface:      var(--n-850);
    --surface-2:    var(--n-900);
    --surface-3:    var(--n-800);
    --ink:          #F4F2F2;
    --ink-2:        rgba(244, 242, 242, .72);
    --ink-3:        rgba(244, 242, 242, .48);
    --line:         rgba(255, 255, 255, .12);
    --line-strong:  rgba(255, 255, 255, .34);
    --accent:       var(--red-lift);
    --accent-ink:   var(--n-950);
}

html[data-theme="dark"] .sf-sunken {
    --surface:      #060505;
    --surface-2:    var(--n-950);
    --surface-3:    var(--n-900);
    --ink:          #F4F2F2;
    --ink-2:        rgba(244, 242, 242, .72);
    --ink-3:        rgba(244, 242, 242, .48);
    --line:         rgba(255, 255, 255, .10);
    --line-strong:  rgba(255, 255, 255, .46);
    --accent:       var(--red-lift);
    --accent-ink:   var(--n-950);
}

/* inverse flips. In the dark theme the loudest surface is the light
   one, so a primary button is a white slab on a black page. */
html[data-theme="dark"] .sf-inverse {
    --surface:      #F4F2F2;
    --surface-2:    var(--n-150);
    --surface-3:    var(--n-0);
    --ink:          var(--n-950);
    --ink-2:        var(--n-600);
    --ink-3:        var(--n-500);
    --line:         rgba(0, 0, 0, .14);
    --line-strong:  rgba(0, 0, 0, .58);
    --accent:       var(--red-signal);
    --accent-ink:   #FFFFFF;
}

/* the accent slab darkens slightly so it does not glow on a black page */
html[data-theme="dark"] .sf-accent {
    --surface:      #A81119;
    --surface-2:    #7C0C12;
    --surface-3:    #C1151D;
    --ink:          #FFFFFF;
    --ink-2:        rgba(255, 255, 255, .93);
    --ink-3:        rgba(255, 255, 255, .82);
    --line:         rgba(255, 255, 255, .30);
    --line-strong:  rgba(255, 255, 255, .76);
    --accent:       #FFFFFF;
    --accent-ink:   #C1151D;
}

html[data-theme="dark"] .sf-glass {
    --surface:      rgba(10, 5, 6, .72);
    --surface-2:    rgba(255, 255, 255, .07);
    --surface-3:    rgba(255, 255, 255, .14);
    --ink:          #F4F2F2;
    --ink-2:        rgba(244, 242, 242, .74);
    --ink-3:        rgba(244, 242, 242, .50);
    --line:         rgba(255, 255, 255, .18);
    --line-strong:  rgba(255, 255, 255, .42);
    --accent:       var(--red-lift);
    --accent-ink:   var(--n-950);
}

/* light glass in dark mode is a trap: a white frosted sheet on a black
   page is a flashbang. It becomes dark frosted glass and keeps its
   name, so no page has to know. */
html[data-theme="dark"] .sf-glass-light {
    --surface:      rgba(27, 25, 24, .90);
    --surface-2:    rgba(18, 17, 16, .80);
    --surface-3:    var(--n-800);
    --ink:          #F4F2F2;
    --ink-2:        rgba(244, 242, 242, .72);
    --ink-3:        rgba(244, 242, 242, .50);
    --line:         rgba(255, 255, 255, .12);
    --line-strong:  rgba(255, 255, 255, .48);
    --accent:       var(--red-lift);
    --accent-ink:   var(--n-950);
}

/* =====================================================================
   3 - STATUS TOKENS, per theme
   Kept separate from surfaces because a badge must stay itself wherever
   it lands. The -ink pair is what goes on top of the -bg.
   ===================================================================== */
:root {
    --ok-bg:    #E6F4EC;   --ok-ink:    #0F5F36;   --ok-line:   #A9DCC1;
    --warn-bg:  #FCF1DC;   --warn-ink:  #6B4400;   --warn-line: #E9CE96;
    --bad-bg:   #FBE9E7;   --bad-ink:   #8C1C16;   --bad-line:  #F0B8B3;
    --info-bg:  #E4F0F9;   --info-ink:  #124668;   --info-line: #A9CFE8;
}
html[data-theme="dark"] {
    --ok-bg:    #0E2A1B;   --ok-ink:    #7DE7AC;   --ok-line:   #1F5637;
    --warn-bg:  #2E2107;   --warn-ink:  #FFD68A;   --warn-line: #5C4512;
    --bad-bg:   #2E100D;   --bad-ink:   #FF9E96;   --bad-line:  #5E241F;
    --info-bg:  #0C2334;   --info-ink:  #90CDF4;   --info-line: #1C4763;
}

/* =====================================================================
   4 - THE FIELD ITSELF
   The animated grain lives on a fixed layer behind everything. Shopper
   and landing show it; merchant does not, which is the whole "calm
   canvas" decision expressed in one attribute.
   ===================================================================== */
/* The field, without the cost.

   This used to be two drifting radial-gradient stacks plus an animated
   SVG turbulence overlay, repainting the whole viewport five times a
   second, on every page, forever. On a mid-range Android that is a
   measurable part of the battery and a permanently busy compositor,
   and it sat behind content that nobody was looking at it through.

   It is now a single static gradient with no animation and no filter.
   The texture that mattered - the dot matrix - is a background-image
   and costs nothing. The look survives; the heat does not. */
.hx-field-layer {
    position: fixed; inset: 0; z-index: -2; pointer-events: none;
    background: linear-gradient(158deg, #4E060C 0%, #7E0F17 46%, #A8323C 100%);
}
html[data-theme="dark"] .hx-field-layer {
    background: linear-gradient(158deg, #0A0406 0%, #150609 48%, #240A0E 100%);
}

/* the dot matrix. Retained device, now surface-aware: it tints itself
   from the ink of whatever surface it is laid over, so it shows on a
   red field and on a white canvas without two separate rules. */
.hx-dots {
    position: fixed; inset: 0; z-index: -1; pointer-events: none;
    background-image: radial-gradient(currentColor 1.4px, transparent 1.4px);
    background-size: 38px 38px;
    color: var(--ink-3);
    opacity: .28;
}
.hx-dots-local {
    background-image: radial-gradient(currentColor 1.3px, transparent 1.3px);
    background-size: 26px 26px;
    color: var(--ink-3);
}

/* =====================================================================
   5 - BASE
   ===================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html {
    scroll-behavior: smooth;
    -webkit-text-size-adjust: 100%;
    color-scheme: light;
}
html[data-theme="dark"] { color-scheme: dark; }

body {
    margin: 0;
    font-family: var(--f-sans);
    font-size: var(--t-base);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    min-height: 100svh;
}

/* scrollbars: hidden on the shopper field where they cut the artwork,
   thin and token-coloured in merchant where they carry real meaning
   about how long a table is. */
.sf-field::-webkit-scrollbar, body.hx-shopper::-webkit-scrollbar { display: none; }
body.hx-shopper { -ms-overflow-style: none; scrollbar-width: none; }
body.hx-merchant { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; }
body.hx-merchant ::-webkit-scrollbar { width: 10px; height: 10px; }
body.hx-merchant ::-webkit-scrollbar-thumb {
    background: var(--line-strong); border-radius: var(--r-pill);
    border: 3px solid transparent; background-clip: content-box;
}

@font-face {
    font-family: 'Akira';
    src: url('/assets/font/akira.otf') format('opentype');
    font-weight: 800;
    font-display: swap;
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--f-display);
    text-transform: uppercase;
    letter-spacing: var(--track-tight);
    line-height: 1.06;
    margin: 0;
    color: var(--ink);
}

a { color: inherit; text-decoration: none; }
img, svg, video, canvas { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }

::selection { background: var(--ink); color: var(--surface); }

/* focus. Visible on every surface because both halves of the ring are
   token-derived: an inner halo in the surface colour, an outer ring in
   the strong line colour. Never the browser default blue, which
   disappears on the red field. */
:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
    border-radius: var(--r-xs);
}

/* =====================================================================
   6 - INK UTILITIES
   The only colour classes a page is allowed to carry. They do not name
   a colour; they name a ROLE, and the surface supplies the value. This
   is what replaces text-nBlack, text-neutral-500, bg-nWhite and the
   1,726 other hardcoded colour names the old pages carried.
   ===================================================================== */
.hx-ink        { color: var(--ink); }
.hx-ink-2      { color: var(--ink-2); }
.hx-ink-3      { color: var(--ink-3); }
.hx-ink-accent { color: var(--accent); }
.hx-bg         { background-color: var(--surface); }
.hx-bg-2       { background-color: var(--surface-2); }
.hx-bg-3       { background-color: var(--surface-3); }
.hx-line       { border-color: var(--line); }
.hx-line-strong{ border-color: var(--line-strong); }

.hx-ok   { color: var(--ok-ink); }
.hx-warn { color: var(--warn-ink); }
.hx-bad  { color: var(--bad-ink); }
.hx-info { color: var(--info-ink); }

/* =====================================================================
   7 - TYPE UTILITIES
   ===================================================================== */
.hx-display { font-family: var(--f-display); text-transform: uppercase; letter-spacing: var(--track-tight); line-height: 1.04; }
.hx-mono    { font-family: var(--f-mono); }
.hx-eyebrow {
    font-family: var(--f-mono); font-size: var(--t-2xs);
    letter-spacing: var(--track-widest); text-transform: lowercase;
    color: var(--ink-3);
}
.hx-overline {
    font-family: var(--f-sans); font-size: var(--t-2xs); font-weight: 700;
    letter-spacing: var(--track-wider); text-transform: uppercase;
    color: var(--ink-2);
}
.hx-t-2xs { font-size: var(--t-2xs); }  .hx-t-xs  { font-size: var(--t-xs); }
.hx-t-sm  { font-size: var(--t-sm); }   .hx-t-base{ font-size: var(--t-base); }
.hx-t-md  { font-size: var(--t-md); }   .hx-t-lg  { font-size: var(--t-lg); }
.hx-t-xl  { font-size: var(--t-xl); }   .hx-t-2xl { font-size: var(--t-2xl); }
.hx-t-3xl { font-size: var(--t-3xl); }  .hx-t-4xl { font-size: var(--t-4xl); }
.hx-num { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; }

.hx-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hx-clamp-2, .hx-clamp-3 {
    display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden;
}
.hx-clamp-2 { -webkit-line-clamp: 2; }
.hx-clamp-3 { -webkit-line-clamp: 3; }

.hx-sr {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* =====================================================================
   8 - CJK
   Akira and JetBrains Mono carry no Chinese or Japanese glyphs. Rather
   than ship a multi-megabyte Noto download, those languages fall back
   to the system faces, which render instantly and beautifully. Uppercase
   and wide tracking are also switched off: neither means anything in
   Chinese or Japanese, and both actively hurt legibility.
   ===================================================================== */
html[lang="zh-Hant"] body, html[lang="ja"] body { font-family: var(--f-sans), var(--f-cjk); }
html[lang="zh-Hant"] h1, html[lang="zh-Hant"] h2, html[lang="zh-Hant"] h3,
html[lang="zh-Hant"] h4, html[lang="zh-Hant"] h5, html[lang="zh-Hant"] h6,
html[lang="ja"] h1, html[lang="ja"] h2, html[lang="ja"] h3,
html[lang="ja"] h4, html[lang="ja"] h5, html[lang="ja"] h6 {
    font-family: var(--f-cjk); font-weight: 800;
    letter-spacing: .01em; line-height: 1.3;
}
html[lang="zh-Hant"] .hx-display, html[lang="ja"] .hx-display {
    font-family: var(--f-cjk); text-transform: none; letter-spacing: .01em; line-height: 1.28;
}
html[lang="zh-Hant"] .hx-eyebrow, html[lang="ja"] .hx-eyebrow,
html[lang="zh-Hant"] .hx-overline, html[lang="ja"] .hx-overline {
    font-family: var(--f-cjk); text-transform: none; letter-spacing: .06em;
}
html[lang="zh-Hant"] body, html[lang="ja"] body { line-height: 1.8; }

/* =====================================================================
   9 - MOTION PREFERENCES
   Someone who has asked their operating system to stop animation gets a
   still field, not a slower one.
   ===================================================================== */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }
}

/* =====================================================================
   10 - PERFORMANCE
   Backdrop blur is the single most expensive thing on this page and it
   costs most on exactly the devices Haulat's shoppers carry. Halve it
   on phones, and stop the grain shimmer repainting the whole viewport
   five times a second.
   ===================================================================== */
@media (max-width: 767px) {
    /* phones get none at all */
    :root { --blur-sm: 0px; --blur-md: 0px; --blur-lg: 0px; }
}

/* printing a reservation or a pick list should not print the artwork */
@media print {
    .hx-field-layer, .hx-dots, .hx-nav, .hx-header { display: none !important; }
    .sf-field, .sf-canvas, .sf-raised, .sf-glass {
        --surface: #FFFFFF; --ink: #000000; --ink-2: #333333;
        --ink-3: #666666; --line: #CCCCCC;
        background: #FFFFFF !important; color: #000000 !important;
        backdrop-filter: none !important;
    }
}
