/*
   KeyCodeMart — the themes
   ========================

   Every colour in this application is a custom property declared in this file.
   No component in app.css, and no style attribute in any view, may name a
   colour directly. A theme is therefore nothing but a block that redefines
   these properties, and adding a sixth is one block here plus one row in
   dbo.AppTheme.

   If a theme change ever requires touching a component, that component has a
   hard-coded colour and that is the bug.

   ---------------------------------------------------------------------------
   TWO INDEPENDENT CHOICES, AND THEY ARE NOT THE SAME QUESTION.

       data-theme="slate|green|indigo|sage|mint|shelf"   which palette
       data-mode="day|night"  (or absent)          which room

   A mart is bright at the counter and dim in the back office, and that is a
   property of where the machine is, not of who likes what. So the room follows
   the operating system unless somebody says otherwise, and the palette is a
   choice that survives across both rooms.

   THE SERVER WRITES BOTH ONTO <html> BEFORE THE PAGE IS SENT. A theme applied
   by script after load gives every page a flash of the wrong colours, and on a
   till that flash is the thing people complain about.

   ---------------------------------------------------------------------------
   THE PATTERN, WHICH EVERY THEME BELOW REPEATS EXACTLY THREE TIMES:

       :root[data-theme="X"]                              the day palette
       @media (prefers-color-scheme: dark) {
           :root[data-theme="X"]:not([data-mode="day"])   dark OS, unless day was forced
       }
       :root[data-theme="X"][data-mode="night"]           night forced, beating the OS

   The :not([data-mode="day"]) guard is what lets somebody on a dark machine
   force the bright palette — without it, a dark OS would always win and the
   "Bright shop" button would do nothing. And the forced-night block comes last
   so it beats the media query in the other direction. Both directions have to
   work or the control is a lie.

   ---------------------------------------------------------------------------
   WHAT THE TOKENS MEAN — the same nineteen in every block, in this order:

       ground / raised / sunken     the page, a card on it, a well in it
       ink / ink-soft / ink-faint   primary text, secondary, hints
       line / line-strong          a hairline, and one that has to be seen
       accent / -soft / -ink       the one bold colour, its tint, text ON it
       second                      a warm second emphasis, for totals only
       ok / warn / bad (+ -soft)   RESERVED FOR STATE. In a food shop these
                                   mean fresh, expiring and expired, and no
                                   accent may borrow them — which is why no
                                   theme here has a red or amber accent.
*/

/* ═══════════════════════════════════════════ measurements and faces ══ */
/*  Not themed. A palette changes colour; it does not change how big a gap is
    or which font a till renders without downloading anything.  */

:root {
    /*  ONE SPACING SCALE, and every component names a step on it rather than a
        number. The first three are the originals and are left alone because
        the whole application already spells them; the last two are the sizes
        the landing page had been inventing inline, which is how a page ends up
        with five different section gaps.  */
    --gap-xs:    .25rem;
    --gap-sm:    .5rem;
    --gap:       1rem;
    --gap-lg:    1.75rem;
    --gap-xl:    3rem;
    --gap-2xl:   4.5rem;

    /*  FOUR CORNERS, NOT NINE. A radius that varies by a pixel or two between
        components is the single most common reason an interface looks
        hand-assembled rather than designed - the eye does not name it, but it
        reads it.  */
    --radius-sm:   .375rem;   /* inputs, small controls */
    --radius:      .625rem;   /* cards, tiles, notices  */
    --radius-btn:  .625rem;   /* every button, at every size */
    --radius-lg:   1rem;      /* bands and panels       */
    --radius-pill: 999px;     /* chips and flags        */

    /*  CONTROL HEIGHTS ARE A SCALE TOO. Buttons and inputs sitting on the same
        row at 34px and 37px is the other half of that same hand-assembled
        look, and it is why every control below sets min-height rather than
        letting padding decide.  */
    --ctl-h:    2.5rem;      /* 40px - the standard button and input       */
    --ctl-h-sm: 2rem;        /* 32px - dense grids and row actions         */
    --ctl-h-lg: 3.25rem;     /* 52px - the hero call to action            */

    /*  THE HEADER HEIGHT IS DERIVED FROM THE LOGO, NOT DECLARED BESIDE IT.

        Two other things have to follow this number: the sticky section nav
        sits directly under the bar, and every anchored heading has to clear
        both. They used to be three separate magic numbers, so making the mark
        bigger slid the nav up underneath the header and hid the top of it.

        Stating the height independently only moved the bug - the bar is as
        tall as the logo makes it, so measuring anything else is a guess that
        goes stale the moment --logo-h changes. The constant is the bar's own
        padding (.375rem top and bottom) plus its 1px bottom border.

        --logo-h IS SIMPLY THE HEIGHT OF THE MARK, and it can be that again
        because the file was fixed rather than worked around.

        keymart-pro.jpg was 1600x639 with the artwork running y=167..476 - so
        51.5 per cent of it was blank white margin, and a 44px image drew a
        21px mark while charging the header the full 44px. app.css carried a
        scale and a pull-up to hide that.

        keymart-pro.png is the same mark trimmed to its own edges with a real
        alpha channel, so all of that arithmetic is gone and this number means
        what it says.

        The constant is the bar's padding (.25rem top and bottom) plus its 1px
        bottom border.  */
    --logo-h:   3.25rem;  /* the round KeyMart Pro badge: square, so it needs height to be read */
    --header-h: calc(var(--logo-h) + .5625rem);

    /*  ── ONE FLUID TYPE SCALE ───────────────────────────────────────────
        Type that sizes itself to the screen, on one formula, so a heading is
        never separately tuned per breakpoint and can never drift.

        THE FAULT THIS FIXES. Sizes were written as clamp(min, X + Nvw, max)
        with the max reached at roughly 1100px. A 1366px laptop - and any
        laptop at Windows 125% scaling, which is most of them - is already
        past that, so the heading sat pinned at its maximum. It looked large
        there and it looked no larger on a 1920px monitor, because both were
        clamped. The size had stopped responding to the screen at exactly the
        widths people actually use.

        EVERY STEP IS ANCHORED TO THE SAME TWO VIEWPORTS: 360px and 1600px.
        Below 360 it holds the floor, above 1600 it holds the ceiling, and
        between them it moves in a straight line. The slope is the difference
        divided by that 1240px span, which is all the vw term is.

        THE rem PART IS NOT DECORATION. A size in pure vw ignores the reader's
        own font setting and cannot be zoomed, which fails WCAG 1.4.4. Keeping
        an em-relative term in the middle means someone who has set a larger
        default still gets larger text at every width.

                        at 360px    at 1600px
            --fs-3xl      26px        46px      the one h1
            --fs-2xl      22px        34px      section headings
            --fs-xl       19px        26px      card and panel headings
            --fs-lg       16px        18px      lead paragraphs
            --fs-base     15px        16px      body
            --fs-sm       13px        14px      captions and meta            */
    --fs-3xl:  clamp(1.625rem,  1.2621rem + 1.6129vw, 2.875rem);
    --fs-2xl:  clamp(1.375rem,  1.1572rem + 0.9677vw, 2.125rem);
    --fs-xl:   clamp(1.1875rem, 1.0605rem + 0.5645vw, 1.625rem);
    --fs-lg:   clamp(1rem,      0.9637rem + 0.1613vw, 1.125rem);
    --fs-base: clamp(0.9375rem, 0.9194rem + 0.0806vw, 1rem);
    --fs-sm:   clamp(0.8125rem, 0.7944rem + 0.0806vw, 0.875rem);

    /*  Motion. Fast enough that nothing is waited for, slow enough to be seen.
        app.css turns all of it off under prefers-reduced-motion.  */
    --dur:      .18s;
    --dur-slow: .32s;
    --ease:     cubic-bezier(.2, .6, .3, 1);

    --menu-w:      14rem;
    --menu-w-thin: 3rem;

    /*  The system font first, because a till renders it with no download and
        the first paint on a slow shop connection is the one that matters.
        Inter and the two UI faces ahead of it are named only in case the
        machine already has them - nothing is fetched.  */
    /*  NIRMALA UI AND NOTO ARE IN HERE FOR DEVANAGARI, and they have to be:
        Inter has no Devanagari at all and neither does Segoe UI, so a line of
        Hindi fell all the way through to the browser's last-resort font -
        which renders, but with metrics nobody chose. Fallback is per glyph, so
        the Latin in the same sentence still comes from Inter.  */
    --font: "Inter", "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system,
            "Nirmala UI", "Noto Sans Devanagari", "Helvetica Neue", Arial, sans-serif;

    /*  Figures line up in a column only if the digits are the same width.
        Every amount, quantity and rate in this application uses this.  */
    --font-num: "Consolas", "SF Mono", "Cascadia Mono", ui-monospace, monospace;

    /*  TWO COLOURS THAT ARE DELIBERATELY NOT THEMED, and they live here rather
        than in app.css so the "no colour outside theme.css" rule still holds.

        The logo plate is white in every palette because the artwork itself is
        drawn on white - it is the image's own ground, not a design choice, and
        tinting it per theme would show as a seam around the mark.

        The large shadow is neutral black at low alpha for the same reason a
        dark theme barely shows it: on a dark ground elevation is carried by
        the border, not the shadow, and a paler shadow there would be a grey
        smear rather than depth.  */
    --shadow-lg:  0 1px 2px rgba(16, 24, 32, .04),
                  0 12px 28px -8px rgba(16, 24, 32, .14),
                  0 32px 64px -24px rgba(16, 24, 32, .12);

    /*  Restored: the mark went to the bar for a while and this went with it.
        It is back at the head of the rail, on a dark ground, and needs its
        own white again - see .rail-brand.  */
    --logo-plate: #FFFFFF;
}

/* ══════════════════════════════════════════════════════════════════════
   1 · SLATE TEAL — the product default
   ══════════════════════════════════════════════════════════════════════
   Cool blue-grey ground, teal accent.

   THE GROUND IS 93% WHITE, NOT 100%, and that is the whole idea. A full-white
   screen at shop brightness is a lamp pointed at the face for eight hours;
   seven percent off it costs nothing in legibility and is the single biggest
   thing a palette can do for somebody who works in it all day.

   Primary text lands near 12:1 against the card — well above the 4.5:1
   accessibility floor and deliberately short of the 21:1 that black-on-white
   gives, because at that end edges begin to shimmer on a long shift.

   Teal, rather than green, so the accent is nowhere near the red and amber
   that expiry status has to own.
*/

:root,
:root[data-theme="slate"] {
    --ground:      #EDF1F2;
    --raised:      #FAFCFC;
    --sunken:      #E1E8EA;
    --ink:         #17212A;
    --ink-soft:    #4E5D68;
    --ink-faint:   #7D8B95;
    --line:        #D6DEE1;
    --line-strong: #BCC8CD;
    --accent:      #0E7C86;
    --accent-soft: #DAEDEF;
    --accent-ink:  #FFFFFF;
    --second:      #8A5A20;
    --ok:   #1C6B4A;  --ok-soft:   #DFEFE7;
    --warn: #8A6410;  --warn-soft: #F7EED8;
    --bad:  #A3352B;  --bad-soft:  #F8E5E3;
    --shadow:    0 1px 2px rgba(23,33,42,.06), 0 10px 30px rgba(23,33,42,.08);
    --shadow-sm: 0 1px 2px rgba(23,33,42,.10);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-mode="day"]),
    :root[data-theme="slate"]:not([data-mode="day"]) {
        --ground:      #0F1518;
        --raised:      #161F23;
        --sunken:      #0A1013;
        --ink:         #DFE8EC;
        --ink-soft:    #93A5AE;
        --ink-faint:   #6B7C85;
        --line:        #222D33;
        --line-strong: #33424A;
        --accent:      #3BB6C1;
        --accent-soft: #10282D;
        --accent-ink:  #06171A;
        --second:      #D2A45F;
        --ok:   #68C39A;  --ok-soft:   #10281E;
        --warn: #D8B65E;  --warn-soft: #292315;
        --bad:  #E08375;  --bad-soft:  #2C1A18;
        --shadow:    0 1px 2px rgba(0,0,0,.40), 0 10px 30px rgba(0,0,0,.32);
        --shadow-sm: 0 1px 2px rgba(0,0,0,.45);
    }
}

:root[data-mode="night"],
:root[data-theme="slate"][data-mode="night"] {
    --ground:      #0F1518;
    --raised:      #161F23;
    --sunken:      #0A1013;
    --ink:         #DFE8EC;
    --ink-soft:    #93A5AE;
    --ink-faint:   #6B7C85;
    --line:        #222D33;
    --line-strong: #33424A;
    --accent:      #3BB6C1;
    --accent-soft: #10282D;
    --accent-ink:  #06171A;
    --second:      #D2A45F;
    --ok:   #68C39A;  --ok-soft:   #10281E;
    --warn: #D8B65E;  --warn-soft: #292315;
    --bad:  #E08375;  --bad-soft:  #2C1A18;
    --shadow:    0 1px 2px rgba(0,0,0,.40), 0 10px 30px rgba(0,0,0,.32);
    --shadow-sm: 0 1px 2px rgba(0,0,0,.45);
}

/* ══════════════════════════════════════════════════════════════════════
   2 · COUNTER GREEN — the trade's own colour
   ══════════════════════════════════════════════════════════════════════
   The palette this product shipped with, and the most familiar to anybody who
   has used grocery software before. Green is the trade's colour and the accent
   does real work.

   Its one weakness, said plainly so nobody has to discover it: green ends up
   meaning two things at once — the brand, and "fresh". In a grid where expiry
   status is the point, that costs the eye a fraction of a second per row.
*/

:root[data-theme="green"] {
    --ground:      #F5F7F4;
    --raised:      #FFFFFF;
    --sunken:      #E9EEE7;
    --ink:         #16201A;
    --ink-soft:    #55635B;
    --ink-faint:   #86948B;
    --line:        #DFE6DC;
    --line-strong: #C6D0C6;
    --accent:      #1F7A46;
    --accent-soft: #E2F0E8;
    --accent-ink:  #FFFFFF;
    --second:      #8A5A20;
    --ok:   #1C6B4A;  --ok-soft:   #E0F0E7;
    --warn: #8A6410;  --warn-soft: #F8EFD9;
    --bad:  #A3352B;  --bad-soft:  #F9E6E3;
    --shadow:    0 1px 2px rgba(22,32,26,.06), 0 10px 30px rgba(22,32,26,.08);
    --shadow-sm: 0 1px 2px rgba(22,32,26,.10);
}

@media (prefers-color-scheme: dark) {
    :root[data-theme="green"]:not([data-mode="day"]) {
        --ground:      #11150F;
        --raised:      #171C15;
        --sunken:      #0C100B;
        --ink:         #E8EFE6;
        --ink-soft:    #A2B0A0;
        --ink-faint:   #71806F;
        --line:        #232A20;
        --line-strong: #36402F;
        --accent:      #4FB77A;
        --accent-soft: #16281C;
        --accent-ink:  #0B1109;
        --second:      #D2A45F;
        --ok:   #6FC79A;  --ok-soft:   #122A1D;
        --warn: #DDBB5C;  --warn-soft: #2A2415;
        --bad:  #E38073;  --bad-soft:  #2E1A17;
        --shadow:    0 1px 2px rgba(0,0,0,.40), 0 10px 30px rgba(0,0,0,.32);
        --shadow-sm: 0 1px 2px rgba(0,0,0,.45);
    }
}

:root[data-theme="green"][data-mode="night"] {
    --ground:      #11150F;
    --raised:      #171C15;
    --sunken:      #0C100B;
    --ink:         #E8EFE6;
    --ink-soft:    #A2B0A0;
    --ink-faint:   #71806F;
    --line:        #232A20;
    --line-strong: #36402F;
    --accent:      #4FB77A;
    --accent-soft: #16281C;
    --accent-ink:  #0B1109;
    --second:      #D2A45F;
    --ok:   #6FC79A;  --ok-soft:   #122A1D;
    --warn: #DDBB5C;  --warn-soft: #2A2415;
    --bad:  #E38073;  --bad-soft:  #2E1A17;
    --shadow:    0 1px 2px rgba(0,0,0,.40), 0 10px 30px rgba(0,0,0,.32);
    --shadow-sm: 0 1px 2px rgba(0,0,0,.45);
}

/* ══════════════════════════════════════════════════════════════════════
   3 · INK INDIGO — cool and serious
   ══════════════════════════════════════════════════════════════════════
   The most software-like of the five, and the one where warnings read hardest:
   blue sits further from red and amber than any other accent here, so nothing
   competes with an expiry chip.

   The trade-off is character. It reads like an accounting package rather than
   like a food shop, which is right for a back office and less right for a
   counter the customer can see.
*/

:root[data-theme="indigo"] {
    --ground:      #EFF1F6;
    --raised:      #FCFDFF;
    --sunken:      #E5E8F0;
    --ink:         #191C26;
    --ink-soft:    #525971;
    --ink-faint:   #838AA0;
    --line:        #DADEE9;
    --line-strong: #C1C7D8;
    --accent:      #3949AB;
    --accent-soft: #E4E7F6;
    --accent-ink:  #FFFFFF;
    --second:      #8A5A20;
    --ok:   #1F6B54;  --ok-soft:   #DFEFE9;
    --warn: #8A6410;  --warn-soft: #F7EDD7;
    --bad:  #A3352B;  --bad-soft:  #F8E5E2;
    --shadow:    0 1px 2px rgba(25,28,38,.06), 0 10px 30px rgba(25,28,38,.08);
    --shadow-sm: 0 1px 2px rgba(25,28,38,.10);
}

@media (prefers-color-scheme: dark) {
    :root[data-theme="indigo"]:not([data-mode="day"]) {
        --ground:      #0E1017;
        --raised:      #151824;
        --sunken:      #090B12;
        --ink:         #E3E6F2;
        --ink-soft:    #959BB5;
        --ink-faint:   #6B7189;
        --line:        #212536;
        --line-strong: #333952;
        --accent:      #8C9BF0;
        --accent-soft: #161C33;
        --accent-ink:  #080B18;
        --second:      #D2A45F;
        --ok:   #63C7A6;  --ok-soft:   #0F2721;
        --warn: #DCBA62;  --warn-soft: #282316;
        --bad:  #E28679;  --bad-soft:  #2C1B19;
        --shadow:    0 1px 2px rgba(0,0,0,.40), 0 10px 30px rgba(0,0,0,.32);
        --shadow-sm: 0 1px 2px rgba(0,0,0,.45);
    }
}

:root[data-theme="indigo"][data-mode="night"] {
    --ground:      #0E1017;
    --raised:      #151824;
    --sunken:      #090B12;
    --ink:         #E3E6F2;
    --ink-soft:    #959BB5;
    --ink-faint:   #6B7189;
    --line:        #212536;
    --line-strong: #333952;
    --accent:      #8C9BF0;
    --accent-soft: #161C33;
    --accent-ink:  #080B18;
    --second:      #D2A45F;
    --ok:   #63C7A6;  --ok-soft:   #0F2721;
    --warn: #DCBA62;  --warn-soft: #282316;
    --bad:  #E28679;  --bad-soft:  #2C1B19;
    --shadow:    0 1px 2px rgba(0,0,0,.40), 0 10px 30px rgba(0,0,0,.32);
    --shadow-sm: 0 1px 2px rgba(0,0,0,.45);
}

/* ══════════════════════════════════════════════════════════════════════
   4 · PAPER SAGE — warm and calm
   ══════════════════════════════════════════════════════════════════════
   The gentlest of the five for long reading, and the closest thing here to
   paper. Choose it when the complaint being solved is tiredness.

   The cost is named rather than hidden: on a warm ground the amber "expiring"
   chip has less to separate it from what is behind it. A mart that lives or
   dies on catching expiry should think twice.
*/

:root[data-theme="sage"] {
    --ground:      #F4F3ED;
    --raised:      #FCFBF7;
    --sunken:      #E9E7DD;
    --ink:         #1F211B;
    --ink-soft:    #5A5C51;
    --ink-faint:   #8B8D80;
    --line:        #E1DFD3;
    --line-strong: #CBC8B8;
    --accent:      #4C6B3C;
    --accent-soft: #E6EDE0;
    --accent-ink:  #FFFFFF;
    --second:      #8A5A20;
    --ok:   #416B3A;  --ok-soft:   #E6EEE1;
    --warn: #8A5F12;  --warn-soft: #F6EBD5;
    --bad:  #9C3A29;  --bad-soft:  #F6E6E1;
    --shadow:    0 1px 2px rgba(31,33,27,.06), 0 10px 30px rgba(31,33,27,.08);
    --shadow-sm: 0 1px 2px rgba(31,33,27,.10);
}

@media (prefers-color-scheme: dark) {
    :root[data-theme="sage"]:not([data-mode="day"]) {
        --ground:      #15160F;
        --raised:      #1C1E16;
        --sunken:      #101109;
        --ink:         #E9EADF;
        --ink-soft:    #A3A697;
        --ink-faint:   #757869;
        --line:        #272A1F;
        --line-strong: #3B3F2E;
        --accent:      #8FB877;
        --accent-soft: #1E2417;
        --accent-ink:  #101207;
        --second:      #D2A45F;
        --ok:   #8FBE83;  --ok-soft:   #1B2517;
        --warn: #D6B15C;  --warn-soft: #292214;
        --bad:  #DC8270;  --bad-soft:  #2B1A16;
        --shadow:    0 1px 2px rgba(0,0,0,.40), 0 10px 30px rgba(0,0,0,.32);
        --shadow-sm: 0 1px 2px rgba(0,0,0,.45);
    }
}

:root[data-theme="sage"][data-mode="night"] {
    --ground:      #15160F;
    --raised:      #1C1E16;
    --sunken:      #101109;
    --ink:         #E9EADF;
    --ink-soft:    #A3A697;
    --ink-faint:   #757869;
    --line:        #272A1F;
    --line-strong: #3B3F2E;
    --accent:      #8FB877;
    --accent-soft: #1E2417;
    --accent-ink:  #101207;
    --second:      #D2A45F;
    --ok:   #8FBE83;  --ok-soft:   #1B2517;
    --warn: #D6B15C;  --warn-soft: #292214;
    --bad:  #DC8270;  --bad-soft:  #2B1A16;
    --shadow:    0 1px 2px rgba(0,0,0,.40), 0 10px 30px rgba(0,0,0,.32);
    --shadow-sm: 0 1px 2px rgba(0,0,0,.45);
}

/* ══════════════════════════════════════════════════════════════════════
   5 · MIDNIGHT MINT — built dark first
   ══════════════════════════════════════════════════════════════════════
   The night set is the real one here; the day set is its fallback for when
   somebody carries the machine into a bright room.

   A near-black with a blue cast, never #000 — pure black ground with pure
   white text is the single worst pairing available, and the haloes it produces
   affect anyone with even mild astigmatism, which is most people.

   Do not ship this as a shop-floor default. In a bright room its light text
   blooms for exactly the same reason.
*/

:root[data-theme="mint"] {
    --ground:      #EEF0F4;
    --raised:      #FBFCFE;
    --sunken:      #E2E6EC;
    --ink:         #191C24;
    --ink-soft:    #515868;
    --ink-faint:   #808898;
    --line:        #D9DDE5;
    --line-strong: #C0C6D2;
    --accent:      #2A6F97;
    --accent-soft: #E0EBF2;
    --accent-ink:  #FFFFFF;
    --second:      #8A5A20;
    --ok:   #1F6B54;  --ok-soft:   #DFEFE9;
    --warn: #8A6410;  --warn-soft: #F7EDD7;
    --bad:  #A3352B;  --bad-soft:  #F8E5E2;
    --shadow:    0 1px 2px rgba(25,28,36,.06), 0 10px 30px rgba(25,28,36,.08);
    --shadow-sm: 0 1px 2px rgba(25,28,36,.10);
}

@media (prefers-color-scheme: dark) {
    :root[data-theme="mint"]:not([data-mode="day"]) {
        --ground:      #0D1017;
        --raised:      #141924;
        --sunken:      #090C11;
        --ink:         #E4E9F2;
        --ink-soft:    #97A2B5;
        --ink-faint:   #6C778A;
        --line:        #1F2532;
        --line-strong: #303A4B;
        --accent:      #5CC8B0;
        --accent-soft: #0F2A25;
        --accent-ink:  #05130F;
        --second:      #D2A45F;
        --ok:   #63C7A6;  --ok-soft:   #0F2721;
        --warn: #DCBA62;  --warn-soft: #282316;
        --bad:  #E28679;  --bad-soft:  #2C1B19;
        --shadow:    0 1px 2px rgba(0,0,0,.45), 0 10px 30px rgba(0,0,0,.36);
        --shadow-sm: 0 1px 2px rgba(0,0,0,.50);
    }
}

:root[data-theme="mint"][data-mode="night"] {
    --ground:      #0D1017;
    --raised:      #141924;
    --sunken:      #090C11;
    --ink:         #E4E9F2;
    --ink-soft:    #97A2B5;
    --ink-faint:   #6C778A;
    --line:        #1F2532;
    --line-strong: #303A4B;
    --accent:      #5CC8B0;
    --accent-soft: #0F2A25;
    --accent-ink:  #05130F;
    --second:      #D2A45F;
    --ok:   #63C7A6;  --ok-soft:   #0F2721;
    --warn: #DCBA62;  --warn-soft: #282316;
    --bad:  #E28679;  --bad-soft:  #2C1B19;
    --shadow:    0 1px 2px rgba(0,0,0,.45), 0 10px 30px rgba(0,0,0,.36);
    --shadow-sm: 0 1px 2px rgba(0,0,0,.50);
}

/* ══════════════════════════════════════════════════════════════════════
   6 · SHELF EDGE — paper and ink
   ══════════════════════════════════════════════════════════════════════
   Taken from the shelf-edge label rather than from software: a paper ground
   with a faint green bias, near-black spinach ink, and hairlines that read as
   printed rules rather than borders.

   THE GROUND IS BIASED, NOT GREY. Every neutral here carries a trace of the
   same green — a flat grey next to a food photograph reads as dirty, and a
   ground that shares a hue with the produce on the shelf does not.

   THE ACCENT IS MARIGOLD — the colour of the sale tag on the shelf edge, and
   the one thing this palette is actually for. It is also the only theme here
   that takes an amber accent, which the note at the top of this file says none
   should, so the cost is written down rather than discovered: marigold sits in
   the same family as --warn, and on a screen where "expiring" chips and Save
   buttons appear together the eye has less to separate them than it does in
   the other five. Chosen knowingly. If it ever proves to be the wrong call,
   the fix is these three --accent lines and nothing else.

   IT IS THE DEEPEST MARIGOLD THAT STAYS LEGIBLE, not the brightest one. The
   sale-tag brightness (#E09612) reads at 2.4:1 against the card - fine as a
   fill under dark ink, and unreadable as the link text this token also has to
   serve. #A06A08 is the same hue carried down until it clears the floor in
   both jobs: 4.5:1 as text on the card, 4.7:1 for white text on it as a fill.
   Night has the room to go bright, so it does - 9.2:1.

   Primary text lands near 13:1 on the card. The paper is 93% white for the
   reason given under Slate.
*/

:root[data-theme="shelf"] {
    --ground:      #EDF1E8;
    --raised:      #FAFCF8;
    --sunken:      #E1E8DB;
    --ink:         #16211B;
    --ink-soft:    #4A5A50;
    --ink-faint:   #7C8C81;
    --line:        #D8E0D2;
    --line-strong: #BFCAB7;
    --accent:      #A06A08;
    --accent-soft: #F7EBD3;
    --accent-ink:  #FFFFFF;
    --second:      #8A5A20;
    --ok:   #1C6B4A;  --ok-soft:   #DFEFE6;
    --warn: #8A6410;  --warn-soft: #F7EED8;
    --bad:  #A3352B;  --bad-soft:  #F8E5E3;
    --shadow:    0 1px 2px rgba(22,33,27,.06), 0 10px 30px rgba(22,33,27,.08);
    --shadow-sm: 0 1px 2px rgba(22,33,27,.10);
}

@media (prefers-color-scheme: dark) {
    :root[data-theme="shelf"]:not([data-mode="day"]) {
        --ground:      #0F1411;
        --raised:      #161E18;
        --sunken:      #0A0F0C;
        --ink:         #E4EDE3;
        --ink-soft:    #9FB0A4;
        --ink-faint:   #718276;
        --line:        #212B23;
        --line-strong: #334136;
        --accent:      #F0B542;
        --accent-soft: #2E2411;
        --accent-ink:  #14100A;
        --second:      #D2A45F;
        --ok:   #68C39A;  --ok-soft:   #10281E;
        --warn: #D8B65E;  --warn-soft: #292315;
        --bad:  #E08375;  --bad-soft:  #2C1A18;
        --shadow:    0 1px 2px rgba(0,0,0,.40), 0 10px 30px rgba(0,0,0,.32);
        --shadow-sm: 0 1px 2px rgba(0,0,0,.45);
    }
}

:root[data-theme="shelf"][data-mode="night"] {
    --ground:      #0F1411;
    --raised:      #161E18;
    --sunken:      #0A0F0C;
    --ink:         #E4EDE3;
    --ink-soft:    #9FB0A4;
    --ink-faint:   #718276;
    --line:        #212B23;
    --line-strong: #334136;
    --accent:      #F0B542;
    --accent-soft: #2E2411;
    --accent-ink:  #14100A;
    --second:      #D2A45F;
    --ok:   #68C39A;  --ok-soft:   #10281E;
    --warn: #D8B65E;  --warn-soft: #292315;
    --bad:  #E08375;  --bad-soft:  #2C1A18;
    --shadow:    0 1px 2px rgba(0,0,0,.40), 0 10px 30px rgba(0,0,0,.32);
    --shadow-sm: 0 1px 2px rgba(0,0,0,.45);
}

/* ═══════════════════════════════════════════════════════ the rail ══ */
/*
   THE NAVIGATION RAIL IS DARK IN EVERY PALETTE AND IN BOTH MODES.

   A dark rail beside a light workspace is the shape a person recognises as
   "the software" rather than "a web page", and it does one useful thing
   besides: the menu stops competing with the figures, because the eye reads
   the lighter surface first and the rail becomes a border you can navigate.

   THREE RULES, NOT EIGHTEEN. Every palette already carries its own ink and
   its own accent, so the rail is derived rather than named: in day it is the
   palette's own ink pushed almost to black and tinted with its accent, and
   at night the palette is already dark and its sunken surface IS the rail.
   Change --accent in a theme above and this follows it, with nothing to
   remember and no seventh colour to keep in step.

   --rail-ink is what is legible ON it. In day that is the palette's lightest
   surface; at night its ink is already light and does the job unchanged.
*/

:root {
    --rail:     color-mix(in srgb, var(--ink) 92%, var(--accent));
    --rail-ink: var(--raised);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-mode="day"]) {
        --rail:     var(--sunken);
        --rail-ink: var(--ink);
    }
}

:root[data-mode="night"] {
    --rail:     var(--sunken);
    --rail-ink: var(--ink);
}

/* ═════════════════════════════════════════════════════ the picker ══ */
/*
   The one component that lives in this file rather than in app.css, because it
   is the only thing in the application that draws a colour it is not painted
   in — each swatch shows a theme that is not the current one, so its colours
   have to come from the markup rather than from the tokens above.
*/

.theme-pick {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
    gap: var(--gap-sm);
}

.theme-opt {
    display: grid;
    gap: .4rem;
    padding: .5rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--raised);
    cursor: pointer;
    font: inherit;
    text-align: left;
    color: var(--ink);
}

.theme-opt:hover { border-color: var(--line-strong); }

.theme-opt[aria-pressed="true"] {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent);
}

.theme-opt .swatch {
    display: flex;
    height: 1.75rem;
    border-radius: var(--radius-sm);
    overflow: hidden;
    border: 1px solid rgba(128, 128, 128, .28);
}

.theme-opt .swatch i { flex: 1; }
.theme-opt .swatch i.wide { flex: 1.8; }

.theme-opt .nm { font-size: .8125rem; font-weight: 600; line-height: 1.2; }
.theme-opt .md { font-size: .6875rem; color: var(--ink-faint); }

/*  Said on the tile rather than discovered later: shipping a dark-first theme
    as a shop-floor default is a mistake somebody makes once per install.  */
.theme-opt .dk {
    font-size: .625rem;
    font-weight: 700;
    color: var(--second);
    text-transform: uppercase;
    letter-spacing: .05em;
}

.theme-note {
    font-size: .8125rem;
    color: var(--ink-soft);
    margin: var(--gap-sm) 0 0;
}

/*  Bright / Dim / Follow the machine.  */
/*  IT WRAPS RATHER THAN PUSHING THE PAGE SIDEWAYS. Three labels that cannot
    break plus a segmented pill came to 314px inside a 247px card on a 375px
    phone, and the four pixels that escaped were enough to give the whole page
    a horizontal scrollbar. Wrapping is the right answer for three touch
    targets; the pill softens to a rounded box below, because a stadium radius
    on a two-row control reads as a mistake.  */
.mode-pick { display: inline-flex; flex-wrap: wrap; max-width: 100%; gap: 2px; padding: 3px; background: var(--sunken); border: 1px solid var(--line); border-radius: 999px; }

@media (max-width: 30rem) { .mode-pick { border-radius: var(--radius); } }
.mode-pick button { font: inherit; font-size: .78rem; font-weight: 600; border: 0; background: transparent; color: var(--ink-soft); padding: .25rem .7rem; border-radius: 999px; cursor: pointer; white-space: nowrap; }
.mode-pick button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
