/*
   KeyCodeMart - the components
   ----------------------------
   Hand-written, with no framework underneath it. The reason is the same one
   that keeps the NuGet list at two packages: a grid of items and rates is a
   dense, unusual layout, and every framework this would have pulled in has to
   be fought before it will draw one. What is here is what the screens use.

   NO COLOUR IS NAMED IN THIS FILE. Everything comes from theme.css. See the
   note at the top of that file for why.

   Read in this order: the reset, the shell, the menu, then the pieces a screen
   is made of - cards, tables, forms, buttons, notices - and the permission grid
   last, because it is the one component that exists for exactly one screen.
*/

/* ═══════════════════════════════════════════════════════════ the reset ══ */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

/*  Anchored sections glide rather than jump, and every anchor already clears
    the two sticky bars through scroll-margin. Honoured only when the machine
    has not asked for less motion - see the block at the foot of the reset.  */
html { scroll-behavior: smooth; }

body {
    margin: 0;
    background: var(--ground);
    color: var(--ink);
    font-family: var(--font);
    font-size: .9375rem;
    line-height: 1.55;

    /*  Grayscale antialiasing on the two platforms that otherwise render this
        weight of text noticeably heavier than it was drawn.  */
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/*  NO overflow-x: hidden ON THE BODY, and this is a trap worth naming because
    it is the standard advice for stopping sideways scroll.

    Setting one axis to hidden computes the other to auto, which makes the body
    itself the scrolling box instead of the viewport. Everything still scrolls
    by hand, so it looks fine - but window.scrollTo and element.scrollIntoView
    silently stop working, and on this page that is the section nav, the
    anchored headings and the scroll spy all quietly failing at once.

    Sideways scroll is a real fault, but it is caused by one child that is too
    wide, and the fix is to find that child. Every wide thing here - the
    section nav, tables, the code and diagram blocks - scrolls inside its own
    box instead.  */

/*  THE HEADING SCALE.

    Six steps, each a clear jump from the last, because hierarchy is what lets
    somebody skim. The tightening letter-spacing as size goes up is doing the
    real work: display text set at body tracking always looks loose, and that
    looseness is most of what reads as "unpolished" on a landing page.  */
h1, h2, h3 { margin: 0 0 var(--gap-sm); line-height: 1.2; font-weight: 650; }
h1 { font-size: 1.75rem;  letter-spacing: -.022em; }
h2 { font-size: 1.25rem;  letter-spacing: -.015em; }
h3 { font-size: 1.0625rem; letter-spacing: -.01em; }

/*  Headings break at a sensible place rather than leaving one word alone on
    the last line - the single cheapest typographic improvement available.  */
h1, h2, h3 { text-wrap: balance; }

p { margin: 0 0 var(--gap); }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/*  Never remove an outline without replacing it. A till is keyed by somebody
    tabbing between fields without looking, and the ring is how they know where
    they are.

    Two layers rather than one: the halo underneath keeps the ring visible when
    the focused control sits on the accent itself, where a plain accent outline
    would vanish into its own background.  */
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
    box-shadow: 0 0 0 4px var(--accent-soft);
}

/*  ONE PLACE TURNS EVERY ANIMATION OFF. A person who has asked their machine
    for less motion has usually asked for a reason, and honouring it in each
    component separately means it gets forgotten in the next 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;
        scroll-behavior: auto !important;
    }
}

.ico { width: 1.15rem; height: 1.15rem; flex: none; }

/*  THE PUBLIC WRAP. Capped and centred, because prose past about 80 characters
    is hard to read and a landing page is read.  */
.wrap { width: min(80rem, 100% - 2rem); margin-inline: auto; }

/*  THE WORKING WRAP, and it is a different thing.

    A grid of items with rates, batches and expiry is SCANNED, not read, and
    every column pushed off the right edge is one the operator has to scroll
    for. Capping the working screens at the same 80rem as the landing page left
    a wide monitor with several inches of empty ground down each side and a
    table scrolling inside a narrow box in the middle - which is the gap the
    owner pointed at.

    Nearly full width, with just enough inset that content is not welded to the
    bezel.  */
.wrap-app { width: calc(100% - 1.5rem); margin-inline: auto; max-width: 130rem; }

.muted   { color: var(--ink-soft); }
.faint   { color: var(--ink-faint); }
.num     { font-family: var(--font-num); font-variant-numeric: tabular-nums; }
.right   { text-align: right; }
.spacer  { flex: 1 1 auto; }
.stack   { display: grid; gap: var(--gap); }
.row     { display: flex; align-items: center; gap: var(--gap-sm); flex-wrap: wrap; }

/* ══════════════════════════════════════════════════════════ the topbar ══ */

/*  A hairline and a whisper of shadow, not a slab. The bar has to separate
    itself from a page that scrolls under it without becoming a second piece of
    furniture competing with the content.  */
.topbar {
    background: var(--raised);
    border-bottom: 1px solid var(--line);
    box-shadow: 0 1px 2px rgb(0 0 0 / .03);
    /*  This bar belongs to the public pages, where the BODY scrolls - so it
        has to stick, or the landing page's section nav (pinned under it at
        --header-h) is left hanging with a gap above it that the page shows
        through. The working shells draw their own bar above a scroller.  */
    position: sticky;
    top: 0;
    z-index: 20;
}

/*  The gap is what keeps the mark off the actions on a narrow laptop, and the
    height follows the logo token rather than being set beside it - so making
    the mark bigger cannot crop it.  */
.topbar-in {
    display: flex;
    align-items: center;
    gap: var(--gap);
    min-height: var(--header-h);
    padding-block: .25rem;
}

.brandmark:hover { text-decoration: none; }

/*  THE LOGO CARRIES ITS OWN WHITE GROUND.

    It is a JPEG drawn on white, so on Midnight Mint - or any palette in night
    mode - dropping it straight onto the bar would show a white rectangle with
    hard corners. The plate below is that white made deliberate: same colour,
    rounded, inset, so it reads as a badge rather than as a mistake. Delete the
    background and the padding the day the mark arrives as an SVG or a
    transparent PNG; nothing else here changes.

    SIZED BY HEIGHT ONLY, never by width, and never with both set in CSS - the
    artwork is 1600x639 and the aspect ratio is preserved by letting width stay
    auto. width/height on the <img> itself reserve the space so the bar does
    not jump while the file loads.

    The mark's own margin is generous, so a 64px plate renders the visible
    wordmark near 44px. That is the size at which "Mart Billing Software" under
    the name is still readable rather than a grey smudge, which is the whole
    reason to show a logo rather than set the name in type.  */
/*  THE FILE'S OWN WHITE MARGIN IS CROPPED OFF, and that is what lets the mark
    get BIGGER while the bar gets SHORTER - two things that otherwise pull
    against each other.

    MEASURED, NOT GUESSED. keymart-pro.jpg is 1600x639, and the artwork inside
    it runs from y=167 to y=476. That is 310px of ink in 639px of file:
    51.5 per cent of the image is blank white margin baked into the JPEG.

    So a 44px <img> was rendering a 21px mark and charging the header 44px of
    height for it. Every attempt to make the mark bigger made the bar deeper,
    because more than half of what was growing was empty.

    The anchor is now the mark itself: --logo-h is the height of the VISIBLE
    artwork, and the image is scaled and pulled up behind a clipping box so
    the white above and below never occupies the bar.

        1.814 = 0.88 / (310 / 639)   scale that makes the ink 88% of the box,
                                     leaving 6% breathing room top and bottom
        0.4142                       the pull-up that puts the ink at that 6%

    Delete both numbers and this whole block the day the mark arrives as an
    SVG or a tightly cropped PNG - then height: var(--logo-h) is all it needs.  */
/*  NO PLATE, NO CROP. keymart-pro.png is the mark cut out of its white ground
    with a real alpha channel and trimmed to the artwork, so it sits on
    whatever the palette provides.

    All of the arithmetic that used to live here - the 1.814 scale and the
    0.4142 pull-up that hid the file's 51.5 per cent of blank margin - is gone
    with the file that needed it. --logo-h is now simply the height.

    ONLY WHITE THAT TOUCHED THE BORDER WAS REMOVED, by a flood fill from the
    edges. The white letters inside the PRO badge are enclosed by teal, so the
    fill never reached them and they are still opaque - which is what stops the
    badge reading as a hole once the bar goes dark.  */
.brandmark {
    display: block;
    flex: none;
    line-height: 0;
}

.brandmark img {
    display: block;
    height: var(--logo-h);
    width: auto;
    max-width: 100%;
}

/*  The text mark is gone from all four shells - every one of them carries the
    image now - so the rules that styled two words either side of a <b> went
    with it.  */


/*  THE WORKING SHELLS CARRY A SMALLER MARK, and this is a deliberate departure
    from "twice as big everywhere".

    The public bar is a brand moment and has one job. The mart, owner and
    operator bars are chrome above a till: every millimetre they take is taken
    from the grid of items underneath, and a 64px mark there pushes the first
    row of a purchase bill off a laptop screen. 44px is still a third again
    larger than the mark used to be and unmistakably a logo.

    BOTH TOKENS HAVE TO BE RESTATED HERE, and the reason is a genuine trap in
    custom properties: --header-h is declared on :root as a calc over --logo-h,
    and that substitution uses :root's --logo-h, not the value some descendant
    later overrides. Setting only --logo-h here gave the working shells a 44px
    mark inside a bar still 82px tall - the shrunk logo with none of the space
    it was shrunk to save.  */
.shell-top {
    --logo-h:   2.625rem;
    --header-h: calc(2.625rem + .5625rem);
}

/*  Below a phone width the mark steps down again rather than pushing the
    sign-in actions onto a second row.  */
/*  A step DOWN on a phone, so the mark and the three actions have a chance of
    sharing one row. It read 2.75rem here for a while, which was larger than
    the desktop value it was supposed to be reducing.  */
@media (max-width: 30rem) {
    :root { --logo-h: 2.5rem; }
}

/* ── who is keying ───────────────────────────────────────────────────────── */
/*
   THE NAME, AND THE THREE THINGS SOMEBODY DOES TO THEIR OWN ACCOUNT.

   On a shared till the name is the thing that matters most: two people work
   one machine and the bills carry whoever is signed in. Somebody who glances
   up and sees the wrong name has caught a mistake that is otherwise found
   weeks later by an accountant. So the initials stay visible at every width
   and everything else folds away.
*/

.profile { position: relative; }

.profile > summary {
    display: inline-flex; align-items: center; gap: .45rem;
    padding: .25rem .5rem .25rem .25rem;
    border: 1px solid transparent;
    border-radius: 2rem;
    cursor: pointer;
    list-style: none;
    font-size: .8125rem;
    font-weight: 600;
    color: var(--ink);
    white-space: nowrap;
}

.profile > summary::-webkit-details-marker { display: none; }
.profile > summary:hover { border-color: var(--line); background: var(--sunken); }
.profile[open] > summary { border-color: var(--accent); background: var(--sunken); }

.avatar {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.9rem; height: 1.9rem;
    flex: none;
    border-radius: 50%;
    background: var(--accent);
    color: var(--raised);
    font-size: .6875rem;
    font-weight: 700;
    letter-spacing: .02em;
}

.avatar.big { width: 2.4rem; height: 2.4rem; font-size: .8125rem; }

.profile-pop {
    position: absolute;
    top: calc(100% + .35rem);
    right: 0;
    z-index: 40;
    min-width: 15rem;
    display: grid;
    padding: .3rem;
    background: var(--raised);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

/*  The head of the panel repeats the name in full, because the bar shows only
    initials once the window is narrow and this is the place somebody checks
    when they are not sure who is signed in.  */
.profile-who {
    display: flex; align-items: center; gap: .55rem;
    padding: .5rem .55rem .6rem;
    border-bottom: 1px solid var(--line);
    margin-bottom: .3rem;
}

.profile-who > span { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.profile-who b { font-size: .875rem; }
.profile-who .faint { font-size: .75rem; }

.profile-pop a,
.profile-pop button {
    display: flex; align-items: center; gap: .5rem;
    width: 100%;
    padding: .45rem .55rem;
    border: none;
    border-radius: var(--radius);
    background: none;
    color: var(--ink);
    font: inherit;
    font-size: .8125rem;
    text-align: left;
    cursor: pointer;
}

.profile-pop a:hover,
.profile-pop button:hover { background: var(--sunken); color: var(--accent); text-decoration: none; }

.profile-pop .ico { width: 1.05rem; height: 1.05rem; flex: none; color: var(--ink-faint); }
.profile-pop a:hover .ico, .profile-pop button:hover .ico { color: var(--accent); }

/*  The way out sits under a line of its own. It is the one item on the panel
    that cannot be undone by clicking again, and it should not be next to
    "Appearance" as though it were the same kind of thing.  */
.profile-cut { height: 1px; background: var(--line); margin: .3rem .3rem; }

.profile-pop button.out:hover { color: var(--bad); }
.profile-pop button.out:hover .ico { color: var(--bad); }

.profile-pop form { margin: 0; }

@media (max-width: 52rem) { .profile > summary .lbl-text { display: none; } }

/*  What the top bar says about where you are: the mart, and who is keying.
    Kept quiet - it is a label, not a heading.  */
/*  NAME AND MARK TRAVEL TOGETHER. Two loose children of the bar drifted
    apart the moment anything else was added between them; one wrapper keeps
    the pair intact at every width.  */
/*  WHAT SCREEN THIS IS, beside the button that opens the rail.

    It reads as a heading because it is one, but at the size of a bar rather
    than the size of a page: this sits 8px from a logo and 8px from a date,
    and a 25px heading between them would be shouting across a strip of quiet
    furniture. The sentence under it is the same sentence the page used to
    carry - see the note in _Person.cshtml.  */
.bar-head {
    display: flex; flex-direction: column;
    justify-content: center;
    min-width: 0;
    line-height: 1.25;
}

.bar-head b { font-size: 1rem; letter-spacing: -.01em; }

/*  The sentence is the first thing to go when the bar runs out of room; the
    screen name is not.  */
.bar-head .faint { font-size: .75rem; }

/*  The sentence goes when the bar tightens. THE NAME NEVER DOES: the page no
    longer carries a heading of its own, so on a phone this is the only place
    that says which screen you are on.

    SCOPED TO THE BAR, not to every head. This was written when the head lived
    in the application bar with the date and the mark crowding it, and it is
    still right there. The head on the screen strip is in different
    circumstances - it has a row to itself and it trims its sentence to an
    ellipsis rather than overflowing - so blanking it at 64rem took the
    description off a 1024px laptop for no reason. That head keeps its own
    ladder, further down, and gives the sentence up at 52rem.  */
@media (max-width: 64rem) { .topbar .bar-head .faint { display: none; } }
@media (max-width: 40rem) { .bar-head b { font-size: .9375rem; } }

/*  THE DAY, set as a stamp rather than as another line of prose - a quiet
    ground and the numeral face, so it reads as a fact the software is
    reporting rather than as something the page is telling you.  */
.bar-date {
    display: inline-flex; align-items: center;
    flex: none;
    padding: .3rem .6rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--sunken);
    color: var(--ink-soft);
    font-size: .75rem;
    white-space: nowrap;
}

/*  On a narrow bar the date is the least urgent thing in it.  */
@media (max-width: 52rem) { .bar-date { display: none; } }

/*  THE MARK IN THE BAR. It sits where the person used to, at the trailing
    end - which on a bar whose left is already carrying a toggle and a screen
    name is the only place left that is not a squeeze.  */
.bar-brand { flex: none; margin-left: .25rem; }

/*  THE MARK STANDS DOWN ON A PHONE, and only on a phone.

    The bar carries four things now: the rail toggle, which shop you are
    keying in, the button that starts the day's work, and the mark. At 375px
    there is room for three of them. The wordmark is 153px of that bar - more
    than the shop's name - and it is the only one of the four that tells you
    nothing you did not already know: you are looking at the product.

    The alternative was a second row of chrome, which is what happened before
    this rule: the mark wrapped onto its own line and the bar went from 45px
    to 85px on the smallest screen there is. Shrinking it instead would take
    it to 22px, where the tagline under the name stops being readable at all.

    So on a phone the bar says toggle, shop, new - and on every screen wider
    than that the mark is exactly where it was.  */
@media (max-width: 46rem) { .bar-brand { display: none; } }

.whoami { display: flex; align-items: center; gap: .6rem; min-width: 0; }

.whereami { display: flex; flex-direction: column; line-height: 1.2; }
.whereami .mart { font-weight: 600; }
.whereami .who  { font-size: .8125rem; color: var(--ink-soft); }

.drawer-toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 2.25rem; height: 2.25rem;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--raised); color: var(--ink-soft); cursor: pointer;
}
.drawer-toggle:hover { background: var(--sunken); color: var(--ink); }

/* ═══════════════════════════════════════════════════════════ the shell ══ */
/*  Menu on the left, work on the right. The grid column is what collapses, so
    putting the menu away is one class on one element and the screen beside it
    reflows on its own.  */

/*  THE SHELL IS THE WHOLE PAGE NOW. It used to be a band inside the content
    column, with the rail floating in it as a rounded panel; the rail is the
    left edge of the application, so the two columns start at the top of the
    window and run to the bottom of it.

    NO GAP AND NO PADDING between them: a moat between a rail and the work it
    leads to is what made it read as a widget parked beside the page rather
    than as part of the frame.  */
.shell {
    /*  ONE HEIGHT FOR THE WHOLE TOP STRIP. The brand plate and the bar beside
        it are read as a single band across the window, and they were coming
        out 2px apart - which at that width is a visible step. Declared once
        here and taken by both.

        3.0625rem IS 49px, AND IT IS A MEASUREMENT RATHER THAN A PREFERENCE.
        It was 2.8125rem (45px), which was only ever a floor: the bar's own
        contents - the mart name, the six shortcuts, the date, the avatar -
        stack up to 48px plus a 1px border, so the bar ignored the token and
        rendered 49 while anything that obeyed the token rendered 45.

        That is how the brand plate came to hang below the bar even after
        being told to use the same number. The token now states the height the
        bar actually is, so min-height binds on both sides instead of being
        overshot on one. If the bar's contents ever grow past this, they will
        push it out again - and the fix then is this number, in this one
        place.  */
    --bar-h: 3.0625rem;

    display: grid;
    grid-template-columns: var(--menu-w) minmax(0, 1fr);
    align-items: stretch;

    /*  ═══ THE APPLICATION SITS ON THE PAGE AS A PANEL ═══

        A gutter of half a gap all round, a deeper ground behind it, and the
        four corners rounded. It costs eight pixels and it is what separates a
        product from a page: the work has an edge, and the edge is deliberate.

        NOTHING IS CLIPPED TO GET IT. overflow:hidden on this element would
        make it the scrollport, and the two sticky bars inside would stop
        sticking to the window - which is the whole reason they exist. So each
        piece rounds its own outside corner instead: the rail the two on the
        left, the bar the top right, the column the bottom right.

        --gap-sm is the gutter, named once here and read by everything that
        has to line up with it - including the sticky offsets, which are
        measured from the window and not from this box.  */
    --page-gutter: var(--gap-sm);

    /*  A touch larger than --radius-lg. The panel is the biggest rounded
        thing on the screen and a corner reads smaller the longer the edges
        running into it are; 20px here matches 16px on a card.  */
    --page-radius: 1.25rem;

    /*  THE WINDOW, NOT THE DOCUMENT. The work scrolls inside - see main,
        below - so the foot of the panel is always the foot of the screen.  */
    height: calc(100vh - var(--page-gutter) * 2);
    border-radius: var(--page-radius);

    /*  Now that nothing inside sticks to the window, one clip rounds all four
        corners and no piece has to round its own.  */
    overflow: hidden;

    /*  AN EDGE, NOT JUST A DIFFERENT GROUND.

        Leaning on the ground behind it to show the corners works in daylight,
        where --sunken sits a shade under the rail. At night it does not: the
        rail IS --sunken there, so the panel and the page behind it came out
        the same colour and the whole left side of the rounding disappeared.

        A hairline is true in every theme and both modes, and it is what makes
        this read as a panel rather than as a page that happens to be inset.  */
    border: 1px solid var(--line);
}

/*  Only where the shell is: the landing page and the sign-in screens have
    their own grounds and are not panels.  */
body:has(.shell) {
    background: var(--sunken);
    padding: var(--gap-sm);
}

/* ═════════════════════════════════════════ what shows around the panel ══ */
/*
   THE FRAME IS EIGHT PIXELS WIDE, so whatever lives in it has to work at a
   glance or not at all. A slow wash of the palette's own accent, drifting
   behind the panel, is enough: it catches the eye when you look at the edge
   and it is invisible while you are keying, which on a screen somebody sits
   in front of for eight hours is the whole requirement.

   IT IS THE ACCENT, MIXED DOWN, so it belongs to whichever palette is on -
   green in Counter Green, marigold in Shelf Edge - rather than being a colour
   this file invented.

   ONE TRANSFORM, NOTHING ELSE. The gradients are painted once onto a layer
   larger than the window and that layer is nudged; nothing repaints, nothing
   reflows, and the compositor carries it. Forty seconds end to end - slow
   enough that you never catch it moving, only notice it has.
*/
body:has(.shell)::before {
    content: "";
    position: fixed;
    inset: -25%;
    z-index: -1;
    pointer-events: none;

    background:
        radial-gradient(38% 38% at 22% 18%,
            color-mix(in srgb, var(--accent) 34%, transparent), transparent 70%),
        radial-gradient(32% 32% at 78% 82%,
            color-mix(in srgb, var(--accent) 24%, transparent), transparent 70%);

    animation: page-wash 40s var(--ease) infinite alternate;
    will-change: transform;
}

/*  The travel is what makes it read as weather rather than as a stain. At two
    and a half percent the two ends of the run were, side by side, the same
    picture; at seven the corner it lights moves the width of a card over the
    forty seconds, which is plainly a change and still far too slow to catch
    in the act.  */
@keyframes page-wash {
    from { transform: translate3d(-7%, -4%, 0) scale(1); }
    to   { transform: translate3d( 7%,  4%, 0) scale(1.14); }
}

/*  Somebody who has asked the machine to stop moving things means it. The
    wash stays - it is a background, and a still one is still nicer than a
    flat grey - it simply stops drifting.  */
@media (prefers-reduced-motion: reduce) {
    body:has(.shell)::before { animation: none; }
}

/*  On a phone the panel is full bleed, so there is no frame for any of this
    to show in and it would be a layer painted for nobody.  */
@media (max-width: 60rem) {
    body:has(.shell)::before { display: none; }
}

/*  Measured on the whole element, borders included, so the plate beside it can
    be given the same number and land on the same line.  */
.shell-right .topbar { min-height: var(--bar-h); }
.shell-right .topbar-in { min-height: 0; }

/*  Everything that is not the rail. A column, so the bar sits above the work
    and the work takes the rest.  */
.shell-right {
    min-width: 0;
    min-height: 0;                     /* or the scroller below cannot shrink */
    display: flex; flex-direction: column;
    background: var(--ground);
}

/*  THE ONE THING THAT SCROLLS. The bars above it are siblings, so they stay
    put without needing to be sticky about it, and the panel keeps its shape
    however long the register is.  */
.shell-right > main {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/*  THE SCREEN STRIP, under the application bar.

    The bar above it belongs to the product - the rail toggle, the day, the
    mark. This belongs to where you are: which shop, what this screen is,
    which year is open, and the things a person starts by habit.

    IT STAYS PUT WHEN THE WORK SCROLLS, and that is the point of separating
    them: the button that opens the till is wanted from the middle of a stock
    report, which is exactly when a strip that had scrolled away is two
    clicks further than it should be. It sits directly under the bar, so the
    two read as one piece of chrome rather than two floating rows.  */
.page-bar {
    position: relative;
    z-index: 15;
    background: var(--raised);
    border-bottom: 1px solid var(--line);
}

/*  THE STRIP IS ONE ROW, AND IT HAS TO BE TOLD SO.

    It was allowed to wrap, and a wrapping strip does not fail evenly: it
    fails by the length of whatever sentence the screen happens to have.
    Measured at 1280px - Bill format, whose sentence is short, sat on one row
    with its action in the corner; Preferences went to two rows and put the
    action 903px away from that corner; Voucher numbering went to THREE and
    read as a centred block rather than a header. Three screens of the same
    kind, three different shapes, none of them chosen.

    So the row never breaks. The screen's name always shows in full and the
    action stays pinned to the far end where a person learns to find it; the
    SENTENCE is the part that gives way, ending in an ellipsis when the window
    is too narrow to hold all of it. That is the right thing to sacrifice - it
    explains the screen, and the screen is already open.  */
.page-bar-in {
    display: flex;
    align-items: center;
    gap: var(--gap);
    min-height: 2.5rem;
    padding-block: .2rem;
    flex-wrap: nowrap;
}

/*  Nothing but the head may be squeezed - a button squeezed is a button with
    its label cut in half.  */
.page-bar-in > * { flex: none; }
.page-bar-in > .spacer { flex: 1 1 auto; }

/*  THE STRIP'S OWN BUTTONS ARE PILLS.

    They were .btn-quiet - no border, no fill - which on the strip read as
    stray words rather than things to press: "Show the menu" and "← All items"
    sat there like part of the sentence. A pill with a hairline is unmistakably
    a control, and the strip is the one place a pill fits: it is a single row
    of short labels, and a rounded shape says "one of a small set" the way a
    square button does not.

    FILLED IN THE MART'S COLOUR AT REST - the owner's call, after a grey pill
    (looked like a footnote) and a black one (looked like the rail had leaked
    into the strip). --accent with --accent-ink on it, so it is whatever colour
    the mart chose and never a fixed one. On hover it deepens a step toward
    the ink and lifts a hair, so the press has somewhere to go. Primary
    buttons keep their own fill and only take the shape.  */
.page-bar .btn:not(.btn-primary) {
    min-height: 2rem;
    padding: .3rem .9rem;
    border-radius: 999px;
    font-size: .8125rem;
    gap: .4rem;
    background: var(--accent);
    border: 1px solid transparent;
    color: var(--accent-ink);
    box-shadow: 0 1px 2px rgb(0 0 0 / .12);
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
                border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
}
.page-bar .btn:not(.btn-primary) .ico { width: .95rem; height: .95rem; flex: none; }
.page-bar .btn:not(.btn-primary):hover {
    background: color-mix(in srgb, var(--accent) 82%, var(--ink));
    color: var(--accent-ink);
    transform: translateY(-1px);
    box-shadow: 0 2px 8px rgb(0 0 0 / .18);
    text-decoration: none;
}
.page-bar .btn:not(.btn-primary):active { transform: none; box-shadow: none; }
.page-bar .btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/*  The primary action takes the pill shape and the lift, nothing else.  */
.page-bar .btn-primary { border-radius: 999px; min-height: 2rem; padding: .3rem 1rem; font-size: .8125rem; }
.page-bar .btn-primary:hover { transform: translateY(-1px); }

/*  The key hints beside them, quieter than the buttons and not fighting them.  */
.page-bar .esc { font-size: .75rem; color: var(--ink-faint); }
/*  A box on the strip - the day picker on "The day" - dressed like the
    strip's pills: the same height, a hairline, rounded.  */
.page-bar input:not([type=checkbox]):not([type=radio]) {
    font: inherit; font-size: .8125rem; color: var(--ink);
    background: var(--raised); border: 1px solid var(--line-strong); border-radius: 999px;
    min-height: 2rem; padding: .25rem .7rem;
}
.page-bar input:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px var(--accent-soft); }

/*  .bar-head.screen-head, not .screen-head, and the extra class is load-
    bearing: ".page-bar .bar-head" further down this file sets flex-wrap and
    align-items at exactly the same specificity, and being later it won. Both
    of the declarations below were being dropped on the floor.  */
.page-bar .bar-head.screen-head {
    flex: 0 1 auto;
    min-width: 0;
    flex-wrap: nowrap;

    /*  CENTRED, NOT ON THE BASELINE, and the reason is worth writing down:
        an overflow:hidden flex item no longer offers the baseline of its text
        - it offers its bottom edge. Aligning that against the baseline of the
        name beside it pushed this head to 50px when its tallest child was 23,
        and the strip to 61px on the screens whose sentence is long enough to
        need trimming. Centring is what the row actually wants anyway.  */
    align-items: center;
}

/*  The name of the screen is never the thing that gets cut.  */
.page-bar .screen-head b { flex: none; white-space: nowrap; }

.page-bar .screen-head .faint {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*  ══════════════════════════════════ THE TWO HEADS, ONE PER BAR ══

    The application bar carries the SHOP: which mart these keystrokes are
    going into. The strip beneath carries the SCREEN: what it is, and the
    sentence that says why.

    Both are laid out the same way - the name and its second line side by
    side rather than stacked, because a bar that stacks is a bar of double
    the height on every screen for the sake of a line most people read
    once.  */
.bar-head.mart-head,
.page-bar .bar-head { flex-direction: row; align-items: baseline; gap: .7rem; flex-wrap: wrap; }

/*  THE SHOP'S NAME IS THE ONE PROPER NOUN IN THE CHROME, and it was set at
    the same weight as the sentence beside it. It carries a little more:
    the tracking pulled in the way a name is set rather than a line of
    prose, and a short accent rule leading it.

    The rule is drawn rather than written into the view - it is a mark of
    where you are, not a word, so it belongs in the styling. IT IS THE ONE
    THING WEARING THE ACCENT in the chrome, which is what makes it read as
    identity rather than as another heading: the screen title below it is
    set plainly, so the two never compete.  */
.bar-head.mart-head b {
    position: relative;
    padding-left: .7rem;
    font-size: 1.0625rem;
    font-weight: 700;
    letter-spacing: -.022em;
    color: var(--ink);
}

.bar-head.mart-head b::before {
    content: "";
    position: absolute;
    left: 0; top: .1em; bottom: .12em;
    width: 3px;
    border-radius: 3px;
    background: var(--accent);
}

/*  THE SCREEN'S NAME, on the strip. The largest word in the chrome, because
    it is the one the page used to carry as its own <h1> - it has moved, it
    has not shrunk.  */
.page-bar .screen-head b {
    font-size: 1.125rem;
    font-weight: 700;
    letter-spacing: -.022em;
    color: var(--ink);
}

/*  A hairline between a name and the sentence beside it, so the two are
    plainly two things on a row that has no room to stack them.  */
.bar-head.mart-head .faint,
.page-bar .bar-head .faint {
    position: relative;
    padding-left: .8rem;
    font-size: .8125rem;
}

.bar-head.mart-head .faint::before,
.page-bar .bar-head .faint::before {
    content: "";
    position: absolute;
    left: 0; top: .15em; bottom: .1em;
    width: 1px;
    background: var(--line-strong);
}

/*  The branch goes first when the bar tightens; the shop never does.  */
@media (max-width: 62rem) { .bar-head.mart-head .faint { display: none; } }
@media (max-width: 52rem) { .page-bar .bar-head .faint { display: none; } }

/*  AND ON A PHONE THE STRIP MAY BREAK AFTER ALL.

    One row is right where there is a row to be had. Customers and suppliers
    puts a title, three filters and a New button on its strip; at 375px that
    is 377px of content in a 351px box, and because nothing in it may shrink,
    what gave way was the SCREEN NAME - pushed off the left edge until it read
    "and s". A heading you cannot read is worse than a strip of two rows.

    So below the width where the sentence has already gone, wrapping comes
    back. It costs nothing on the screens that do not need it: a title and one
    button still sit on a single row and never reach for a second.  */
@media (max-width: 52rem) {
    .page-bar-in { flex-wrap: wrap; row-gap: .35rem; }
}

/*  THE NAME GIVES WAY BEFORE THE BAR BREAKS.

    On a phone the bar is carrying the rail toggle, the shop, the button that
    starts the day and the mark. A long shop name pushed the mark onto a
    second row and made the bar 85px of a 812px screen - two rows of chrome
    before any work.

    So the name is the thing that shrinks: it takes what room is left and
    ends in an ellipsis rather than wrapping the row. Nothing is lost - the
    element carries the full name in its title, so a shop whose name is cut
    is one hover away from being read in full.  */
.bar-head.mart-head { flex: 0 1 auto; min-width: 0; }

.bar-head.mart-head b {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}


/*  Put away, the rail is icons only - so the gap beside it shrinks with it.
    Leaving the full gap made a narrow strip of icons sit in a wide moat, which
    reads as a layout that has broken rather than one that has collapsed.  */
.shell.is-collapsed { grid-template-columns: var(--menu-w-thin) minmax(0, 1fr); }

/*  PUT AWAY, THE MENU KEEPS ITS ICONS AND ITS HIGHLIGHT - so it stays a map
    rather than becoming a strip of symbols nobody can read.  */
.shell.is-collapsed .menu-h { display: none; }
.shell.is-collapsed .menu a { justify-content: center; padding-inline: 0; }

/*  ═══ THE NAME COMES BACK ON HOVER ═══

    display: none ON THE LABEL WAS TWO BUGS IN ONE LINE, and it read as
    "on hover no menu is showing".

    The first is the obvious one: a rail of twelve unlabelled icons is a
    guessing game. "Is that box the stock report or the purchase order?" is
    answered by clicking and finding out, which is the opposite of a menu.

    The second is quieter and worse. A display: none span is not hidden from
    sight, it is REMOVED FROM THE PAGE - so the link lost its accessible name
    along with its visible one. Collapsed, every entry in this menu announced
    itself to a screen reader as an unlabelled link. Twelve of them.

    So the label is not removed any more. It is moved out of the way and
    brought back under the pointer, which fixes both at once: the name is in
    the document the whole time for anything reading it, and it appears beside
    the icon for anyone looking at it.

    POSITION: FIXED, NOT ABSOLUTE, and that is the load-bearing detail. The
    rail sets overflow: auto so a long module list can scroll inside itself;
    an absolutely positioned label would be clipped by that at the rail's
    edge - which is exactly 48px away, so it would be cut off mid-word. A
    fixed element is positioned against the viewport and escapes the clip.
    With no top/left of its own it stays at its static position, which is
    where the icon put it.

    ONE COMPROMISE, STATED: a fixed label does not scroll with the rail. If
    somebody holds the pointer still and scrolls the icons under it, the name
    stays where it was. That lasts as long as the scroll does, and it is the
    price of not being clipped. The rail only scrolls when the modules do not
    fit, which on a normal screen they do.  */
.shell.is-collapsed .menu .lbl-text {
    position: fixed;
    z-index: 40;

    /*  Clear of the rail, and clear of its shadow.  */
    margin-left: calc(var(--menu-w-thin) + var(--gap-sm));

    padding: .3rem .5rem;
    border-radius: var(--radius-sm);
    background: var(--rail);
    color: var(--rail-ink);
    border: 1px solid color-mix(in srgb, var(--rail-ink) 22%, var(--rail));
    box-shadow: 0 2px 8px rgb(0 0 0 / .18);

    font-size: .8125rem;
    line-height: 1.2;
    white-space: nowrap;

    /*  Invisible rather than absent - see above. opacity keeps it in the
        accessibility tree; pointer-events keeps it from eating the hover that
        is showing it.  */
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur) var(--ease);
}

/*  :focus-visible as well as :hover, because the rail is reachable by keyboard
    and somebody tabbing through twelve icons needs the name just as much as
    somebody pointing at them.  */
.shell.is-collapsed .menu a:hover .lbl-text,
.shell.is-collapsed .menu a:focus-visible .lbl-text,
.shell.is-collapsed .menu .grp > summary:hover .lbl-text,
.shell.is-collapsed .menu .grp > summary:focus-visible .lbl-text,
.shell.is-collapsed .rail-out button:hover .lbl-text,
.shell.is-collapsed .rail-out button:focus-visible .lbl-text {
    opacity: 1;
}

/*  Somebody who has asked for less motion gets the label without the fade.  */
@media (prefers-reduced-motion: reduce) {
    .shell.is-collapsed .menu .lbl-text { transition: none; }
}

.shell-main { min-width: 0; padding-block: var(--gap) var(--gap-lg); }   /* min-width: without it a wide table stretches the grid */

@media (max-width: 60rem) {
    .shell, .shell.is-collapsed { grid-template-columns: minmax(0, 1fr); min-height: 0; }

    /*  ═══ NO PANEL ON A PHONE ═══

        The gutter and the rounded corners are worth eight pixels on a desktop,
        where they say where the application ends. On a 375px screen those same
        eight pixels each side are eight pixels of the work, and there is no
        "outside" to show anyway - the application IS the window. So the panel
        goes full bleed and every corner squares up.  */
    body:has(.shell) { padding: 0; background: var(--ground); }

    /*  The window scrolls again here: the shell is not a fixed frame on a
        phone, where the address bar and the keyboard both change the height
        of the viewport under you.  */
    .shell { --page-gutter: 0px; height: auto; min-height: 0; border-radius: 0; border: 0; overflow: visible; }
    .shell-right > main { overflow: visible; }
    .shell .menu { height: auto; }
    .shell .menu { border-radius: var(--radius); }
    .shell-right { border-radius: 0; }
    .topbar { border-start-end-radius: 0; }

    /*  The plate stops being sticky in a band that is itself scrolling.  */
    .menu .rail-brand { position: static; border-radius: var(--radius) 0 0 0; }
    .rail-out { margin-top: var(--gap-sm); }

    /*  THE RAIL BECOMES A BAND, AND THE BAND IS BOUNDED.

        Stacked above the work at full height it was eleven rows before the
        first figure - a phone opened on the dashboard showed the menu and
        nothing else. Nothing is hidden and nothing is dropped: the band
        keeps every entry and scrolls inside itself, which it is already set
        up to do (overflow and overscroll-behavior are on .menu above).  */
    .shell .menu { position: static; height: auto; max-height: 45vh; border-radius: var(--radius); }

    /*  ═══ AND ON A PHONE IT STARTS PUT AWAY ═══

        Bounding the band was not enough. 45vh is still HALF THE SCREEN spent
        on the menu before the work begins, and it is worse on a module screen
        because that module's group is open inside it: opening Items showed the
        logo, Dashboard, MODULES, Masters, Overview, Items, New item and
        Expired stock - and the items themselves below the fold.

        THE BUTTON IN THE BAR ALREADY EXISTED AND DID NOTHING HERE. It toggles
        .is-collapsed, which on a desk narrows the rail to its icons - but this
        breakpoint gives .shell and .shell.is-collapsed the same single column,
        so on a phone the two states looked identical and the menu could not be
        put away at all.

        Now it can. Collapsed means gone on a phone rather than narrowed, which
        is what a menu behind a button is; the drawer script defaults it closed
        at this width and remembers anybody who says otherwise.  */
    .shell.is-collapsed .menu { display: none; }

    /*  A screen that hangs its own actions in the bar (see the TopActions
        section in _Work.cshtml) can put more in it than a narrow window has
        room for. Wrapping to a second row costs a few pixels of height;
        the alternative is the mart and the operator's name squeezed until
        neither can be read.  */
    .topbar-in { flex-wrap: wrap; row-gap: var(--gap-sm); }
}

/* ════════════════════════════════════════════════════════════ the menu ══ */

/*  A DARK RAIL BESIDE A LIGHT WORKSPACE. See --rail in theme.css for how it
    is derived; nothing here names a colour.

    IT RUNS THE FULL HEIGHT rather than stopping where the last entry does.
    A short dark box floating in the corner reads as a widget; a rail that
    reaches the floor reads as the edge of the application - and on a screen
    with one mart on it, it is what stops the page looking like content
    parked at the top of an empty field.  */
.menu {
    /*  A full-height column of the panel; it no longer has a window to
        stick to, and the grid stretches it for us.  */
    position: relative;

    /*  A COLUMN, NOT A GRID, and the reason is one line further down: Sign
        out is pushed to the foot with margin-top:auto, and auto margins do
        nothing against a grid whose rows are already packed to the start.  */
    display: flex; flex-direction: column; gap: 1px;

    background: var(--rail);
    border: 0;

    border-radius: 0;                  /* the shell clips the corners now */
    padding: 0 var(--gap-sm) var(--gap-sm);
    color: var(--rail-ink);

    /*  ═══ A STICKY MENU STILL HAS TO FIT ON THE SCREEN ═══

        Sticky positioning pins the top and says nothing about the bottom, so
        ten modules with one of them expanded simply ran off the foot of the
        window with no way to reach the last few entries - Chart of accounts
        was unreachable on a laptop. Bounding the height to what is left below
        the top bar and letting it scroll is the whole fix.

        overscroll-behavior stops a flick inside the menu from carrying on and
        scrolling the page underneath once the list ends, which on a till is
        the difference between finding a screen and losing your place.  */
    height: 100%;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/*  A scrollbar that is only there when it is needed, and quiet when it is.  */
.menu { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--rail-ink) 26%, var(--rail)) transparent; }
.menu::-webkit-scrollbar { width: 8px; }
.menu::-webkit-scrollbar-track { background: transparent; }
.menu::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--rail-ink) 26%, var(--rail)); border-radius: 4px; }
.menu::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--rail-ink) 45%, var(--rail)); }

.menu a, .menu .locked-item {
    position: relative;
    display: flex; align-items: center; gap: .6rem;
    padding: .5rem .6rem;
    border-radius: var(--radius-sm);
    color: color-mix(in srgb, var(--rail-ink) 90%, var(--rail));
    font-size: .875rem;
    white-space: nowrap;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}

.menu a:hover {
    background: color-mix(in srgb, var(--rail-ink) 14%, var(--rail));
    color: var(--rail-ink);
    text-decoration: none;
}

/*  The icon leads the eye down the rail, so it takes the colour of the state
    rather than staying grey while the label changes underneath it.  */
.menu a .ico { color: color-mix(in srgb, var(--rail-ink) 74%, var(--rail)); transition: color var(--dur) var(--ease); }
.menu a:hover .ico { color: var(--rail-ink); }

/*  THE LABEL IS INK, NOT ACCENT. Accent on accent-soft measures 4.08:1 in
    the day palettes - under the 4.5 that normal text needs, on the one
    entry in the rail somebody is most likely to read. The colour has not
    left: it is in the bar on the edge and in the icon, both of which are
    graphics and only owe 3:1. The label just stops paying for it.  */
/*  ON A DARK RAIL THE CURRENT SCREEN IS A FILLED PILL, not a tint. A tint
    that reads clearly against white is nearly invisible against near-black,
    and this is the one entry that has to be findable at a glance from across
    a counter. accent-ink is the palette's own answer to "what is legible on
    the accent", so every theme gets a readable pill for free.  */
.menu a.on {
    background: var(--accent);
    color: var(--accent-ink);
    font-weight: 600;
}

.menu a.on .ico { color: var(--accent-ink); }

/*  WHERE YOU ARE, SAID TWICE. The tint alone is easy to lose on the pale
    palettes and in a menu that is mostly tint-free; a solid bar on the edge is
    readable at a glance from across a counter, and it is the one mark in the
    rail that cannot be mistaken for a hover.  */
/*  A marker on the trailing edge of the pill. On the tinted version this bar
    sat on the left and carried the accent; on a filled pill the accent is
    the pill, so it moves to the far edge and is drawn in the ink instead -
    the same "you are here" mark, still visible when the rail is collapsed to
    icons and the label is gone.  */
.menu a.on::before {
    content: "";
    position: absolute;
    right: .35rem; top: 50%;
    width: 3px; height: 1rem;
    transform: translateY(-50%);
    border-radius: 3px;
    background: var(--accent-ink);
    opacity: .75;
}

/*  A MODULE NOBODY MAY OPEN IS SHOWN, NOT HIDDEN. A cashier who can see that
    GST exists and is closed to them knows there is something to ask the owner
    for; one who sees nothing concludes the software cannot do it.  */
.menu .locked-item { color: color-mix(in srgb, var(--rail-ink) 58%, var(--rail)); cursor: default; }
.menu .locked-item .ico { color: color-mix(in srgb, var(--rail-ink) 48%, var(--rail)); opacity: 1; }
.menu .locked-item .lock { margin-left: auto; font-size: .8rem; opacity: .7; }

/*  A LABEL NEEDS THE AIR ABOVE IT, not below: it belongs to the list that
    follows, and with equal space on both sides it read as though it belonged
    to the entry above. The first one loses the extra, so the rail does not
    start with a gap.  */
.menu-h {
    padding: 1.1rem .6rem .3rem;
    font-size: .6875rem;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: color-mix(in srgb, var(--rail-ink) 72%, var(--rail));
    font-weight: 700;
}

/* ────────────────────────────────────────────────── modules and screens ── */
/*
   A module that has screens inside it is a <details>. Native disclosure gives
   keyboard support, works before the JavaScript loads, and survives it failing
   — which on a till on a bad connection is a real state.

   ONLY THE GROUP HOLDING THE CURRENT SCREEN IS OPEN, so the menu stays a short
   list instead of becoming a wall of forty entries. That is the space this
   gets back.
*/

/*  WHO IS SIGNED IN, AT THE HEAD OF THE RAIL.

    IT SWAPPED PLACES WITH THE MARK, and the swap paid for itself twice. The
    mark is back on a light surface - the bar - which is what the artwork was
    drawn for, so the plate that had to be built to make it legible against
    near-black is gone. And the rail head now carries the thing worth a glance
    rather than the thing you learn once.

    Flush across the rail and stuck to its top, so it is the head of the
    column rather than a card dropped into it, and so it stays put when forty
    entries scroll underneath.  */
/*  OPEN, IT IS A PORTRAIT: the disc centred and the name under it. Beside
    each other they were competing with the row of module entries below for
    the same left-to-right reading; stacked and centred, the head of the rail
    is plainly a different kind of thing from the list it heads.

    Put away it goes back to just the disc - see the collapsed rules below.  */
.rail-who {
    position: sticky; top: 0; z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .5rem;
    box-sizing: border-box;
    margin-inline: calc(var(--gap-sm) * -1);
    margin-bottom: var(--gap-sm);
    padding: 1.15rem var(--gap) 1rem;
    background: var(--rail);
    border-bottom: 1px solid color-mix(in srgb, var(--rail-ink) 15%, var(--rail));
    text-align: center;
}

/*  Bigger, because it is the thing the block is now built around rather than
    a mark beside a line of text.  */
.rail-who .avatar { width: 2.75rem; height: 2.75rem; font-size: .9375rem; }

/*  A RING RATHER THAN A BORDER. On a near-black rail a bordered circle reads
    as a hole; a ring set slightly off the surface reads as a disc sitting on
    it, which is what an avatar is meant to look like.  */
.rail-who .avatar {
    background: var(--accent);
    color: var(--accent-ink);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--rail-ink) 18%, var(--rail));
}

.rail-who-text { display: flex; flex-direction: column; align-items: center; max-width: 100%; min-width: 0; line-height: 1.3; }

/*  A long name is cut with an ellipsis rather than wrapped: the head is one
    bar high and a second line would push the whole rail down.  */
.rail-who-text b {
    max-width: 100%;
    font-size: .9375rem;
    color: var(--rail-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.rail-who-text > span {
    font-size: .625rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: color-mix(in srgb, var(--rail-ink) 60%, var(--rail));
}

/*  Put away, the initials are the whole of it - which is the one part of a
    name that still means something at 48px.  */
/*  Put away, the disc alone, and back to its ordinary size: a 44px circle in
    a 48px rail leaves no room for the rail to be a rail.  */
.shell.is-collapsed .rail-who { padding: .7rem 0; gap: 0; }
.shell.is-collapsed .rail-who .avatar { width: 1.9rem; height: 1.9rem; font-size: .6875rem; }
.shell.is-collapsed .rail-who-text { display: none; }

/*  ── the way out, at the foot ──
    margin-top:auto is the whole mechanism: it eats every spare pixel in the
    column, so the button sits on the floor of the rail however short the
    menu is, and travels back up the moment the menu grows.  */
.rail-out { margin: auto 0 0; padding-top: var(--gap-sm); }

.rail-out button {
    display: flex; align-items: center; gap: .6rem;
    width: 100%;
    padding: .5rem .6rem;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: color-mix(in srgb, var(--rail-ink) 90%, var(--rail));
    font: inherit;
    font-size: .875rem;
    text-align: left;
    white-space: nowrap;
    cursor: pointer;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}

.rail-out button .ico { color: color-mix(in srgb, var(--rail-ink) 74%, var(--rail)); flex: none; }

/*  Signing out is the one thing in this rail somebody does by accident, so
    it answers in the colour of a thing you meant to do.  */
.rail-out button:hover { background: color-mix(in srgb, var(--bad) 22%, var(--rail)); color: var(--rail-ink); }
.rail-out button:hover .ico { color: var(--rail-ink); }

.shell.is-collapsed .rail-out button { justify-content: center; padding-inline: 0; }

.menu > .menu-h:first-child { padding-top: .25rem; }

.menu .grp { border-radius: var(--radius-sm); }

.menu .grp > summary {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .5rem .6rem;
    border-radius: var(--radius-sm);
    color: color-mix(in srgb, var(--rail-ink) 90%, var(--rail));
    font-size: .875rem;
    cursor: pointer;
    white-space: nowrap;
    list-style: none;              /* the default triangle, replaced below */
    user-select: none;
}

.menu .grp > summary::-webkit-details-marker { display: none; }
.menu .grp > summary::marker { content: ""; }

.menu .grp > summary { transition: background var(--dur) var(--ease), color var(--dur) var(--ease); }
.menu .grp > summary:hover {
    background: color-mix(in srgb, var(--rail-ink) 14%, var(--rail));
    color: var(--rail-ink);
}
.menu .grp > summary .ico { color: color-mix(in srgb, var(--rail-ink) 74%, var(--rail)); }
.menu .grp[open] > summary .ico { color: var(--rail-ink); }

.menu .grp[open] > summary { color: var(--rail-ink); font-weight: 600; }

/*  A chevron drawn from a border rather than a glyph, so it rotates cleanly
    and needs no icon font.  */
.menu .chev {
    margin-left: auto;
    width: .4rem; height: .4rem;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(-45deg);
    transition: transform .15s ease;
    opacity: .6;
    flex: none;
}

.menu .grp[open] .chev { transform: rotate(45deg); }

/* ══════════════════════════════════════════ the mark, at the rail head ══ */
/*
   IT CHANGED PLACES WITH THE PERSON, who is out in the bar now. The rail runs
   the full height of the window and is on screen whatever you are doing, so
   the product signs itself here rather than in the corner of one bar.

   ON A PLATE, and it has to be. The artwork is dark teal drawn on white, with
   opaque white inside the letters - it cannot be lightened for a dark ground,
   and against the rail as it stands it measures about 2:1. --logo-plate is
   white in every palette and in both modes for exactly this reason: it is the
   image own ground, not a design choice.

   STICKY, like the head it replaces, so the mark stays put while the modules
   under it scroll.
*/
/*  SCOPED TO .menu, and it has to be: ".menu a" is a class and a type, which
    outranks a bare ".rail-brand" - it was quietly winning the radius AND the
    padding of this plate.  */
.menu .rail-brand {
    display: flex;
    align-items: center;
    justify-content: center;

    position: sticky; top: 0; z-index: 2;
    margin-inline: calc(var(--gap-sm) * -1);
    margin-bottom: var(--gap-sm);

    /*  THE PLATE AND THE TOP BAR SHARE ONE BOTTOM EDGE, and they have to:
        they sit side by side at the top of the application, each with its own
        1px bottom border, and those two borders read as ONE line across the
        head of the screen. Any difference in height breaks that line and the
        rail looks like it is sliding out from under the bar.

        It was broken. The plate was padded .85rem above and below a 2.5rem
        mark, which came to 68px against a 49px bar - the white panel hung
        nineteen pixels below it, which is what "correct the menu and logo
        layout" was about.

        --bar-h ALREADY EXISTED FOR THIS, declared on .shell and used by
        .shell-right .topbar - and this plate was simply not taking it. The
        token's own comment says it was added because the two came out "2px
        apart, which at that width is a visible step"; the plate then grew its
        own padding and drifted nineteen. Both sides read it now, so there is
        one number and no way for them to disagree again.

        The padding is gone from the block axis on purpose: the height is the
        token's to decide, and align-items: center above puts the mark in the
        middle of whatever that is. Left and right padding stays, because it is
        what keeps the wordmark off the edges.  */
    min-height: var(--bar-h);
    padding: 0 var(--gap);

    background: var(--logo-plate);
    border-bottom: 1px solid color-mix(in srgb, var(--rail-ink) 15%, var(--rail));

    /*  Square: the shell clips the panel corner over it.  */
}

.menu .rail-brand img { height: var(--logo-h); width: auto; max-width: 100%; }

/*  The square mark waits until the rail is put away.  */
.menu .rail-brand .mark-square { display: none; }
.menu .rail-brand:hover { text-decoration: none; }


/*  COLLAPSED, THE WORDMARK IS CROPPED TO ITS CART. The rail is 3rem wide
    there, and a mark four and a half times wider than it is tall would come
    out nine pixels high - not a mark, a smudge. object-fit crops instead of
    scaling, and the cart sits at the left of the artwork, so what survives is
    the part somebody actually recognises.  */
.shell.is-collapsed .menu .rail-brand { padding: .55rem .25rem; }

/*  PUT AWAY, THE CART STANDS IN FOR THE WHOLE MARK.

    This was a crop of the wordmark - object-fit cutting the lettering off at
    whatever column the arithmetic landed on. It read as a picture that had
    failed to load rather than as a logo. It is a separate file now, cut from
    the same artwork at the gap the designer already left between the cart and
    the K, so nothing is sliced through.  */
.shell.is-collapsed .menu .rail-brand .mark-wide { display: none; }

.shell.is-collapsed .menu .rail-brand .mark-square {
    display: block;
    width: 1.7rem;
    height: 1.7rem;
    max-width: 100%;
}

/* ═════════════════════════════════════ the things you start, in the bar ══ */
/*
   THEY WERE BEHIND A "NEW" MENU. A menu is two clicks and a guess at what is
   inside it; six buttons are one click and no guess, and the row itself says
   what this end of the bar is for.

   Held together as one segmented control rather than left as six loose icons,
   because they ARE one thing - the half-dozen jobs somebody starts by habit -
   and six unattached buttons beside the shop name read as clutter.
*/
.starter-bar {
    display: inline-flex;
    align-items: center;
    gap: .15rem;
    flex: none;
    padding: .15rem;
    border-radius: var(--radius);
    background: var(--sunken);
    border: 1px solid var(--line);
}

.starter-bar .btn-icon { border-color: transparent; background: none; }

/*  NAMED, NOT JUST DRAWN. Each button is its glyph and a short word - Till,
    Sale, Quotation, Purchase, Order, Stock - so nobody has to hover to
    learn what six symbols mean. The button widens to fit the word; under
    78rem the words step back and the glyphs stay, because the bar also
    carries the shop, the date and whoever is keying.  */
.starter-bar .btn-icon:has(> .lbl) {
    width: auto;
    height: 1.85rem;
    padding: 0 .65rem 0 .5rem;
    gap: .35rem;
    font-size: .8125rem;
    font-weight: 600;
    color: var(--ink);
    text-decoration: none;
}
.starter-bar .btn-icon .lbl { white-space: nowrap; }
.starter-bar .btn-icon:has(> .lbl) .ico { color: var(--accent); }
.starter-bar .btn-icon:has(> .lbl):hover { background: var(--accent-soft); color: var(--accent); }
@media (max-width: 78rem) {
    .starter-bar .btn-icon .lbl { display: none; }
    .starter-bar .btn-icon:has(> .lbl) { width: 1.75rem; padding: 0; }
}

/*  ON A PHONE THE ROW STANDS DOWN. Six buttons is 220px of a 351px bar that
    also carries the rail toggle, the shop and whoever is keying - and every
    one of them is a row in the rail underneath, spelled out in words.  */
@media (max-width: 62rem) { .starter-bar { display: none; } }

.menu .grp-items {
    display: grid;
    gap: 1px;
    /*  Indented under the module and hung off a hairline, so a sub-screen is
        visibly inside something rather than another top-level entry.  */
    margin: 1px 0 .3rem .95rem;
    padding-left: .5rem;
    border-left: 1px solid var(--line);
}

.menu .grp-items a { font-size: .8125rem; padding: .34rem .5rem; }
.menu .grp-items .ico { width: 1rem; height: 1rem; opacity: .8; }

/*  Put away, the sub-screens go with it — the drawer keeps icons and the
    current highlight, and a nested list of unlabelled icons is unreadable.  */
.shell.is-collapsed .menu .grp-items,
.shell.is-collapsed .menu .chev { display: none; }
.shell.is-collapsed .menu .grp > summary { justify-content: center; padding-inline: 0; }

/* ═══════════════════════════════════════════════════════════ the pieces ══ */

/*  A PANEL ON A WORKING SCREEN IS NOT A PANEL ON A BROCHURE. 28px of padding
    is generous on a landing page and wasteful here, where four of these stack
    down a laptop and every row of air is a row of the register nobody can see.
    20px keeps them comfortably apart and gets a whole line back per panel.  */
.card {
    /*  Declared as a token because a child that bleeds to the card's edge -
        see .spec-row - has to cancel exactly this and no other number.  */
    --pad-card: 1.25rem;

    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--pad-card);
    box-shadow: var(--shadow-sm);
}

.card + .card { margin-top: var(--gap); }

.card-head {
    display: flex; align-items: center; gap: .65rem;
    margin-bottom: var(--gap);
    padding-bottom: .6rem;
    border-bottom: 1px solid var(--line);
}

/*  A PANEL HEADING IS NOT A PAGE HEADING. At the default h2 it was 20px and
    competing with the screen name above it; --fs-lg puts the two in order.  */
.card-head h2 { margin: 0; font-size: var(--fs-lg); letter-spacing: -.01em; }

/*  ON A PHONE THE HEADING KEEPS ITS LINE. With a sentence beside it in the
    same row, the heading was the thing that shrank - "Prices on the shelf"
    broke into three lines next to a caption that had plenty. Now the caption
    is what moves, to a row of its own under the heading.  */
@media (max-width: 40rem) {
    .card-head { flex-wrap: wrap; }
    .card-head h2 { flex: none; }
}

/*  THE PANEL ICON SITS IN A TINTED TILE - the same device the landing page
    uses, so the two halves of the product look like one product. It gives the
    heading a fixed thing to start from instead of a loose glyph, and it is
    drawn on the <svg> itself, so not one view had to change for it.  */
.card-head > .ico {
    width: 1.85rem; height: 1.85rem;
    padding: .42rem;
    flex: none;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);
}

.page-head {
    display: flex; align-items: flex-end; gap: var(--gap);
    margin-bottom: var(--gap-lg);
    flex-wrap: wrap;
}
.page-head p { margin: 0; }

/*  THE SCREEN NAME AT WORKING SIZE. It was inheriting the page h1 the landing
    uses for its one headline - 28px on a screen somebody opens forty times a
    day, which reads as a shout and pushes the first figure further down every
    single time. --fs-xl is the panel-heading step, and it is what a working
    screen wants.  */
.page-head h1 {
    display: flex;
    align-items: center;
    gap: .6rem;
    margin: 0;
    font-size: var(--fs-xl);
    letter-spacing: -.02em;
    line-height: 1.2;
}

/*  A SCREEN THAT LEADS WITH ITS MARK GETS THE SAME TILE the panels and the
    figures use. A bare glyph beside a heading reads as something that did
    not finish loading; the tile is what makes it deliberate, and it is the
    same device in the same product.  */
.page-head h1 > .ico {
    width: 2rem; height: 2rem;
    padding: .45rem;
    flex: none;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);
}

/*  Off the screen, still read aloud. For a heading that would only repeat what
    the bar above it already says: dropping the <h1> outright would leave the
    page with no heading at all, which is what a screen reader navigates by.  */
.only-spoken {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ---- a search box in the page heading ------------------------------------ */
/*
    THE PLACEHOLDER IS THE INSTRUCTION, so the box has to be wide enough to
    finish the sentence. A placeholder clipped mid-word is worse than none: it
    reads as a bug and it teaches nothing.

    It grows into whatever the heading is not using and stops at a comfortable
    reading width — past about 34rem a single line of text starts to need the
    eye to travel, and the buttons beside it get pushed off the row on a laptop.
*/
/*  Sized for what is actually in the row: a group chooser, the box, and two
    buttons. The type inside the box is the till's — 1.1rem monospace, roughly
    half again the width of the body face — so the room it needs is not what the
    character count suggests.

    GROW 3, NOT 1, because the spacer beside it also grows. At equal weights
    they split the free room evenly and the placeholder came up short of
    finishing. The spacer only has to push the buttons to the edge; the box has
    something to say.  */
.head-search { flex: 3 1 26rem; min-width: 0; max-width: 46rem; }

.head-search input[name="Q"] {
    flex: 1 1 auto;
    min-width: 0;
    /*  Ellipsis rather than a hard cut, for the narrow screens where even this
        cannot fit the whole sentence.  */
    text-overflow: ellipsis;
}

/*  The group sits beside the box at a FIXED width. Letting it grow would mean
    the search box shrank every time somebody added a category with a long name,
    and the placeholder in it is a sentence that has to finish.

    Same vertical padding as the input, so the two line up on their edges rather
    than nearly lining up — which is the difference a row like this is judged
    by. Smaller type, because it is a chooser and not the thing being typed in. */
.head-search select[name="CategoryId"] {
    flex: 0 0 auto;
    width: 11rem;
    font-size: .9375rem;

    /*  STRETCHED TO THE ROW, not padded to guess at the box's height. The
        input beside it is 1.1rem type inside a 2px border and the select is
        .9375rem inside 1px; matching padding still left them ten pixels apart,
        which on a single row reads as a mistake. Letting flex size it means
        they stay level if either ever changes.  */
    align-self: stretch;
    padding-block: 0;
}

/*  Below a tablet the heading stacks, and a box told to be 22rem wide in a
    20rem column is what pushes a page into sideways scrolling.  */
@media (max-width: 40rem) {
    .head-search { flex-basis: 100%; max-width: none; }
}

/* ---- the counts on the dashboard ----------------------------------------- */

.counts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(10rem, 100%), 1fr)); gap: var(--gap); }

.count {
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--gap);
}
.count b {
    display: block;
    font-family: var(--font-num);
    font-size: 1.75rem;
    line-height: 1.1;
    font-weight: 600;
}
.count span { font-size: .8125rem; color: var(--ink-soft); }

/* ---- the figures on the front page --------------------------------------- */
/*
   ONE FIGURE PER CARD, AND THE FIGURE IS THE BIGGEST THING ON IT.

   A dashboard is read from four feet away while somebody is doing something
   else — opening the shutter, taking a call. So each card is a caption, one
   number large enough to read across a counter, and one line of context
   underneath saying what the number is measured against. Anything more and it
   stops being glanceable, which is the only thing a dashboard is for.

   THE NUMBERS ARE MONOSPACED AND TABULAR so the column of them lines up on the
   decimal point. Six figures in six proportional fonts is six different widths
   and the eye cannot compare them.

   THE STRIPE CARRIES THE MEANING, NOT THE NUMBER'S COLOUR. Money coming in,
   money going out and money owed are different kinds of fact, and a shopkeeper
   learns the colours in a morning. The number itself stays ink-coloured except
   where its SIGN is the point — profit, which can be a loss.
*/

/*  COUNTED COLUMNS RATHER THAN auto-fit, because the number of cards is not a
    variable — it is six, and it will stay six. Left to fit itself, a wide
    screen packed five across and dropped the sixth onto a row of its own,
    which reads as one figure that got left out rather than as six that belong
    together. Three and three is balanced at every width a shop actually uses. */
.kpis {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--gap);
    margin-bottom: var(--gap-lg);
}
/*  ONE ROW ACROSS THE WINDOW, however many there are - the day's figures.
    Each card takes an equal share and the row folds only when a card would
    drop under 9rem, so a laptop shows five across and a phone stacks them.  */
.kpis.kpis-row {
    grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr));
    gap: var(--gap-sm);
    margin-bottom: var(--gap-sm);
}

@media (max-width: 68rem) { .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 38rem) { .kpis { grid-template-columns: minmax(0, 1fr); } }

.kpi {
    position: relative;
    display: block;
    overflow: hidden;
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    /*  Tighter than they were. Six of these across the top of the window is a
        band, not a row of posters - and every row of air in them is a row of
        the work underneath that nobody can see.  */
    padding: .75rem .85rem .8rem;
    color: var(--ink);

    /*  A slightly softer corner than the panels below, so the band of figures
        reads as a set of its own.  */
    border-radius: .75rem;

    /*  A HAIRLINE OF LIGHT ALONG THE TOP EDGE. One inset pixel, the way a
        surface catches light from above - it is what stops a flat tinted
        rectangle looking printed on. It costs nothing and it is the only
        decoration on the card.  */
    box-shadow: inset 0 1px 0 rgb(255 255 255 / .55);
}

/*  At night the light comes from the same place but there is far less of it. */
@media (prefers-color-scheme: dark) {
    :root:not([data-mode="day"]) .kpi { box-shadow: inset 0 1px 0 rgb(255 255 255 / .06); }
}
:root[data-mode="night"] .kpi { box-shadow: inset 0 1px 0 rgb(255 255 255 / .06); }

/*  A card that leads somewhere lifts; one that is only a figure does not, so
    the cursor tells the truth about which is which.  */
/*  NOTHING MOVES UNDER THE POINTER. It used to rise a pixel, and a pixel is
    small - but six of these sit in two rows and the eye crossing them saw the
    whole band twitch. The border taking the accent and the shadow arriving say
    "this one is a door" without anything leaving its place.  */
/*  A HOVER IN THE CARD'S OWN COLOUR. An accent border on a green card said
    "something happens here" in a voice that belonged to a different card;
    deepening the ground it already has, and filling the disc it already
    carries, says the same thing in the card's own terms.

    NOTHING MOVES. Six of these sit in two rows and a lift on each in turn
    makes the whole band twitch as the pointer crosses it.  */
a.kpi:hover, .kpi.hoverable:hover { box-shadow: inset 0 1px 0 rgb(255 255 255 / .55), var(--shadow); text-decoration: none; }

a.kpi.is-in:hover, .kpi.hoverable.is-in:hover {
    background: color-mix(in srgb, var(--ok-soft) 85%, var(--raised));
    border-color: color-mix(in srgb, var(--ok) 50%, var(--line));
}
a.kpi.is-out:hover, .kpi.hoverable.is-out:hover {
    background: color-mix(in srgb, var(--warn-soft) 85%, var(--raised));
    border-color: color-mix(in srgb, var(--warn) 50%, var(--line));
}
a.kpi.is-owed:hover, .kpi.hoverable.is-owed:hover {
    background: color-mix(in srgb, var(--bad-soft) 85%, var(--raised));
    border-color: color-mix(in srgb, var(--bad) 50%, var(--line));
}
.kpis > a.kpi:not(.is-in):not(.is-out):not(.is-owed):hover,
.kpis > .kpi.hoverable:not(.is-in):not(.is-out):not(.is-owed):hover {
    background: color-mix(in srgb, var(--accent-soft) 85%, var(--raised));
    border-color: color-mix(in srgb, var(--accent) 50%, var(--line));
}

/*  The disc fills. --raised is the answer to "what is legible on a tone at
    full strength" in both modes: near-white in day against a dark tone, and
    near-black at night against a light one.  */
a.kpi.is-in:hover .kpi-head .ico, .kpi.hoverable.is-in:hover .kpi-head .ico { background: var(--ok);   color: var(--raised); }
a.kpi.is-out:hover .kpi-head .ico, .kpi.hoverable.is-out:hover .kpi-head .ico { background: var(--warn); color: var(--raised); }
a.kpi.is-owed:hover .kpi-head .ico, .kpi.hoverable.is-owed:hover .kpi-head .ico { background: var(--bad);  color: var(--raised); }
.kpis > a.kpi:not(.is-in):not(.is-out):not(.is-owed):hover .kpi-head .ico,
.kpis > .kpi.hoverable:not(.is-in):not(.is-out):not(.is-owed):hover .kpi-head .ico {
    background: var(--accent); color: var(--accent-ink);
}

a.kpi .kpi-head .ico, .kpi.hoverable .kpi-head .ico { transition: background var(--dur) var(--ease), color var(--dur) var(--ease); }

a.kpi, .kpi.hoverable { transition: box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease); }

@media (prefers-reduced-motion: reduce) { a.kpi { transition: none; } }

/*  THE STRIPE IS GONE, AND THE TILE BESIDE THE LABEL TOOK ITS JOB.

    Three pixels down the left edge was the only thing separating money in
    from money out, and it was a legend nobody read. The icon itself is now
    green, amber or red, at the size somebody actually looks at. Two marks
    for one fact is the decoration this product is meant to be free of.  */

.kpi-head {
    display: flex;
    align-items: center;
    gap: .45rem;
    margin-bottom: .35rem;
}

/*  THE TILE CARRIES THE COLOUR THE STRIPE WAS CARRYING ALONE. Three pixels
    down the left edge is a legend nobody reads; a tinted tile beside the label
    says money-in, money-out or money-owed at a glance, and the icon inside it
    stops being a grey afterthought. Every colour is a theme token, so all six
    palettes and both modes get their own version for free.  */
.kpi-head .ico {
    width: 1.45rem; height: 1.45rem;
    padding: .32rem;
    flex: none;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);
}

/*  ═══ THE SAME TONES THE OWNER'S FIGURES CARRY ═══

    The mart's six cards already say which is money in, money out and money
    owed - is-in, is-out and is-owed have been on them since the register was
    written. Only the icon was using it. The ground uses it too now, so the
    two dashboards in this product read the same way and nobody has to learn
    the colours twice.

    The soft is mixed back into the card surface rather than used neat: six
    pastel blocks at full strength is a paint chart, not a billing screen.

    NOT ONE VIEW CHANGED FOR THIS. The classes were already there.  */

.kpi.is-in   { background: color-mix(in srgb, var(--ok-soft) 55%, var(--raised));
               border-color: color-mix(in srgb, var(--ok) 18%, var(--line)); }
.kpi.is-out  { background: color-mix(in srgb, var(--warn-soft) 55%, var(--raised));
               border-color: color-mix(in srgb, var(--warn) 18%, var(--line)); }
.kpi.is-owed { background: color-mix(in srgb, var(--bad-soft) 55%, var(--raised));
               border-color: color-mix(in srgb, var(--bad) 18%, var(--line)); }

/*  The two that carry no in-or-out meaning - what is on the shelf, and what
    was earned - take the accent instead. They are still figures about money,
    just not money crossing the counter in either direction.  */
.kpis > .kpi:not(.is-in):not(.is-out):not(.is-owed) {
    background: color-mix(in srgb, var(--accent-soft) 55%, var(--raised));
    border-color: color-mix(in srgb, var(--accent) 18%, var(--line));
}

/*  A shut card keeps the shape and loses the colour: a cashier who may not
    see margin should not be shown a card that looks full of it.  */
.kpi.is-shut { background: var(--raised); border-color: var(--line); }

/*  The rule under the figure follows the tone, so it belongs to the card
    rather than looking like a line that landed on it.  */
.kpi.is-in   .kpi-sub { border-top-color: color-mix(in srgb, var(--ok) 20%, transparent); }
.kpi.is-out  .kpi-sub { border-top-color: color-mix(in srgb, var(--warn) 20%, transparent); }
.kpi.is-owed .kpi-sub { border-top-color: color-mix(in srgb, var(--bad) 20%, transparent); }
.kpis > .kpi:not(.is-in):not(.is-out):not(.is-owed) .kpi-sub {
    border-top-color: color-mix(in srgb, var(--accent) 20%, transparent);
}

/*  The disc, matching the owner's.  */
.kpi-head .ico { border-radius: 50%; }
.kpis > .kpi:not(.is-in):not(.is-out):not(.is-owed) .kpi-head .ico {
    background: var(--accent-soft); color: var(--accent);
}

.kpi.is-in   .kpi-head .ico { background: var(--ok-soft);   color: var(--ok); }
.kpi.is-out  .kpi-head .ico { background: var(--warn-soft); color: var(--warn); }
.kpi.is-owed .kpi-head .ico { background: var(--bad-soft);  color: var(--bad); }

/*  THE LABEL TAKES THE CARD'S OWN COLOUR, and it had to: on a tinted ground
    --ink-faint measured 3.11:1 to 3.19:1 across the six cards, under the 4.5
    that text of this size owes. It was fine on the plain white card it was
    written for; tinting the ground moved the floor under it.

    Taking the tone fixes the reading and ties the head to the card at the
    same time - the label, the disc and the ground now say one thing.  */
.kpi-head span {
    font-size: .6875rem;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

.kpi.is-in   .kpi-head span { color: var(--ok); }
.kpi.is-out  .kpi-head span { color: var(--warn); }
.kpi.is-owed .kpi-head span { color: var(--bad); }
/*  The accent came out at 4.40 against its own tint - the narrowest of the
    four, and still short. Pulled a fifth of the way towards the ink, which
    darkens it in day and lightens it at night, because ink follows the mode. */
.kpis > .kpi:not(.is-in):not(.is-out):not(.is-owed) .kpi-head span {
    color: color-mix(in srgb, var(--accent) 78%, var(--ink));
}

/*  A shut card keeps the grey: there is no tone on it to take.  */
.kpi.is-shut .kpi-head span { color: var(--ink-soft); }

.kpi-num {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: 1.5rem;
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: -.02em;
    margin: 0;
    white-space: nowrap;
}

/*  A figure too long for its card shrinks rather than wrapping onto a second
    line: a lakh and a thousand must not make one card taller than its
    neighbours and break the row.  */
@media (max-width: 90rem) { .kpi-num { font-size: 1.4rem; } }
@media (max-width: 78rem) { .kpi-num { font-size: 1.3rem; } }

.kpi-num.up   { color: var(--ok); }
.kpi-num.down { color: var(--bad); }

.kpi-sub {
    font-size: .75rem;
    color: var(--ink-soft);
    margin: .4rem 0 0;
    padding-top: .4rem;
    border-top: 1px solid var(--line);
    line-height: 1.4;
}

.kpi-sub b { font-family: var(--font-num); font-variant-numeric: tabular-nums; color: var(--ink); }

/*  WHAT A SHUT GATE LOOKS LIKE. A cashier has no business seeing margin, and
    the procedure never computed it for them — so the card says so rather than
    showing a zero that reads as "the shop earned nothing today".  */
.kpi.is-shut { opacity: .55; }
.kpi.is-shut .kpi-num { font-size: 1rem; color: var(--ink-faint); font-weight: 500; }

/* ---- the bottom half of the dashboard ------------------------------------ */
/*
   THE FIGURES ARE READ IN FOUR SECONDS. What follows is the other half of the
   job: what needs doing, what has been going on, and the doors somebody opens
   most mornings. A dashboard that reports and then stops leaves the reader to
   go and find the screen that fixes whatever they just read.

   TWO COLUMNS, because they are read differently: the left is a list somebody
   works down, the right is a set of things they glance at and click.
*/

.deck {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: var(--gap);
    align-items: start;
}

@media (max-width: 68rem) { .deck { grid-template-columns: minmax(0, 1fr); } }

.deck-main, .deck-side { display: grid; gap: var(--gap); align-content: start; }

/*  ── what needs doing ── */
/*  A LIST, NOT A TABLE. Every line is a sentence ending in a link, because the
    point of it is the link: naming a problem without offering the screen that
    fixes it is how a dashboard becomes something people scroll past.  */

.todo { list-style: none; margin: 0; padding: 0; display: grid; gap: .1rem; }

.todo li {
    position: relative;
    padding: .55rem .7rem .55rem 1.1rem;
    font-size: .875rem;
    line-height: 1.45;
    border-radius: var(--radius);
}

.todo li + li { border-top: 1px solid var(--line); border-top-left-radius: 0; border-top-right-radius: 0; }
.todo li:hover { background: var(--sunken); }

/*  The stripe says how much it costs to ignore. Three states only — a scale
    with five steps is one nobody learns.  */
.todo li::before {
    content: '';
    position: absolute;
    left: .35rem; top: .75rem;
    width: .35rem; height: .35rem;
    border-radius: 50%;
    background: var(--ink-faint);
}

.todo li.warn::before { background: var(--warn); }
.todo li.bad::before  { background: var(--bad); }

.todo a { font-weight: 600; white-space: nowrap; }

/*  ── what has been going on ── */

.feed { display: grid; }

.feed-row {
    display: grid;
    grid-template-columns: 1.5rem minmax(0, 1fr) auto auto;
    align-items: center;
    gap: .6rem;
    padding: .5rem .2rem;
    color: var(--ink);
    border-bottom: 1px solid var(--line);
    font-size: .875rem;
}

.feed-row:last-child { border-bottom: none; }
.feed-row { transition: background var(--dur) var(--ease); }
.feed-row:hover { background: var(--sunken); text-decoration: none; }

.feed-ico .ico { width: 1.1rem; height: 1.1rem; color: var(--ink-faint); }

.feed-what { display: flex; flex-direction: column; min-width: 0; gap: .1rem; }

/*  The number and its pill on one line. Without this row the pill is a direct
    child of the column above and stretches to the card's full width, drawing a
    coloured bar across every cancelled document.  */
.feed-name { display: flex; align-items: center; gap: .35rem; min-width: 0; }
.feed-name .pill { flex: none; }

.feed-what b { font-size: .8125rem; }
.feed-what .faint { font-size: .75rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.feed-when { font-size: .75rem; }
.feed-amt { font-variant-numeric: tabular-nums; font-weight: 600; }

/*  ── the doors somebody opens most mornings ── */

.quick { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .4rem; }

@media (max-width: 26rem) { .quick { grid-template-columns: minmax(0, 1fr); } }

.quick-go {
    display: flex; align-items: center; gap: .45rem;
    padding: .6rem .55rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--sunken);
    color: var(--ink);
    font-size: .8125rem;
    font-weight: 600;
    line-height: 1.2;
}

.quick-go:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
.quick-go .ico { width: 1.05rem; height: 1.05rem; flex: none; }

/*  ── promises nobody's books will mention ── */

.waiting { display: grid; gap: .3rem; }

.waiting-row {
    display: flex; align-items: baseline; gap: .5rem;
    padding: .45rem .2rem;
    color: var(--ink);
    font-size: .8125rem;
}

.waiting-row:hover { text-decoration: none; color: var(--accent); }
.waiting-row b { font-size: 1.25rem; font-variant-numeric: tabular-nums; min-width: 2ch; }

/* ---- the module tiles ---------------------------------------------------- */

/*  EVERY TILE THE HEIGHT OF THE TALLEST. Three cards of 161px above three of
    142px is a grid that looks like it slipped, and the ragged edge is the
    first thing the eye finds on a page whose whole job is to look ordered.
    One line does it, and it costs nothing: the row simply takes its height
    from the longest description in the set.  */
.tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
    grid-auto-rows: 1fr;
    gap: var(--gap);
}

.tile {
    /*  A column, so the chips at the foot of one card line up with the chips
        at the foot of the next however long the sentence above them runs.  */
    display: flex;
    flex-direction: column;

    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--gap);
    color: var(--ink);
    transition: border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
}

/*  THE TILE ANSWERS THE POINTER - the owner's call: a rounder card with a
    firmer, accent-tinted edge, and on hover it lifts two pixels, the edge
    goes to the mart's colour, a soft coloured shadow appears, the icon tile
    fills, and the chevron at its foot slides right. All from the tokens.  */
.tile {
    border-radius: var(--radius-lg);
    border-color: color-mix(in srgb, var(--accent) 22%, var(--line-strong));
    box-shadow: var(--shadow-sm);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease), background var(--dur) var(--ease);
}
.tile:hover {
    text-decoration: none;
    transform: translateY(-2px);
    border-color: var(--accent);
    box-shadow: 0 10px 24px -12px color-mix(in srgb, var(--accent) 55%, transparent), var(--shadow);
}
.tile:hover .tile-head .ico { background: var(--accent); color: var(--accent-ink); }
.tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/*  The rights card is its head alone now - the chips sit beside the
    heading - so the head needs no rule under it and the card no room
    below.  */
.card:has(> .card-head > .rights):not(:has(> :not(.card-head))) { padding-block: .55rem; }
.card:has(> .card-head > .rights):not(:has(> :not(.card-head))) > .card-head { margin: 0; padding: 0; border: 0; }
.card-head > .rights { margin-left: .5rem; }

/*  A toned tile answers in its own colour, the same way the mart's figures
    do - see a.kpi:hover.  */
.tile.tone-ok:hover     { background: color-mix(in srgb, var(--ok-soft) 85%, var(--raised));
                          border-color: color-mix(in srgb, var(--ok) 50%, var(--line)); }
.tile.tone-warn:hover   { background: color-mix(in srgb, var(--warn-soft) 85%, var(--raised));
                          border-color: color-mix(in srgb, var(--warn) 50%, var(--line)); }
.tile.tone-bad:hover    { background: color-mix(in srgb, var(--bad-soft) 85%, var(--raised));
                          border-color: color-mix(in srgb, var(--bad) 50%, var(--line)); }
.tile.tone-accent:hover { background: color-mix(in srgb, var(--accent-soft) 85%, var(--raised));
                          border-color: color-mix(in srgb, var(--accent) 50%, var(--line)); }

.tile .tile-head .ico { transition: background var(--dur) var(--ease), color var(--dur) var(--ease); }
.tile.tone-ok:hover     .tile-head .ico { background: var(--ok);     color: var(--raised); }
.tile.tone-warn:hover   .tile-head .ico { background: var(--warn);   color: var(--raised); }
.tile.tone-bad:hover    .tile-head .ico { background: var(--bad);    color: var(--raised); }
.tile.tone-accent:hover .tile-head .ico { background: var(--accent); color: var(--accent-ink); }

.tile.is-locked { opacity: .6; }
.tile.is-locked:hover { border-color: var(--line); box-shadow: none; }

.tile-head { display: flex; align-items: center; gap: .6rem; margin-bottom: .5rem; }

/*  THE ICON SITS IN A TINTED TILE, the same device the figures cards and the
    panel headings use. A loose glyph beside a label reads as an afterthought;
    a tile gives the heading something to start from, and it is the one place
    the accent appears at full strength on these cards.  */
.tile-head .ico {
    width: 1.9rem; height: 1.9rem;
    padding: .45rem;
    flex: none;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);
}

.tile.is-locked .tile-head .ico { background: var(--sunken); color: var(--ink-faint); }
.tile-head b { font-size: .9375rem; letter-spacing: -.01em; }
.tile p { font-size: .8125rem; color: var(--ink-soft); margin: 0 0 .6rem; line-height: 1.45; }

/*  DEVANAGARI ASKS FOR MORE ROOM BETWEEN LINES THAN LATIN DOES, and it is not
    a matter of taste: matras sit above the headline and below the baseline, so
    a leading that is comfortable for Inter puts the top of one line into the
    bottom of the last. A touch more, on anything marked as Hindi.

    A bare [lang="hi"] does not reach these: it scores one class, and ".tile p"
    scores a class and a type, so the 1.45 above kept winning. Measured 18.85px
    on a 13px line - the rule was there and doing nothing.  */
[lang="hi"],
.tile p[lang="hi"] { line-height: 1.65; }

/*  The rights sit on the floor of the tile, not under the last line of prose. */
.tile > .rights { margin-top: auto; padding-top: .2rem; }

/*  ── the door mark ──
    A tile you can open says so with a chevron in the corner. It darkens
    rather than moves: these come six to a screen, and six marks sliding in
    turn as the pointer crosses them is the twitch this product is free of.  */
a.tile { position: relative; }

a.tile::after {
    content: "";
    position: absolute;
    right: .85rem; bottom: .95rem;
    width: .4rem; height: .4rem;
    border-right: 2px solid var(--ink-faint);
    border-bottom: 2px solid var(--ink-faint);
    transform: rotate(-45deg);
    transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}

a.tile:hover::after { border-color: var(--accent); transform: rotate(-45deg) translate(2px, 2px); }

/*  Room for it, so a long line of chips never runs under the mark.  */
a.tile > .rights { padding-right: 1.1rem; }

/*  THE FIGURE ITSELF, which four of these carried as an inline style repeated
    on every card - the same three declarations written out four times, so a
    change of mind meant four edits and a fifth card would have been written
    at whatever size the last one was copied at.

    Tabular numerals because these sit in a row and the eye compares them
    down the column, not along the line.  */
.tile .tile-num {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums;
    font-size: 1.6rem;
    font-weight: 600;
    line-height: 1.15;
    letter-spacing: -.02em;
    color: var(--ink);
    margin: .15rem 0 .45rem;
    white-space: nowrap;
}

/*  The sign is the whole point of the earnings figure, so that one takes a
    colour. The rest stay ink: a row of green numbers says nothing by saying
    it everywhere.  */
.tile .tile-num.up   { color: var(--ok); }
.tile .tile-num.down { color: var(--bad); }

/*  The supporting line sits under a hairline, so the figure and the sentence
    that qualifies it are two things rather than one run-on.  */
.tile .tile-num + p {
    margin-top: 0;
    padding-top: .5rem;
    border-top: 1px solid var(--line);
}

/*  The four rights, spelled out on the tile. Somebody who can see that they
    hold View and Add but not Edit never has to discover the third by pressing
    something and being refused.  */
.rights { display: flex; gap: .25rem; flex-wrap: wrap; }

/*  THE FOUR RIGHTS, AND THE ONE THAT WAS NOT READABLE.

    A held right was accent on accent-soft, which measures 4.08:1 - under the
    4.5 that text of this size owes, on the one chip the card exists to show.
    Filled, it uses the palette's own answer to "what is legible on the
    accent" and clears it in every theme.

    A held right also carries a tick, drawn from two borders. The colour said
    it on its own, which is a poor way to say anything: somebody who cannot
    separate the two chips by colour now has a mark to go on.  */
.right-chip {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    font-size: .6875rem;
    font-weight: 600;
    padding: .2rem .55rem;
    border-radius: 1rem;
    border: 1px solid var(--line-strong);
    color: var(--ink-soft);
    background: var(--raised);
}

/*  TINTED, NOT FILLED - and that is a decision about a different screen.

    Filled reads best on the module overview, where four chips sit alone in a
    band. But Home/Modules shows ten modules with four each: forty solid
    accent pills on one page is a wall, and the accent stops meaning anything
    the moment it is everywhere.

    So the ground stays soft and the TEXT is what changes - pulled towards the
    ink until it clears 4.5:1, which the plain accent did not. Ink follows the
    mode, so this darkens by day and lightens at night on its own.  */
.right-chip.on {
    background: var(--accent-soft);
    border-color: color-mix(in srgb, var(--accent) 55%, var(--line-strong));
    color: color-mix(in srgb, var(--accent) 62%, var(--ink));
    font-weight: 700;
}

.right-chip.on::before {
    content: "";
    width: .3rem; height: .55rem;
    margin-top: -.15rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg);
    flex: none;
}

/*  A right somebody does NOT hold is not a hole in the row - it is a fact
    about them, so it is drawn quietly rather than left to look unfinished.  */
.right-chip:not(.on) { border-style: dashed; }

/* ═══════════════════════════════════════════════════════════ the tables ══ */

.grid { width: 100%; border-collapse: collapse; font-size: .875rem; }

/* ══════════════════════════════════════════════ the bill format rows ══ */
/*
   ONE ROW OF THE RECEIPT LAYOUT: whether it prints, what it is called, where
   it comes in the order, and how it is aligned. The same table serves the
   item columns and the totals - see _BillFormatLines.cshtml.

   The caption box takes the whole cell. It was at its default width, which is
   a width nobody chose: it left the field key stranded beside it and the
   column ragged down the page.
*/
.bf-caption input[type="text"] {
    width: 100%;
    min-width: 0;
}

/*  THE NAME THE SOFTWARE USES, under the name the owner gave it. Once
    somebody has renamed "Total Payable" to something of their own, this is
    the only way to tell which line of the bill they are looking at - so it is
    quiet, but it is not decoration.  */
.bf-key {
    display: block;
    margin-top: .2rem;
    font-size: .6875rem;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
}

/*  A HEADING ROW MUST NOT WRAP. Eight short labels above eight columns of
    figures line up only while each one stays on its line; the moment "Owed
    to you" breaks in two, that column sits a row lower than its neighbours
    and the whole band looks broken. The table already scrolls sideways in
    its own box, which is where the width should go instead.  */
.grid th {
    text-align: left;
    /*  A size up and a weight up. At 11px/700 on the band the headings read
        grey though they were full ink - too little stroke for the colour to
        show. 12px/800 is still a caption, and now it reads as dark as the
        names under it.  */
    font-size: .75rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    font-weight: 800;
    white-space: nowrap;
    padding: .6rem .7rem;
    border-bottom: 1px solid var(--line-strong);

    /*  A DARK GREY BAND WITH THE MART'S COLOUR IN IT - not black. --rail is
        the rail's dark; lifted a fifth toward the surface it becomes a deep
        grey, and a twelfth of the accent folded in makes it this mart's grey
        rather than anybody's. By night the same mix lands a shade above the
        sunken tone, still darker than the rows. --rail-ink reads on it in
        both rooms. Change the theme and the band changes with it.  */
    background: color-mix(in srgb,
                    color-mix(in srgb, var(--rail) 78%, var(--raised)) 88%,
                    var(--accent));
    /*  THE WORDS ON THE BAND ARE WHITE, WHATEVER THE THEME - the owner's
        call, and the one place in this file a colour is fixed on purpose.
        The band is always a dark grey (see above), so white reads on it in
        every palette and in both rooms; letting it follow --rail-ink meant
        the headings changed colour with the theme while the band did not
        visibly, which read as a mistake.  */
    color: #FFFFFF;
    position: sticky; top: 0;
}

/*  Rows a person reads across need height as much as they need width; at
    .55rem a row carrying a name, a sub-line and a pill was tighter than the
    text inside it.  */
.grid td {
    padding: .7rem;
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
    transition: background var(--dur) var(--ease);
}

.grid tbody tr:hover td { background: var(--sunken); }
.grid tbody tr:last-child td { border-bottom: 0; }

/*  THE BAND HAS ENDS. Square corners on a coloured heading row inside a
    rounded panel is the detail that makes a table look pasted in.  */
.grid thead th:first-child { border-top-left-radius: var(--radius-sm); }
.grid thead th:last-child  { border-top-right-radius: var(--radius-sm); }

/*  A NAME IS READ, NOT WRAPPED. The first column carries the thing the row
    is about, and breaking it over two lines to save a few pixels makes
    every row a different height. The table already scrolls sideways in its
    own box - that is where the width should come from.  */
.grid td:first-child > strong { white-space: nowrap; }

/*  A name that must stay on one line. The item list's names were folding
    their pack size onto a second line; marked .one-line they hold, the
    column widens to fit, and a narrow window scrolls the table sideways in
    its scroller instead.  */
.grid .one-line { white-space: nowrap; }

/*  A row that is off - a cancelled bill, a deactivated item - fades, the way
    a switched-off price row does in the MRP grid. Its actions keep full
    strength so the way back on is still obviously a button.  */
.grid tbody tr.off td { color: var(--ink-faint); }
.grid tbody tr.off td > strong { color: var(--ink-soft); }

/*  The actions column takes exactly what its icons need and no more.  */
.grid td:has(> .row-acts) { width: 1%; white-space: nowrap; }

/*  THE TOTALS ROW IS A CONCLUSION, NOT ANOTHER ROW. It only appears when
    there is more than one mart, and when it does it is the line an owner
    reads first - so it is separated by a rule of its own weight and set in
    ink rather than in the heading grey.  */
.grid tfoot th {
    position: static;
    background: transparent;
    color: var(--ink);
    font-size: .875rem;
    text-transform: none;
    letter-spacing: 0;
    border-top: 2px solid var(--line-strong);
    border-bottom: 0;
    padding-block: .75rem;
}
.grid tfoot th.num { font-family: var(--font-num); font-variant-numeric: tabular-nums; }
.grid td.num, .grid th.num { font-family: var(--font-num); text-align: right; font-variant-numeric: tabular-nums; }

/*  .r IS "RIGHT", AND IT HAD NEVER BEEN WRITTEN DOWN.

    Seventy-one cells across the product carry class="r" and no stylesheet
    defined it, so every one of them has been sitting left-aligned. The
    printed documents got away with it because each of them declares the same
    rule in its own <style> and sets Layout = null - which is how the name
    stayed obvious to everybody writing views and invisible to the screen.

    It is not .num: these are cells of buttons at the end of a row, not
    figures, so they want the alignment and none of the numeral face.  */
.grid td.r, .grid th.r, td.r, th.r { text-align: right; }

/*  A WIDE TABLE SCROLLS INSIDE ITS OWN BOX. Letting the page scroll sideways
    instead takes the menu and the top bar with it, and a screen whose menu has
    slid off the left is a screen somebody thinks has broken.

    THE PULL HAS TO MATCH THE PANEL IT SITS IN. It was written against the
    28px .card padding; when that came down to 20px the table started hanging
    7px past its own panel on both sides. One value, named once, so the two
    cannot drift apart again.  */
/*  IT PULLS OUT BY EXACTLY THE CARD'S PADDING, whatever that card is.

    The number was written here as a copy of .card's padding, and a copy goes
    stale: inside a .card.compact - which pads by --gap, not 1.25rem - the
    table was pulled 20px out and padded 16px back, so every grid in a compact
    card stood 4px proud of the panel on each side. The heading band visibly
    overhung the rounded corner it was supposed to sit inside.

    The card states its own padding as --pad-card now, and this reads it. The
    fallback is for a .scroller that is not in a card at all.  */
.scroller {
    --pull: var(--pad-card, 1.25rem);
    overflow-x: auto;
    margin-inline: calc(var(--pull) * -1);
    padding-inline: var(--pull);
}

.empty {
    padding: var(--gap-lg);
    text-align: center;
    color: var(--ink-soft);
    background: var(--sunken);
    border-radius: var(--radius);
}

/* ════════════════════════════════════════════════════════════ the forms ══ */

/*  align-items: start, and it is not cosmetic.

    Without it every cell stretches to the tallest in its row, so a field
    carrying a two-line hint makes the input BESIDE it grow to match — and the
    owner's forms ended up with four boxes at three different heights, sitting
    on no common baseline. Each field is now its own height and they align at
    the top, which is where the eye reads them from.  */
.fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
    gap: var(--gap);
    align-items: start;
}

/*  `hidden` HAS TO WIN. Half the layout sets display on a class - .field is a
    grid, .count is a flex - and every one of those beats the browser's own
    display:none for [hidden]. Screens were toggling el.hidden and nothing was
    happening. One rule, before the layout, and the attribute means what it
    says everywhere.  */
[hidden] { display: none !important; }

.field { display: grid; gap: .25rem; min-width: 0; align-content: start; }

/*  The hint is pulled out of the flow's height contribution where it can be,
    so one long hint does not push the row below it down.  */
.field .hint { line-height: 1.35; }
.field.wide { grid-column: 1 / -1; }

/*  CAPTIONS ARE FULL INK, on every form. They were --ink-soft, then still
    read as grey next to what was typed under them; the owner wanted the
    label as dark as the value. The size and weight are what keep it a
    caption rather than a second value.  */
.field > label { font-size: .8125rem; font-weight: 600; color: var(--ink); }

.field input, .field select, .field textarea {
    font: inherit;
    color: var(--ink);
    background: var(--raised);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    padding: .45rem .65rem;
    min-height: var(--ctl-h);
    width: 100%;
    min-width: 0;
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.field textarea { min-height: 4.5rem; }

/*  A box under the pointer takes the mart's colour on its edge - the
    owner's call - so every form answers the mouse in one voice.  */
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }

/*  A ring rather than a thicker border, so the control does not change size on
    focus and shove the field beside it sideways by a pixel.  */
.field input:focus, .field select:focus, .field textarea:focus {
    border-color: var(--accent);
    outline: none;
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/*  THE DENSE GRIDS KEEP THEIR OWN HEIGHT, and this is not a detail to tidy
    away. A purchase bill is forty controls on one screen; the comfortable
    height above is right for a six-field form and wrong here, where it would
    push the last line of a bill below the fold. These three already set their
    own padding - they now have to unset the height that came with it.  */
.fields.tight input, .fields.tight select,
.fields.g12 input, .fields.g12 select, .fields.g12 textarea,
.fields.compact input, .fields.compact select, .fields.compact textarea {
    min-height: 0;
}

.field input[readonly] { background: var(--sunken); color: var(--ink-soft); }

.field .hint { font-size: .75rem; color: var(--ink-faint); }
.field .err  { font-size: .75rem; color: var(--bad); }

.form-foot {
    display: flex; align-items: center; gap: var(--gap-sm);
    margin-top: var(--gap-lg);
    padding-top: var(--gap);
    border-top: 1px solid var(--line);
    flex-wrap: wrap;
}

/* ══════════════════════════════════════════════════════════ the buttons ══ */

/*  ONE HEIGHT, SET BY min-height RATHER THAN BY PADDING.

    Padding alone gives a button whatever height its own font and line-height
    happen to produce, so a button with an icon and a button without one end up
    a pixel or two apart - which is invisible on its own and obvious in a row
    of four. min-height pins them; the padding only decides the sides.  */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
    font: inherit; font-weight: 600; font-size: .875rem;
    min-height: var(--ctl-h);
    padding: .4rem 1.1rem;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-btn);
    background: var(--raised);
    color: var(--ink);
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--dur) var(--ease),
                border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}
/*  EVERY BUTTON ANSWERS THE POINTER THE SAME WAY - the owner's call, after
    the item screen's got it first. The plain one takes the mart's colour
    on hover (tinted, with the accent as its edge), lifts a pixel and casts
    a small shadow; the primary brightens and lifts; the quiet one gains a
    tint; the bad one goes red. All from the theme's tokens.  */
.btn:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent);
    text-decoration: none;
    transform: translateY(-1px);
    box-shadow: 0 3px 8px -2px rgb(0 0 0 / .18);
}

/*  PRESSED IS A REAL STATE, and it was missing. A button that looks identical
    while held gives no confirmation the press registered, and on a touch
    screen that is what makes somebody press a second time - which on a save is
    exactly the press you do not want.  */
.btn:active { transform: translateY(1px); box-shadow: none; }

/*  The one button on a screen that carries the action. The lift is small
    enough to read as material rather than as a toy.  */
.btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
    box-shadow: 0 1px 2px rgb(0 0 0 / .10);
}
.btn-primary:hover {
    filter: brightness(1.07);
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
    box-shadow: 0 4px 12px -2px rgb(0 0 0 / .22);
}
.btn-primary:active { filter: brightness(.97); box-shadow: 0 1px 2px rgb(0 0 0 / .10); }

.btn-quiet { border-color: transparent; background: transparent; color: var(--ink-soft); }
.btn-quiet:hover { background: var(--accent-soft); border-color: transparent; color: var(--accent); box-shadow: none; }

.btn-bad { color: var(--bad); border-color: var(--line-strong); }
.btn-bad:hover { background: var(--bad-soft); border-color: var(--bad); color: var(--bad); }

.btn-sm { min-height: var(--ctl-h-sm); padding: .2rem .7rem; font-size: .8125rem; }

/*  THE HERO PAIR, and nothing else should use this. A call to action is bigger
    than a toolbar button because it is the one thing on the screen a first
    visitor is meant to press; using it anywhere else spends that emphasis.  */
.btn-lg {
    min-height: var(--ctl-h-lg);
    padding-inline: 1.6rem;
    font-size: .9375rem;
    border-radius: var(--radius-btn);
}

/*  A button that is genuinely unavailable says so, rather than looking pressable
    and doing nothing - one of the faults called out in the brief.  */
.btn[disabled], .btn[aria-disabled="true"] {
    opacity: .55;
    cursor: not-allowed;
    pointer-events: none;
}

.pill {
    display: inline-block;
    font-size: .6875rem; font-weight: 700;
    padding: .1rem .45rem;
    border-radius: 1rem;
}
.pill.ok  { background: var(--ok-soft);  color: var(--ok); }
.pill.off { background: var(--bad-soft); color: var(--bad); }
.pill.sys { background: var(--accent-soft); color: var(--accent); }
/*  Two tones the item list asks for and nothing had drawn: a pack past its
    date, and one within the warning window.  */
.pill.bad  { background: var(--bad-soft);  color: var(--bad); }
.pill.warn { background: var(--warn-soft); color: var(--warn); }
.pill { white-space: nowrap; }

/* ══════════════════════════════════════════════════════════ the notices ══ */
/*  Said and Wrong are separate classes for the same reason they are separate
    TempData keys: a screen must never show both and leave somebody guessing
    which one is true.  */

.notice {
    padding: .65rem .85rem;
    border-radius: var(--radius-sm);
    border: 1px solid;
    margin-bottom: var(--gap);
    font-size: .875rem;
}
.notice.ok  { background: var(--ok-soft);  border-color: var(--ok);  color: var(--ok); }
.notice.bad { background: var(--bad-soft); border-color: var(--bad); color: var(--bad); }

.errors { margin: 0 0 var(--gap); padding: .65rem .85rem .65rem 1.6rem; }

/* ═══════════════════════════════════════════════════ sign-in and landing ══ */

.slab { display: grid; place-items: center; min-height: 78vh; padding-block: var(--gap-lg); }

.slab .card { width: min(26rem, 100%); }

.slab-wide .card { width: min(34rem, 100%); }

.slab-foot { margin-top: var(--gap); font-size: .875rem; color: var(--ink-soft); text-align: center; }

/* ══════════════════════════════════════════════════ the three front doors ══ */
/*
   Sign in, register, and the mart's own door.

   THERE ARE THREE OF THEM AND THAT IS THE DESIGN PROBLEM. A lone centred card
   said "sign in" and left the visitor to work out which sign-in they had
   found; the owner's door and the mart's door look identical from the outside
   and lead to different places, and somebody standing at the wrong one gets
   "we do not know that address" for a password that is perfectly correct.

   So the panel is split: the form on one side, and on the other a side that
   says WHOSE DOOR THIS IS before anything is typed. The side is not
   decoration — it is the label on the door, and the switch to the other one
   sits under the form where somebody who has failed once will look.

   ONE PANEL, NOT TWO CARDS. The aside and the form share a border and a
   shadow, so it reads as a single object with two halves rather than a form
   with a poster next to it.
*/

.auth { display: grid; place-items: center; min-height: 78vh; padding-block: var(--gap-lg); }

/*  ONE CARD, TWO HALVES - and the halves must not read as two cards. The
    radius is on this element only, the join between them is a change of tint
    rather than a rule, and the shadow belongs to the whole thing.

    .9fr rather than .8fr: the left is now carrying a picture as well as the
    words, and at .8 the illustration had to be scaled down far enough that the
    till screen in it stopped being legible.  */
.auth-panel {
    width: min(60rem, 100%);
    display: grid;
    grid-template-columns: minmax(0, .9fr) minmax(0, 1fr);
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

/*  The accent tint, which is the one place in the product where the palette
    gets to introduce itself before somebody is inside it.  */
/*  A COMPOSITION, NOT A SWATCH.

    The panel used to be one flat fill, and a flat fill is the worst possible
    ground to dissolve a photograph into: the fade has nothing to fade INTO,
    so the eye finds the boundary anyway. Two soft washes - the accent lifting
    out of the top-left, and a deeper pool in the bottom-right where the
    picture arrives - give the fade somewhere to land.

    BOTH WASHES ARE BUILT FROM --accent, so all six palettes get their own
    version of this and none of it is hard-coded. color-mix keeps the alpha
    relative to whatever the theme's accent happens to be.

    NO SEPARATING BORDER. The border-inline-end drew a line down the middle of
    the page and made the two halves read as two boxes set side by side, which
    is the thing the brief is against. The tint change alone is enough of a
    join, and it is a softer one.  */
.auth-aside {
    background:
        radial-gradient(90% 70% at 12% 0%,
            color-mix(in srgb, var(--accent) 14%, transparent), transparent 70%),
        radial-gradient(85% 75% at 92% 100%,
            color-mix(in srgb, var(--accent) 20%, transparent), transparent 72%),
        var(--accent-soft);

    padding: clamp(1.25rem, 3vw, 2rem);
    display: flex;
    flex-direction: column;
    gap: var(--gap);

    /*  This is what removes the picture's other two edges - see .auth-art.
        Without it the tilted image would hang over the form beside it.  */
    overflow: hidden;
}


/*  Which door, said in one word, in the corner where a sign would be.  */
.auth-door {
    align-self: flex-start;
    font-size: .6875rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: .08em;
    color: var(--accent-ink);
    background: var(--accent);
    border-radius: 2rem;
    padding: .2rem .6rem;
}

.auth-aside h2 { margin: 0; font-size: 1.2rem; letter-spacing: -.01em; }
.auth-aside .lead { margin: 0; font-size: .875rem; color: var(--ink-soft); }

/*  THE PANEL ART.

    margin-top:auto on the list used to push the points to the foot of the
    panel; the picture takes that job now, so the list sits directly under the
    prose and the art fills whatever is left.

    Rounded and inset rather than bled to the panel's edges: the image is a
    photograph of a counter and the panel is a flat tinted card, and running
    one into the other makes the card look like a cropped screenshot.  */
/*  The mark at the head of the panel, in place of the square-and-name it
    replaces. Smaller than the header's, because here it is a signature on a
    panel rather than the bar's own brand.  */
/*  The round KeyMart Pro badge carries its name and tagline inside the
    circle, so on the sign-in pages - where there is room - it is shown large
    enough for them to be read.  */
.auth-logo { --logo-h: 8rem; align-self: flex-start; }

/*  THE ILLUSTRATION IS NOT A CARD ON THE PANEL. IT IS THE FOOT OF THE PANEL.

    WHY THE EARLIER ATTEMPTS STILL LOOKED STUCK ON. A rounded rectangle with a
    shadow is read as an object lying on a surface - that is exactly what a
    shadow is FOR. Bleeding it off the edge helped, but three things still
    announced it: the radius, the shadow, and above all a hard edge where the
    photograph stopped and the panel started. Any one of those is enough for
    the eye to say "something was placed here".

    SO THE EDGE IS REMOVED RATHER THAN DECORATED. The mask fades the picture
    out along its top and left - the two edges that face into the panel - so
    there is no boundary to notice. The other two run off the panel and are cut
    by its overflow. The result has no visible edge at all: it emerges from the
    tint and leaves through the corner.

    NO SHADOW, NO BORDER, NO RADIUS. Each of those re-states the boundary the
    mask exists to dissolve.

    The tilt stays, but gently. Once the edges are gone the angle is felt in
    the counter and the shelves rather than in a frame, and a steep rotation
    with no frame to explain it just reads as a crooked photograph.  */
.auth-art {
    display: block;
    width: 132%;
    max-width: none;
    height: auto;

    /*  Pushed to the foot of the panel, and out through the bottom-right
        corner. The negative margins are what put the two hard edges outside
        the panel, where overflow:hidden removes them.  */
    margin: auto -22% -12% -10%;

    border: 0;
    border-radius: 0;
    box-shadow: none;

    transform: rotate(-4deg);

    /*  The fade. An ellipse centred low and right, opaque across the counter
        itself and falling away before it reaches the top-left - so the
        picture dissolves into the panel exactly where it would otherwise
        have an edge against the text above it.  */
    -webkit-mask-image: radial-gradient(115% 105% at 78% 82%,
                        #000 42%, rgb(0 0 0 / .55) 66%, transparent 84%);
            mask-image: radial-gradient(115% 105% at 78% 82%,
                        #000 42%, rgb(0 0 0 / .55) 66%, transparent 84%);
}

/*  Below the two-column breakpoint the panel becomes a strip above the form,
    and a 3:2 photograph there is a screenful the person has to scroll past to
    reach the first field.  */
@media (max-width: 52rem) {
    .auth-art { display: none; }
}

.auth-points { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; }
/*  ═══ THE DOT SITS IN A GUTTER, NOT IN A GRID COLUMN ═══

    This was a two-column grid - a .5rem column for the dot, then 1fr for the
    words - and it worked for exactly one shape of bullet: a single run of
    inline text.

    The moment a bullet held anything more, such as "<strong>A grid that does
    not fit is refused</strong> when you save it", those became TWO grid items.
    The first took the 1fr column and the second wrapped onto a new row, into
    the half-rem column meant for the dot - so the rest of the sentence came out
    one word per line, down the left edge. Every list of this kind in the
    product was doing it.

    Absolute positioning has no such failure mode: the content is laid out as
    ordinary flow, whatever it contains, and the dot is painted into the padding
    beside it. A bullet can now hold bold text, several paragraphs or a link
    without the layout caring.  */
.auth-points li {
    position: relative;
    padding-left: 1.1rem;
    font-size: .8125rem; color: var(--ink-soft); line-height: 1.45;
}
.auth-points li::before {
    content: "";
    position: absolute; left: 0;
    /*  Aligned to the first LINE's centre rather than the top of the box, so
        the dot stays level with the text whatever the font size.  */
    top: .55em;
    width: .5rem; height: .5rem; border-radius: 50%;
    background: var(--accent);
}

/* ── a field with an icon inside it ───────────────────────────────────────
   Used by the sign-in and register forms, and nowhere in the working screens:
   a till operator keys the same six fields a hundred times a day and knows
   them by position, so an icon there is decoration. On a form somebody meets
   once it is the fastest thing on the control.

   THE ICON IS POSITIONED AND THE INPUT IS PADDED, rather than the two sitting
   in a flex row. A flex row would put the border round the wrapper, and then
   the focus ring, the invalid state and the readonly styling would all have
   to be re-taught to it. This way the input keeps every state it already has
   and only its left padding changes.  */

.in-ico { position: relative; display: block; }

.in-ico > .ico {
    position: absolute;
    left: .75rem;
    top: 50%;
    transform: translateY(-50%);
    width: 1.05rem;
    height: 1.05rem;
    color: var(--ink-soft);
    pointer-events: none;              /* the click belongs to the field */
    transition: color var(--dur) var(--ease);
}

.in-ico > input { padding-left: 2.4rem; }

.in-ico:focus-within > .ico { color: var(--accent); }

/*  THE REVEAL BUTTON IS WRITTEN BY site.js, not by the view - so a browser
    with no script shows a plain password field rather than a dead button.  */
.in-ico > .reveal {
    position: absolute;
    right: .3rem;
    top: 50%;
    transform: translateY(-50%);

    display: inline-flex; align-items: center; justify-content: center;
    width: 2rem; height: 2rem;
    padding: 0;
    border: 0;
    background: none;
    color: var(--ink-soft);
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.in-ico > .reveal:hover { color: var(--ink); background: var(--sunken); }
.in-ico > .reveal .ico { width: 1.05rem; height: 1.05rem; }
.in-ico:has(.reveal) > input { padding-right: 2.6rem; }

/*  An arrow drawn from two borders, so it needs no icon font and inherits the
    button's own colour.  */
.btn .arw {
    width: .4rem; height: .4rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(-45deg);
    margin-left: .15rem;
    transition: transform var(--dur) var(--ease);
}
.btn:hover .arw { transform: rotate(-45deg) translate(2px, 2px); }

.auth-form { padding: clamp(1.25rem, 3vw, 2rem); display: grid; align-content: start; gap: var(--gap); }
.auth-form h1 { margin: 0; font-size: 1.4rem; letter-spacing: -.02em; }
.auth-form > .muted { margin: -.5rem 0 0; font-size: .875rem; }
.auth-form .form-foot { margin-top: .25rem; }
.auth-form .btn-primary { width: 100%; justify-content: center; }

/*  The way out, under the form rather than beside it — somebody who has just
    been refused is looking at the button, not at the top of the page.  */
.auth-switch {
    margin: 0; padding-top: var(--gap-sm);
    border-top: 1px solid var(--line);
    font-size: .8125rem; color: var(--ink-soft); text-align: center;
}

/*  ══════════════════════════════════════ the doors fit on one screen ══ */

/*  THE REGISTER FORM RAN 225px PAST THE FOOT OF A 1366x768 LAPTOP, which is
    still the commonest screen this gets opened on. Scrolling to reach the
    button is worse here than anywhere else in the product: the button is the
    entire point of the page, and it was below the fold on the one form a
    person fills in once.

    NOTHING IS TAKEN OUT TO GET THERE. Every field, label, placeholder, hint
    and validation message is still on the page. The height comes out of the
    air between them - the padding round the panel, the gaps between rows,
    and a footer that was reserving 169px on a screen with no links in it.

    ALL OF IT IS SCOPED TO .auth-form. The owner's and the mart's screens are
    built from the same .field and .fields components and must not tighten
    along with these three.  */
/*  THE SHELL HOLDS THE SCREEN OPEN, so the panel sits in the middle of what
    is left and the footer sits on the floor.

    The page was ending wherever the panel happened to end: a short sign-in
    form left the footer stranded halfway up with white below it, and the
    panel itself was pinned to the top rather than centred. min-height on the
    .auth block could not fix that - it can centre the panel inside its own
    box, but it cannot tell the body to reach the bottom of the window.

    A column that fills the viewport does both at once. main takes the slack,
    .auth centres inside main, and the footer is simply last.

    body:has(.auth) rather than a class on <body>: three views own that
    element and the layout is shared with the landing page, which must keep
    growing past the fold. svh rather than vh because on a phone vh is the
    address bars idea of the height, not the visible one.  */
body:has(.auth) { min-height: 100svh; display: flex; flex-direction: column; }
body:has(.auth) > main { flex: 1 1 auto; display: flex; }
body:has(.auth) > main > .wrap { flex: 1 1 auto; display: flex; flex-direction: column; }

.auth { flex: 1 1 auto; min-height: 0; padding-block: var(--gap); }

/*  THE PICTURE SIDE HAS TO COME DOWN WITH THE FORM. Once the form fitted, the
    panel was still 77px too tall - the aside was now the taller half, and a
    column of air beside a form that already fits is the easiest 100px in the
    product to give back.

    The image only narrows; it still runs off both edges of the column and
    still fades out on the same mask, so the composition is the one that was
    signed off, just smaller.  */
.auth-aside { padding: clamp(1rem, 2.2vw, 1.25rem); gap: .625rem; }
.auth-aside .auth-points { gap: .4rem; }
/*  IT MUST NOT READ AS A PICTURE PLACED ON THE PANEL, and that is the one
    thing it has to get right. The version that showed the photograph whole,
    sitting inside its column with air on every side, did read that way - a
    rectangle put there rather than part of the ground.

    SO IT RUNS OFF THE EDGES AGAIN. The negative margins carry two of its sides
    out past the panel where overflow:hidden takes them, the mask dissolves the
    other two into the tint, and nowhere does it end at a straight line inside
    the panel.

    THE CAP IS WHAT KEEPS THE SIGN-IN PAGES OFF THE SCROLLBAR. Those two forms
    are short, and without it the picture alone decided the panel height and
    pushed a three-field form past the fold. It is looser than it was - the
    passwords went back to their own rows, which gave the register form the
    height to spend here - so less of the counter is lost to the crop.  */
.auth-aside .auth-art {
    width: 120%;
    /*  Tuned against the SHORT forms, which are the ones it can push around.
        The register panel is taller than this anyway, so the cap only ever
        decides how much air is left beside a two-field sign-in.  */
    max-height: min(22rem, 37vh);
    object-fit: cover;
    object-position: 50% 32%;
}


/*  THE FORM IS CENTRED IN ITS COLUMN. The two halves of the panel are never
    the same natural height, and whichever is shorter gets the difference as
    air. Stacked from the top it all landed under the form - 161px of nothing
    below the copyright on the mart sign-in. Split in two it reads as margin;
    piled at the bottom it read as a mistake. Shared by all three doors, so
    they stay the same page in three moods.  */
.auth-form {
    /*  THE RHYTHM IS A VARIABLE, NOT A NUMBER, because the three doors are not
        the same length. Everything below spaces itself from these two.  */
    --row: .625rem;
    --ctl: 2.25rem;

    padding: clamp(1rem, 2.2vw, 1.25rem);
    gap: var(--row);
    align-content: center;
}

/*  FOUR FIELDS OR MORE IS THE REGISTER FORM, and the tight rhythm above was
    bought for it - it is the only one that has to fit five rows, a hint and a
    button on a 768px laptop.

    THE TWO SIGN-IN FORMS WERE PAYING FOR THAT and getting nothing back: two
    or three fields squeezed to the same rhythm, sitting in the middle of a
    panel with 124px of air above and below. Cramped in the middle of empty is
    the worst of both.

    So a short form takes the spacing the rest of the product uses - full-size
    controls, a full row gap, a heading at its proper size and real padding
    top and bottom. It fills the column instead of huddling in it.

    Counted rather than named: it asks the form how many fields it has, so
    neither view has to carry a class that says how it should be spaced, and a
    field added or removed later re-spaces the page on its own.  */
.auth-form:not(:has(.field:nth-of-type(4))) {
    --row: var(--gap);
    --ctl: var(--ctl-h);
    padding-block: clamp(1.5rem, 3.5vw, 3rem);
}
.auth-form:not(:has(.field:nth-of-type(4))) h1 { font-size: 1.4rem; }
.auth-form:not(:has(.field:nth-of-type(4))) > .muted { margin-top: -.15rem; }
.auth-form:not(:has(.field:nth-of-type(4))) .field > label { font-size: .8125rem; }
.auth-form:not(:has(.field:nth-of-type(4))) .field { gap: .3rem; }
.auth-form:not(:has(.field:nth-of-type(4))) .form-foot { padding-top: var(--gap); }
.auth-form:not(:has(.field:nth-of-type(4))) .auth-switch { padding-top: var(--gap-sm); }

.auth-form h1 { font-size: 1.25rem; }
.auth-form > .muted { margin-top: -.35rem; }

.auth-form .field { gap: .15rem; }
.auth-form .field > label { font-size: .75rem; line-height: 1.35; }
.auth-form .field input { min-height: var(--ctl); padding-block: .3rem; }

.auth-form .form-foot { margin-top: 0; padding-top: var(--gap-sm); }
.auth-form .btn-lg { min-height: 2.75rem; }
.auth-form .auth-switch { padding-top: .45rem; }


/*  The rows breathe a little less than the rest of the product does. That is
    the whole of the compaction now that the two passwords are back in their
    own rows - see the note in Register.cshtml for why they went back.  */
.auth-form .fields { gap: var(--row) var(--gap-sm); }

/*  Full height back on touch. Below this the page scrolls regardless, so the
    36px control buys nothing and costs a tap target.  */
@media (max-width: 52rem) {
    .auth-form { --ctl: var(--ctl-h); }
}

/*  A WINDOW UNDER 704px TALL is a laptop carrying a toolbar or two, and five
    stacked fields plus a footer do not fit in it at the ordinary rhythm.
    Nothing is dropped and nothing is cropped - the air between the rows goes
    first, which is the only thing on this page nobody will miss.  */
@media (max-height: 44rem) {
    .auth { padding-block: var(--gap-sm); }
    .auth-form, .auth-form:not(:has(.field:nth-of-type(4))) {
        --row: .5rem;
        padding-block: 1rem;
    }
}


/*  THE WAY BACK OUT.

    The bar above these three screens is gone, and it took the only signposted
    route home with it. The mark in the panel is a link, but a logo is a
    signature rather than a signpost - nobody who has not been told tries
    clicking it. This says it in words.  */
.auth-back {
    justify-self: start;
    display: inline-flex; align-items: center; gap: .35rem;
    font-size: .8125rem; font-weight: 500;
    color: var(--ink-soft); text-decoration: none;
}
.auth-back:hover, .auth-back:focus-visible { color: var(--accent); }
.auth-back .ico {
    width: 1rem; height: 1rem;
    transition: transform var(--dur) var(--ease);
}
.auth-back:hover .ico { transform: translateX(-3px); }

/*  THE TWO LINES THE FOOTER USED TO CARRY, now inside the panel.

    The bar itself is gone from these three screens - see the note in
    _Layout.cshtml. Both lines still say what they said; they just say it
    where the eye already is, and the page ends at the panel instead of at a
    rule with two words under it.

    Quiet on purpose. Neither is anything a person came here to read, and a
    copyright that competes with the sign-in button is a copyright in the
    wrong place.  */
.auth-tag {
    margin: 0;
    font-size: .75rem; font-weight: 500; letter-spacing: .01em;
    color: var(--ink-soft);
}
.auth-copy {
    margin: 0;
    font-size: .6875rem; color: var(--ink-faint); text-align: center;
}

/*  Below this the aside is a header, not a column. Its points go: on a phone
    the form is the whole reason the page was opened, and three bullets above
    it are three bullets between the visitor and the first field.  */
@media (max-width: 52rem) {
    .auth-panel { grid-template-columns: 1fr; }
    .auth-aside {
        border-inline-end: 0;
        border-block-end: 1px solid var(--line);
        flex-direction: row; align-items: center; flex-wrap: wrap;
        gap: .5rem var(--gap-sm);
        padding-block: var(--gap);
    }
    .auth-aside h2 { font-size: 1rem; }
    .auth-aside .lead, .auth-points { display: none; }
}


.tile-head .mark { font-size: 1.2rem; line-height: 1; }
.tile-head .num { font-family: var(--font-num); font-size: .8125rem; }



/* ──────────────────────────────────────── company and banking details ── */


.facts { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .4rem var(--gap); margin: 0; }
.facts dt { font-size: .75rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-soft); }
.facts dd { margin: 0; font-size: .875rem; }

/*  An unfinished detail is shown as visibly unfinished, rather than as a blank
    line that reads like a design choice.  */
.facts dd.unset { color: var(--ink-faint); font-style: italic; }

/* ═══════════════════════════════════════════════════════════ the footer ══ */

.footer {
    border-top: 1px solid var(--line);
    background: var(--raised);
    margin-top: var(--gap-2xl);
    padding-block: var(--gap-xl) var(--gap-lg);
}

/*  The brand column is given twice the room of a link column, so the
    description has somewhere to sit rather than breaking every four words.  */
.footer-cols {
    display: grid;
    grid-template-columns: minmax(0, 1.6fr) repeat(3, minmax(0, 1fr));
    gap: var(--gap-lg);
    padding-bottom: var(--gap-lg);
    margin-bottom: var(--gap);
    border-bottom: 1px solid var(--line);
}

@media (max-width: 62rem) {
    .footer-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 34rem) {
    .footer-cols { grid-template-columns: minmax(0, 1fr); gap: var(--gap); }
}

/*  THE FOOTER MARK IS THE SAME COMPONENT, not a second copy of it. It carries
    .brandmark like the header's, so the crop, the plate and the rounding are
    declared once; only the size differs, and it is set by overriding the token
    the component already reads.  */
.footer-brand { --logo-h: 4rem; }
.footer-brand .brandmark { width: max-content; max-width: 100%; margin-bottom: var(--gap-sm); }

/*  A guard for the image sitting DIRECTLY in the footer rather than inside the
    mark. There is no global img rule in this file - deliberately, because the
    cropped mark has to be wider and taller than the box clipping it - so an
    unwrapped 1600px image would render at 1600px and push the page sideways.
    Matches nothing once the markup is rebuilt; costs one rule to be safe if it
    is not.  */
.footer-brand > img { height: var(--logo-h); width: auto; border-radius: var(--radius); }
.footer-brand p {
    margin: 0;
    font-size: .8125rem;
    line-height: 1.6;
    color: var(--ink-soft);
    max-width: 34ch;
}

.footer-col { display: flex; flex-direction: column; align-items: flex-start; gap: .45rem; }

.footer-col h2 {
    font-size: .6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .09em;
    color: var(--ink-soft);
    margin: 0 0 .3rem;
}

.footer-col a {
    font-size: var(--fs-sm);
    color: var(--ink-soft);
    transition: color var(--dur) var(--ease);
}
.footer-col a:hover { color: var(--accent); text-decoration: none; }

.footer-in { display: flex; gap: var(--gap); font-size: var(--fs-sm); color: var(--ink-soft); flex-wrap: wrap; }

/* ── the header actions ──────────────────────────────────────────────────── */
/*  Wraps rather than overlaps. Three buttons and a 64px mark do not fit across
    a small phone, and a header that grows by one row is a great deal better
    than one where the register button sits on top of the logo.  */
/* ── the theme control in the bar ─────────────────────────────────────────
   The picker used to be a band on the landing page. It is a preference about
   the machine somebody is sitting at, not a thing to scroll to, so it belongs
   in the chrome - and from the bar it is reachable on every public screen
   rather than on one.

   A <details>, so it opens with no script and closes on Esc. Everything
   inside it is the existing _ThemePicker partial, unchanged: a real form that
   posts and comes back to the page it was opened from.  */

.themebox { position: relative; flex: none; }

.themebox > summary {
    display: inline-flex; align-items: center; gap: .4rem;
    min-height: 2.25rem;
    padding: .35rem .7rem;
    font-size: .8125rem; font-weight: 600;
    color: var(--ink-soft);
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-btn);
    cursor: pointer;
    list-style: none;
    white-space: nowrap;
}
.themebox > summary::-webkit-details-marker { display: none; }
.themebox > summary::marker { content: ""; }
.themebox > summary:hover { background: var(--sunken); color: var(--ink); }
.themebox[open] > summary { border-color: var(--line); color: var(--ink); background: var(--sunken); }

.themebox > summary .ico { width: 1.05rem; height: 1.05rem; }

.themebox .chev {
    width: .35rem; height: .35rem;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg) translate(-1px, -1px);
    transition: transform var(--dur) var(--ease);
}
.themebox[open] .chev { transform: rotate(-135deg); }

/*  Anchored to the RIGHT edge of the button, because the control sits on the
    right of the bar - a panel opening leftwards from there would run off the
    screen.  */
.themebox-pop {
    position: absolute;
    top: calc(100% + .4rem);
    right: 0;
    z-index: 40;

    width: min(24rem, calc(100vw - 2rem));
    max-height: min(30rem, calc(100vh - var(--header-h) - 2rem));
    overflow-y: auto;

    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    padding: .85rem;
}

/*  Two across inside the panel. The band this came from had room for six in a
    row; a dropdown does not, and one column would make it a list to scroll
    rather than a set to compare - which is the whole point of showing the
    swatches at all.  */
.themebox-pop .theme-pick { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .5rem; }
.themebox-pop .theme-note { margin: .7rem 0 0; font-size: .75rem; }
.themebox-pop .mode-pick { margin-top: .2rem; }

@media (max-width: 30rem) {
    .themebox > summary .lbl-text { display: none; }
    .themebox-pop .theme-pick { grid-template-columns: minmax(0, 1fr); }
}

.topbar-actions { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }

/*  THE BAR'S OWN BUTTONS ARE SHORTER THAN THE PAGE'S.

    A header is chrome: its controls are always available and are pressed far
    less often than the ones inside a screen, so they do not need the 40px
    touch height that a form's buttons do. At full height they also set the
    bar's floor - three 40px buttons in a 57px bar leave 8px of air above and
    below and the whole thing reads as packed.

    36px, and the bar breathes without the mark having to shrink again.  */
.topbar-actions .btn {
    min-height: 2.25rem;
    padding-inline: .85rem;
    font-size: .8125rem;
}

@media (max-width: 48rem) {
    .topbar-in { flex-wrap: wrap; row-gap: .4rem; }
    .topbar-actions .btn { padding-inline: .7rem; }
}

/* ─────────────────────────────────────────────────────────────── chips ── */
/*  Ten one-line highlights. As tiles they were ten boxes for ten short
    phrases — most of each box was air. As chips they are one band the eye
    crosses in a second, which is all a promotional strip has ever been for.  */

.chips { display: flex; flex-wrap: wrap; gap: .5rem; }

.chip {
    display: inline-flex; align-items: center; gap: .45rem;
    background: var(--ground);
    border: 1px solid var(--line);
    border-radius: 2rem;
    padding: .4rem .85rem;
    font-size: .8125rem;
    color: var(--ink-soft);
}
.chip .mark { font-size: .95rem; line-height: 1; }

/* ─────────────────────────────────────────── denser feature grids ── */
/*  Sixteen features and eight schemes still want cards — each has a sentence
    to carry. They only wanted narrower ones, so four fit across a desktop
    instead of three.  */

.tiles.compact { grid-template-columns: repeat(auto-fill, minmax(min(13.5rem, 100%), 1fr)); gap: .75rem; }
.tiles.compact .tile { padding: .95rem 1.05rem 1rem; }

/*  ═══════════════════════════════════════════ A TONE PER FIGURE ══

    Four cards side by side, all in the same grey, ask the reader to work out
    from the words which one is money coming in and which is money going out.
    A tinted ground and a coloured disc answer that before the label is read.

    EVERY COLOUR IS A TOKEN THIS PRODUCT ALREADY HAS - ok, warn, bad, accent
    and their soft pairs - so all six palettes and both modes get their own
    version and nothing here is hard-coded. The soft is mixed back into the
    card surface rather than used neat: at full strength four pastel blocks
    in a row shout, and this is a billing screen.

    THE FIGURES STAY INK. Only the one whose sign is the point takes a
    colour - see .tile-num.up/.down. A row of coloured numbers says nothing
    by saying it everywhere, which is the same argument the figures cards on
    the mart dashboard are built on.  */

.tile.tone-ok     { background: color-mix(in srgb, var(--ok-soft) 60%, var(--raised));
                    border-color: color-mix(in srgb, var(--ok) 20%, var(--line)); }
.tile.tone-warn   { background: color-mix(in srgb, var(--warn-soft) 60%, var(--raised));
                    border-color: color-mix(in srgb, var(--warn) 20%, var(--line)); }
.tile.tone-bad    { background: color-mix(in srgb, var(--bad-soft) 60%, var(--raised));
                    border-color: color-mix(in srgb, var(--bad) 20%, var(--line)); }
.tile.tone-accent { background: color-mix(in srgb, var(--accent-soft) 60%, var(--raised));
                    border-color: color-mix(in srgb, var(--accent) 20%, var(--line)); }

/*  The disc, and the reason it is a disc rather than the rounded square the
    panel headings use: these four sit in a row of their own and the round
    mark is what separates a figure card from a section heading at a glance. */
.tiles.compact .tile-head .ico { border-radius: 50%; }

.tile.tone-ok     .tile-head .ico { background: var(--ok-soft);     color: var(--ok); }
.tile.tone-warn   .tile-head .ico { background: var(--warn-soft);   color: var(--warn); }
.tile.tone-bad    .tile-head .ico { background: var(--bad-soft);    color: var(--bad); }
.tile.tone-accent .tile-head .ico { background: var(--accent-soft); color: var(--accent); }

/*  The hairline under the figure follows the tone, so it belongs to the card
    rather than looking like a rule that landed on it.  */
.tile.tone-ok     .tile-num + p { border-top-color: color-mix(in srgb, var(--ok) 22%, transparent); }
.tile.tone-warn   .tile-num + p { border-top-color: color-mix(in srgb, var(--warn) 22%, transparent); }
.tile.tone-bad    .tile-num + p { border-top-color: color-mix(in srgb, var(--bad) 22%, transparent); }
.tile.tone-accent .tile-num + p { border-top-color: color-mix(in srgb, var(--accent) 22%, transparent); }
.tiles.compact .tile p { margin-bottom: 0; }


/* ═════════════════════════════════════════════════ the permission grid ══ */
/*
   The one screen this whole product turns on: ten modules down, four rights
   across.

   THE FIRST COLUMN STAYS PUT while the four scroll. On a narrow screen the
   rights are what slide out of view, and a ticked box with no module name
   beside it is a box nobody can safely change.
*/

.rights-grid { width: 100%; border-collapse: collapse; }

.rights-grid th, .rights-grid td { padding: .5rem .6rem; border-bottom: 1px solid var(--line); }

.rights-grid thead th {
    font-size: .6875rem; text-transform: uppercase; letter-spacing: .06em;
    color: var(--ink); font-weight: 700; text-align: center;
    background: var(--sunken);
    border-bottom: 1px solid var(--line-strong);
}

.rights-grid th.mod, .rights-grid td.mod {
    text-align: left;
    position: sticky; left: 0;
    background: var(--raised);
    min-width: 15rem;
}
.rights-grid thead th.mod { background: var(--sunken); }

.rights-grid tbody tr:hover td { background: var(--sunken); }
.rights-grid tbody tr:hover td.mod { background: var(--sunken); }

.rights-grid td.tick { text-align: center; width: 5.5rem; }

.rights-grid input[type=checkbox] {
    width: 1.15rem; height: 1.15rem;
    accent-color: var(--accent);
    cursor: pointer;
}

.mod-name { display: flex; align-items: center; gap: .5rem; font-weight: 600; }
.mod-name .ico { color: var(--accent); }
.mod-why { font-size: .75rem; color: var(--ink-faint); margin: .15rem 0 0 1.65rem; }

/*  The column header doubles as a "tick the lot" control. Forty boxes keyed by
    hand is how a grid gets half filled.  */
.col-all {
    display: block;
    margin-top: .2rem;
    font-size: .6875rem;
    font-weight: 600;
    color: var(--accent);
    background: none; border: 0; cursor: pointer;
    text-transform: none; letter-spacing: 0;
}
.col-all:hover { text-decoration: underline; }

/* ══════════════════════════════════════════════════════ the full window ══ */
/*
   A SCREEN THAT IS KEYED RATHER THAN READ ASKS FOR THE WHOLE WINDOW. Item
   entry, purchase entry and the till are all forty fields and a grid, and the
   menu beside them is forty pixels of nothing useful while somebody is typing.

   THE CHROME IS HIDDEN, NOT LEFT OUT. Coming back is a class change — instant,
   and a half-keyed item survives it. A mode that had to reload to leave is a
   mode nobody dares press.
*/

body.fullscreen .topbar,
body.fullscreen .menu,
body.fullscreen .footer,
body.fullscreen .page-nav { display: none; }

/*  ── AND THE PANEL STOPS BEING A PANEL ──

    "Full window" was hiding the rail and the bar and then leaving the frame
    round the outside: measured at 1280x900 the shell still came out 1264x884,
    inset eight pixels, bordered and with its corners rounded. It was the full
    window with a picture frame on it, which is not what the button says.

    In this mode the gutter, the edge and the corners all go, and the shell is
    the window. Everything comes straight back on Escape - it is a class.  */
body.fullscreen:has(.shell) { padding: 0; }

body.fullscreen .shell,
body.fullscreen .shell.is-collapsed {
    grid-template-columns: minmax(0, 1fr);
    padding-block: 0;

    /*  THE GUTTER GOES TO ZERO RATHER THAN THE HEIGHT BEING RESTATED. The
        shell stands at calc(100vh - gutter*2); setting the gutter to nothing
        makes that 100vh on a desktop and leaves the phone, where the height is
        deliberately auto, completely alone. Written as a height here it came
        out 884px in a 900px window - sixteen pixels of nothing at the foot.  */
    --page-gutter: 0px;

    border: 0;
    border-radius: 0;
}

body.fullscreen .wrap { width: 100%; padding-inline: var(--gap); }

/*  The bar that replaces the chrome: what is being keyed, and the way out.  */
.full-bar {
    display: none;
    align-items: center;
    gap: var(--gap);
    padding: .5rem 0;
    margin-bottom: var(--gap);
    border-bottom: 1px solid var(--line);
    position: sticky;
    top: 0;
    background: var(--ground);
    z-index: 15;
    flex-wrap: wrap;
}

body.fullscreen .full-bar { display: flex; }

/*  ── the two full-window buttons, one at a time ──

    A screen that keeps both in one bar - see Master/Item.cshtml, which folded
    its .full-bar into the screen strip - would otherwise offer "Full window"
    to somebody already in it and "Show the menu" to somebody looking at the
    menu. The key hints go the same way: they are about a mode, so they
    belong to it.  */
body.fullscreen [data-full-enter] { display: none; }

body:not(.fullscreen) [data-full-exit],
body:not(.fullscreen) .page-bar .esc { display: none; }

.full-bar b { font-size: 1rem; }
.full-bar .esc { font-size: .75rem; color: var(--ink-faint); }

/*  ═══ THE TILL'S OWN BAR ═══  (the owner's call: attractive)

    The counter is the one screen that keeps a .full-bar of its own, so it
    gets a proper one: a dark band in the theme's grey (the same mix the
    table headings wear) with the mart's colour breathed in, white words,
    the shop and the next bill number as glass pills, the key hints in the
    same glass, and the buttons as white-edged pills that fill on hover.
    One row that does not wrap; what it carries is short.  */
.full-bar {
    gap: .55rem;
    padding: .45rem .8rem;
    margin: 0 0 var(--gap-sm);
    border: 0;
    border-radius: var(--radius);
    background: color-mix(in srgb,
                    color-mix(in srgb, var(--rail) 78%, var(--raised)) 88%,
                    var(--accent));
    color: #FFFFFF;
    box-shadow: var(--shadow-sm);
    flex-wrap: wrap;
    row-gap: .3rem;
}
/*  The key hints are the first thing to go when the bar is short of room -
    the keys still work, and the buttons say what they do.  */
@media (max-width: 96rem) { .full-bar .esc { display: none; } }
.full-bar > * { flex: none; }
.full-bar > .spacer { flex: 1 1 auto; }
.full-bar b { display: inline-flex; align-items: center; gap: .4rem; font-size: 1rem; color: #FFFFFF; letter-spacing: -.01em; }
.full-bar b .ico { width: 1.15rem; height: 1.15rem; color: #FFFFFF; }
.full-bar .faint { color: rgb(255 255 255 / .72); }
.full-bar .pill { background: rgb(255 255 255 / .16); color: #FFFFFF; border: 1px solid rgb(255 255 255 / .18); }
.full-bar .pill.sys { background: rgb(255 255 255 / .22); }
.full-bar .pill.ok { background: color-mix(in srgb, var(--ok) 55%, transparent); border-color: transparent; }
.full-bar .esc { color: rgb(255 255 255 / .78); }
.full-bar kbd { background: rgb(255 255 255 / .14); border-color: rgb(255 255 255 / .28); color: #FFFFFF; }
.full-bar .btn {
    min-height: 1.9rem; padding: .25rem .8rem; font-size: .8125rem;
    border-radius: 999px;
    background: rgb(255 255 255 / .1); border: 1px solid rgb(255 255 255 / .35); color: #FFFFFF;
    box-shadow: none;
}
.full-bar .btn .ico { color: inherit; }
.full-bar .btn:hover { background: #FFFFFF; border-color: #FFFFFF; color: var(--accent); transform: translateY(-1px); box-shadow: 0 2px 8px rgb(0 0 0 / .2); }
.full-bar .btn:focus-visible { outline: 2px solid #FFFFFF; outline-offset: 2px; }

kbd {
    font-family: var(--font-num);
    font-size: .72rem;
    border: 1px solid var(--line-strong);
    border-bottom-width: 2px;
    border-radius: 4px;
    padding: .05rem .3rem;
    background: var(--sunken);
}

/* ══════════════════════════════════════════════════ the entry work area ══ */
/*
   Two columns on a wide screen: what the item IS on the left, what it costs
   and how it is stocked on the right. On a narrow one they stack, because a
   two-column form at 700px is two half-forms.
*/

.entry { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: var(--gap); align-items: start; }

@media (max-width: 62rem) { .entry { grid-template-columns: minmax(0, 1fr); } }

.entry .card { margin: 0; }
.entry .card + .card { margin-top: var(--gap); }

/*  ONE COLUMN, for a screen whose left-hand card wants the whole width.

    The item form is a twelve-column field grid; in half the window it folds
    to about four fields a row and the operator scrolls to reach ones that
    would otherwise be on screen. What sat in the right column stacks under
    it instead - the two groups keep their own order, so nothing is reordered
    by this, only unstacked sideways.  */
.entry.one { grid-template-columns: minmax(0, 1fr); gap: .6rem; }
/*  No margin between the two halves: the grid's own gap already separates
    them, and a margin on top of it was a 3rem band of nothing on screen.
    The gap itself is tighter than the two-column entry's - four cards in one
    column are read top to bottom, and 1rem between each pushed the prices
    further from the item they belong to than the owner wanted.  */
.entry.one .card + .card { margin-top: .6rem; }
.entry.one .card-head { margin-bottom: .65rem; padding-bottom: .45rem; }

/*  A tighter field for a dense entry screen — the ordinary .fields grid is
    sized for a settings page that is read once.  */
.fields.tight { grid-template-columns: repeat(auto-fit, minmax(min(10rem, 100%), 1fr)); gap: .6rem var(--gap-sm); }
.fields.tight .field > label { font-size: .75rem; }
.fields.tight input, .fields.tight select { padding: .35rem .45rem; }

.check {
    display: flex;
    align-items: center;
    gap: .45rem;
    font-size: .8125rem;
    padding: .35rem 0;
}
.check input[type=checkbox] { width: 1.05rem; height: 1.05rem; accent-color: var(--accent); cursor: pointer; }

/* ═══════════════════════════════════════════════════ one setting, one row ══ */
/*
   THE PREFERENCES SCREEN HAD NO STYLING AT ALL. .spec-row was written on the
   form and never given a rule, and the <dt> and <dd> inside it sat outside
   any <dl> - so every setting rendered as a browser default: the label on one
   line, the control on the next, indented by the 40px an orphan <dd> gets.
   Twenty settings of that is a page nobody wants to read, let alone change.

   It is a two-column grid now. WHAT THE SETTING IS on the left; WHAT IT IS
   SET TO on the right, with the sentence that explains it and the record of
   who last touched it underneath the control they belong to. The eye runs
   down one column of names to find a setting, and across one row to change
   it - which is the whole job of this screen.

   Every row is its own form with its own Save, which is deliberate and is
   explained in the view. That makes the row the unit somebody works in, so
   the row is what lights up under the pointer.
*/
.spec-row {
    display: grid;
    grid-template-columns: minmax(12rem, 19rem) minmax(0, 1fr);
    gap: .3rem var(--gap);
    align-items: start;

    /*  Pulled out to the card's own padding so the hover reads as a band of
        the card rather than a floating box inside it.  */
    margin-inline: calc(var(--pad-card) * -1);
    padding: .7rem var(--pad-card);

    border-top: 1px solid var(--line);
    transition: background var(--dur) var(--ease);
}

/*  The first row sits straight under the group name, which already has a rule
    under it - two lines a pixel apart is a mistake, not a border.  */
.card > .spec-row:first-of-type { border-top: 0; }

.spec-row:hover { background: var(--sunken); }

/*  A SETTING THIS MART HAS ACTUALLY CHOSEN is marked, quietly, on the edge of
   the row. Most of a settings screen is untouched defaults; the few rows that
   are not are the ones somebody came to look at, and hunting for the rows
   MISSING a "standard" pill is reading the screen backwards.  */
.spec-row.is-set { box-shadow: inset 3px 0 0 var(--accent); }

/*  THE LABEL IS PROSE, SO IT IS SET AS PROSE. As a flex row the "standard"
    pill was a second item, which meant any label long enough to fill the
    column pushed the pill onto a line of its own where it read as a heading
    for the row rather than a note on the label. Inline, it simply follows
    the last word the way a footnote mark does.  */
.spec-key {
    padding-top: .45rem;
    font-size: .875rem;
    font-weight: 600;
    color: var(--ink);
    line-height: 1.4;
}

.spec-key .pill {
    margin-left: .4rem;
    vertical-align: 1px;
    padding: .05rem .35rem;
    font-size: .625rem;
    font-weight: 700;
    letter-spacing: .01em;

    /*  --ink-faint measured 2.82:1 on this ground. It is a word, at 10px, and
        a word owes 4.5 whatever it is sitting in.  */
    color: var(--ink-soft);
    background: var(--sunken);
    border: 1px solid var(--line);
}

/*  On the row the mart HAS chosen, the ground the pill sits on is the ground
    the row is hovering with - so it takes a border to stay a pill.  */
.spec-row:hover .spec-key .pill { background: var(--raised); }

.spec-val { min-width: 0; }

.spec-controls {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--gap-sm);
}

/*  THE CONTROL WIDTHS, WHICH WERE CEILINGS AND NEEDED TO BE WIDTHS.

    They were inline on the elements as max-width, and a max-width alone does
    nothing to a box that is already narrower than it: measured in the page,
    the number box was 37px, the dropdown 64px and the text box 170px. Three
    different widths, none of them chosen, and a text line meant to hold a
    sentence printed on every bill showing about twenty characters of it.

    So each kind is given the width its content actually needs, and the free
    text one grows into whatever the row has left.  */
.spec-choice { width: 14rem; max-width: 100%; }
.spec-num    { width: 7rem;  max-width: 100%; }
.spec-text   { flex: 1 1 20rem; max-width: 30rem; }

.spec-range { font-size: .75rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/*  A setting nobody understands is a setting nobody dares change, so the
    reasoning is set to be read - a comfortable measure, and enough contrast
    to be prose rather than a footnote.  */
.spec-note {
    margin-top: .4rem;
    max-width: 70ch;
    font-size: .8125rem;
    line-height: 1.5;
    color: var(--ink-soft);
}

/*  WHO LAST TOUCHED IT AND WHAT THE STANDARD WAS. This is the line somebody
    reads when they are deciding whether to put a setting back, so it is set
    quietly but it is set to be read - --ink-faint measured 3.40:1 here.  */
.spec-meta {
    margin-top: .3rem;
    font-size: .6875rem;
    color: var(--ink-soft);
}

.spec-meta strong { color: var(--ink-soft); font-weight: 600; }

/*  ── THE ROW YOU ARE WORKING IN ──

    A settings screen is a list of things you look at and one thing you change,
    and until you have changed it the page has no idea which. So the row says
    it: it lifts under the pointer, and when a control inside it actually has
    the keyboard it takes the accent edge as well. On a screen of twelve
    identical rows that is the difference between editing a setting and hoping
    you are editing the right one.  */
.spec-row:focus-within { background: var(--sunken); box-shadow: inset 3px 0 0 var(--accent); }

/*  ══════════════════════ CONTROLS THAT ARE NOT INSIDE A .field ══

    THIS PRODUCT HAS NO BASE RULE FOR AN <input>. Every control is styled
    through the thing that contains it - .field, .fields.tight, .mrp-grid,
    .find - and a control outside all of them gets nothing at all. Measured at
    night on the bill format screen: white ground, black text, square corners,
    21px tall, sitting on a dark card next to the app's own 34px rounded ones.
    In daylight it reads as an unfinished screen; at night it is a row of
    white slabs.

    Two screens are in that position - the preferences rows and the bill
    format rows - and both are lists rather than forms, which is why neither
    was built out of .field in the first place. They borrow the same look
    here, one size down, because a list wants a denser control than a form.

    The checkbox is left alone: it is styled where it lives, on .check and on
    the format table's own Print column, and an accent-color is all it wants.  */
.spec-controls input:not([type="checkbox"]),
.spec-controls select,
[data-format-rows] td input:not([type="checkbox"]),
[data-format-rows] td select {
    font: inherit;
    color: var(--ink);
    background: var(--raised);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    padding: .35rem .5rem;
    min-height: var(--ctl-h-sm);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

/*  In the format table the cell decides the width - the column headings
    already say how wide each one is meant to be.  */
[data-format-rows] td input:not([type="checkbox"]),
[data-format-rows] td select { width: 100%; }

.spec-controls input:not([type="checkbox"]):hover,
.spec-controls select:hover,
[data-format-rows] td input:not([type="checkbox"]):hover,
[data-format-rows] td select:hover { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }

/*  A ring rather than a thicker border, so the control does not change size on
    focus and shove the Save button beside it sideways by a pixel. Same device
    as .field input:focus - these controls are simply not inside a .field.  */
.spec-controls input:focus, .spec-controls select:focus,
[data-format-rows] td input:focus, [data-format-rows] td select:focus {
    border-color: var(--accent);
    outline: none;
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/*  The whole label is the switch, not just the 17px box in it.  */
.spec-row .check { cursor: pointer; border-radius: var(--radius-sm); }

/*  ── THE SAVE ──

    It was the plain grey button, which is the button this product uses for
    "go back" and "cancel" - the two things Save is not. It carries the accent
    now: tinted at rest so it is plainly the live thing in the row, and filling
    solid the moment the pointer is on it.

    IT IS TINTED, NOT SOLID, AT REST because there are twelve of these down the
    page. Twelve solid accent buttons is a screen shouting one word twelve
    times; twelve tinted ones is a column of things you may press, and the one
    under your hand is the one that lights up.  */
.spec-row .btn {
    background: var(--accent-soft);
    border-color: color-mix(in srgb, var(--accent) 40%, var(--line-strong));
    color: color-mix(in srgb, var(--accent) 62%, var(--ink));
}

.spec-row .btn:hover {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
    box-shadow: var(--shadow-sm);
}

.spec-row .btn:focus-visible {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
}

/*  ON A NARROW SCREEN THE TWO COLUMNS BECOME TWO LINES, which is the one
    place stacking is right: a 17rem label column on a 375px screen leaves the
    control 4rem to live in.  */
@media (max-width: 46rem) {
    .spec-row { grid-template-columns: minmax(0, 1fr); gap: .35rem; }
    .spec-key { padding-top: 0; }
}

/* ══════════════════════════════════════════════════ the compact entry form ══ */
/*
   A DENSER GRID THAN .fields, for screens that are keyed rather than read.

   The ordinary .fields grid is sized for a settings page somebody visits twice
   a year. An entry screen is forty fields, and the operator wants all of them
   on one screen — scrolling to find a field you know is there is the specific
   thing that makes data entry feel slow.

   Four columns on a wide monitor, and the label sits ON the line above at a
   smaller size rather than taking a row of its own.
*/

.fields.compact {
    grid-template-columns: repeat(auto-fit, minmax(min(8.5rem, 100%), 1fr));
    gap: .45rem var(--gap-sm);
}

/* ─────────────────────────────────────────────── twelve columns, aligned ── */
/*
   AUTO-FIT WAS THE WRONG TOOL FOR AN ENTRY FORM.

   repeat(auto-fit, minmax(...)) sizes every field the same and then wraps
   wherever it runs out of room, so a code box ends up as wide as an item name
   and the rows below never line up with the rows above. On a form of twenty
   controls that reads as though the layout has come apart.

   An explicit twelve-column grid with a span per field fixes the geometry: a
   code is two columns because a code is short, a name is six because a name is
   long, and every row starts at the same left edge as the one above it.

   Twelve because it divides by 2, 3, 4 and 6 — every split a form actually
   wants.
*/

.fields.g12 {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: .55rem var(--gap-sm);
    align-items: start;
}

.fields.g12 .field { gap: .1rem; min-width: 0; }

.fields.g12 .field > label {
    font-size: .6875rem;
    font-weight: 600;
    letter-spacing: .02em;
    /*  Full ink - see .field > label. Faint read as a watermark, soft still
        as grey; the small uppercase is what marks it as a caption.  */
    color: var(--ink);
    text-transform: uppercase;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.fields.g12 input,
.fields.g12 select,
.fields.g12 textarea { padding: .34rem .45rem; font-size: .875rem; width: 100%; min-width: 0; }

.fields.g12 .hint { font-size: .6875rem; line-height: 1.3; color: var(--ink-faint); }

/*  A CHECKBOX IN THE DENSE GRID IS NOT A FIELD CAPTION.

    Two rules above were reaching it by accident. "width: 100%" is right for
    every text box in the grid and wrong for a checkbox - measured on the item
    screen, the box was 478px wide with a 17px glyph at its left end, which is
    why "MRP includes GST" sat half a screen away from its tick. And the caption
    style - 11px, uppercase, faint - was being applied to what is a sentence
    somebody reads before ticking, not a label over a box.

    Outranks both by one class, so it wins without !important.  */
.fields.g12 .check input[type="checkbox"] { width: 1.05rem; }

.fields.g12 .field > label.check {
    font-size: .8125rem;
    font-weight: 500;
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink);
    white-space: normal;
    overflow: visible;
    padding: .45rem 0 .2rem;
}

/*  WRITTEN AS "auto / span N", NOT "span N", AND THE DIFFERENCE IS NOT
    COSMETIC. `grid-column: span 4` is shorthand for
    `grid-column-start: span 4; grid-column-end: auto` — the span lives in the
    START. So .cbreak setting grid-column-start: 1 silently destroyed it, and
    every field that had to begin a new row collapsed to one column wide.
    Putting the span in the END leaves the start free for .cbreak to set.  */
.c1  { grid-column: auto / span 1; }
.c2  { grid-column: auto / span 2; }
.c3  { grid-column: auto / span 3; }
.c4  { grid-column: auto / span 4; }
.c5  { grid-column: auto / span 5; }
.c6  { grid-column: auto / span 6; }
.c7  { grid-column: auto / span 7; }
.c8  { grid-column: auto / span 8; }
.c9  { grid-column: auto / span 9; }
.c10 { grid-column: auto / span 10; }
.c12 { grid-column: 1 / -1; }

/*  A field that must start a fresh row — used where a group of controls
    belongs together and should not be split by a wrap.  */
.cbreak { grid-column-start: 1; }

/*  Under about 62rem the twelve columns become six, and every span doubles up
    naturally because the numbers all divide. Under 34rem it is one column: a
    two-column form on a phone is two half-forms.  */
@media (max-width: 62rem) {
    .fields.g12 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
    .c7, .c8, .c9, .c10 { grid-column: auto / span 6; }
}

@media (max-width: 34rem) {
    .fields.g12 { grid-template-columns: minmax(0, 1fr); }
    .fields.g12 .field { grid-column: 1 / -1 !important; }
}

.fields.compact .field { gap: .1rem; }

.fields.compact .field > label {
    font-size: .6875rem;
    font-weight: 600;
    letter-spacing: .02em;
    color: var(--ink);
    text-transform: uppercase;
}

.fields.compact input,
.fields.compact select,
.fields.compact textarea { padding: .32rem .45rem; font-size: .875rem; }

.fields.compact .hint { font-size: .6875rem; line-height: 1.3; }

/*  A field with a "+" beside it — the picker and its inline add, on one line.
    The button is the same height as the input so the row does not jog.  */
.with-add { display: flex; gap: .25rem; align-items: stretch; min-width: 0; }
.with-add select, .with-add input { flex: 1 1 auto; min-width: 0; }

/*  ═══ A SELECT YOU TYPE INTO ═══  (see [data-combo] in site.js)

    The box takes the select's place in the row; the list drops under it in
    the card's own colours, on top of whatever follows. The current pick is
    ticked in the mart's colour, the keyboard's row is tinted, and the "add
    ..." row at the foot is the accent so it reads as an action rather than
    another name.  */
.combo { position: relative; flex: 1 1 auto; min-width: 0; display: flex; }
/*  The box carries its own dress, so a select that sat outside a .field -
    on a tool row, in a table cell - still comes back as a proper control
    and not the browser's bare input. Inside a .field the field's denser
    rules still win where they are more specific.  */
.combo > input {
    width: 100%;
    font: inherit;
    color: var(--ink);
    background: var(--raised);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    padding: .4rem .6rem;
    padding-right: 2.1rem !important;
    min-height: var(--ctl-h);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.combo > input:hover { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.combo > input:focus { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px var(--accent-soft); }

/*  The list button at the end of the box - the owner's picture: a small
    "list" glyph you press to see everything without typing. Inside the
    box's own edge, in the mart's colour, filled on hover.  */
.combo-btn {
    position: absolute; right: .25rem; top: 50%; transform: translateY(-50%);
    width: 1.6rem; height: 1.6rem;
    display: inline-flex; align-items: center; justify-content: center;
    border: 0; border-radius: var(--radius-sm);
    background: var(--accent-soft); color: var(--accent);
    cursor: pointer; padding: 0;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.combo-btn .ico { width: .95rem; height: .95rem; }
.combo-btn:hover { background: var(--accent); color: var(--accent-ink); }
.combo-list li.combo-empty { color: var(--ink-soft); font-style: italic; }

/*  Where a select had its own width rule, the box that stands in for it
    takes the same.  */
.head-search > .combo { flex: 0 0 11rem; align-self: stretch; }
.head-search > .combo > input { font-size: .9375rem; }
.combo-list {
    position: absolute; left: 0; right: 0; top: calc(100% + .25rem);
    z-index: 30;
    margin: 0; padding: .25rem; list-style: none;
    max-height: 16rem; overflow-y: auto;
    background: var(--raised);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
}
.combo-list li {
    padding: .4rem .6rem;
    border-radius: var(--radius-sm);
    font-size: .875rem;
    cursor: pointer;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.combo-list li:hover,
.combo-list li.is-active { background: var(--accent-soft); color: var(--accent); }
.combo-list li.is-current { font-weight: 600; }
.combo-list li.is-current::after { content: " ✓"; color: var(--accent); }
.combo-list li.combo-add { color: var(--accent); font-weight: 600; border-top: 1px solid var(--line); border-radius: 0; margin-top: .15rem; padding-top: .5rem; }
.combo-list li.combo-none { color: var(--ink-faint); cursor: default; }
.combo-list li.combo-none:hover { background: none; color: var(--ink-faint); }

/*  THE "+" IS IN THE MART'S COLOUR, NOT GREY. Grey on grey read as disabled -
    nobody found the quick-add until they hovered. Tinted at rest, solid on
    hover, and it lifts a hair so the press has somewhere to go. The colour
    comes from the theme tokens, so it follows whatever the mart chose.  */
.btn-add {
    flex: none;
    width: 1.9rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);
    cursor: pointer;
    font: inherit;
    padding: 0;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
                transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.btn-add:hover {
    background: var(--accent);
    color: var(--accent-ink);
    transform: translateY(-1px);
    box-shadow: 0 2px 6px rgb(0 0 0 / .14);
}
.btn-add:active { transform: none; box-shadow: none; }
.btn-add:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn-add .ico { width: .95rem; height: .95rem; }

/*  A CAPTION WITH A GLYPH. The item form puts a small icon before each
    field's name - scanner, T, printer, folder, tag, box, balance, percent -
    so a field is found by shape before the word is read. Flex rather than
    inline so the glyph and the text share one baseline, and the text keeps
    the ellipsis the grid caption already had.  */
.field > label:has(> .ico) {
    display: flex;
    align-items: center;
    gap: .3rem;
}
.field > label > .ico {
    width: .85rem; height: .85rem;
    flex: none;
    color: var(--accent);
}
.field > label > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/*  ═══ THE ITEM SCREEN, COMPACT ═══  (the owner's call)

    Everything on the item form a step tighter: the cards hold less air and
    sit closer, their heads are a line, the boxes and the price grid's cells
    are the dense height, and the captions a size down. Nothing moves; it
    all just takes less of the window, which on a 150%-scaled screen is the
    difference between the form and a scroll.  */
[data-mrp-form] .card { --pad-card: .6rem; padding: var(--pad-card); }
[data-mrp-form] .entry.one { gap: .4rem; }
[data-mrp-form] .entry.one .card + .card { margin-top: .4rem; }
/*  The screen itself gives the form the window: less padding above and
    below the work, so a laptop at 150% holds the whole form without a
    scroll.  */
body:has([data-mrp-form]) .shell-main { padding-block: .4rem .5rem; }
body:has([data-mrp-form]) .page-bar-in { min-height: 2.25rem; padding-block: .15rem; }
/*  The card heads as captions, not titles: a small glyph in the mart's
    colour, the name in the same small dark uppercase the fields' captions
    use, a hairline under it - one thin line that names the card and gets
    out of the way.  */
form[data-mrp-form] .card .card-head { margin-bottom: .45rem; padding-bottom: .3rem; gap: .35rem; min-height: 0; }
form[data-mrp-form] .card .card-head h2 {
    font-size: .6875rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
    color: var(--ink);
}
form[data-mrp-form] .card .card-head > .ico {
    width: .95rem; height: .95rem; padding: 0;
    background: none; color: var(--accent); border-radius: 0;
}
form[data-mrp-form] .card .card-head .faint { font-size: .75rem; }
form[data-mrp-form] .card .card-head .faint strong { font-weight: 600; }
[data-mrp-form] .fields.g12 { row-gap: .3rem; }
[data-mrp-form] .fields.g12 input:not([type=checkbox]),
[data-mrp-form] .fields.g12 select,
[data-mrp-form] .fields.g12 .combo > input { min-height: 0; padding: .3rem .45rem; font-size: .8125rem; }
[data-mrp-form] .fields.g12 .field > label { font-size: .65rem; }
[data-mrp-form] .btn-add { width: 1.75rem; }
[data-mrp-form] .mrp-grid th { padding: .3rem .4rem; font-size: .6rem; }
[data-mrp-form] .mrp-grid td { padding: .15rem .35rem; }
[data-mrp-form] .mrp-grid input[type=text],
[data-mrp-form] .mrp-grid input[type=number],
[data-mrp-form] .mrp-grid input[type=date] { padding: .22rem .35rem; font-size: .78rem; }
[data-mrp-form] .mrp-foot { margin-top: .3rem; }
[data-mrp-form] .mrp-foot .btn { min-height: 1.85rem; padding: .15rem .6rem; font-size: .8125rem; }
[data-mrp-form] .form-foot { margin-top: .4rem; }
[data-mrp-form] .form-foot .btn { min-height: 2rem; padding-block: .25rem; }

/*  THE ITEM CARD HAS NO HEADING, so it needs no room for one - and its two
    rows sit a touch closer than the default grid.  */
.item-card .fields.g12 { row-gap: .45rem; }
/*  A checkbox cell on a row of captioned boxes sits on the boxes' line,
    not up at the captions'.  */
.fields.g12 .field.check-row { align-self: end; padding-bottom: .3rem; }
.fields.g12 .field.check-row .check { white-space: nowrap; }
.item-card > .err:empty { display: none; }

/*  A FIRMER EDGE ON THE ITEM SCREEN'S CARDS. The hairline they share with
    every other card is right for a list; here each card is a box somebody
    types into, and the owner wanted its edge to read. Not black - the line
    is the mart's own colour folded into the strong line tone, so it belongs
    to the theme and deepens or lightens with it. All four cards take it, so
    the form does not sit in a darker box than the prices under it.  */
.entry.one .card {
    border-color: color-mix(in srgb, var(--accent) 45%, var(--line-strong));
    box-shadow: var(--shadow-sm), 0 0 0 3px color-mix(in srgb, var(--accent) 8%, transparent);
}

/*  A card with less air, for the same reason as the grid above.  */
.card.compact { --pad-card: var(--gap); padding: var(--pad-card); }
.card.compact .card-head { margin-bottom: .5rem; padding-bottom: .35rem; }
.card.compact .card-head h2 { font-size: .95rem; }
/*  Two compact cards in a row sit closer than two ordinary ones - a filter
    and the list it filters are one thing, and a full gap between them read
    as two.  */
.card.compact + .card.compact { margin-top: .6rem; }

/* ═══════════════════════════════════ a billing screen, tightened ══ */
/*
    THE SCREEN'S JOB IS SHOWING LINES OF A BILL, so every millimetre spent on
    anything else is a line the operator cannot see. A grocery bill of twenty
    items is ordinary; scrolling one while a customer waits is not.

    Nothing is made smaller than it can be read or hit — the row heights here
    are the till's, which is worked all day at arm's length. What goes is the
    air between things, not the things.
*/
form.tight > .card { padding: .6rem .7rem; }
form.tight > .card + .card { margin-top: .5rem; }
form.tight .card-head { margin-bottom: .45rem; padding-bottom: .35rem; }
form.tight .card-head h2 { font-size: .9rem; }

form.tight .fields { gap: .45rem var(--gap-sm); }
form.tight .field > label { font-size: .65rem; }
form.tight .field input,
form.tight .field select { padding: .3rem .4rem; font-size: .8125rem; }

/*  ═══ SHOWN, NOT ASKED FOR ═══

    A read-only box that looks like every other box is an invitation to type
    into it. The bill number is assigned by the numbering series inside the
    save's own transaction and nothing on this screen can set it, so it is drawn
    as a fact: sunken, quiet, no border to click at, and out of the tab order in
    the markup.  */
input.ro,
.fields input[readonly]:not(.item-cell) {
    background: var(--sunken);
    border-color: transparent;
    color: var(--ink-faint);
    cursor: default;
}
input.ro:focus,
.fields input[readonly]:not(.item-cell):focus { outline: none; }

/*  The line grid: the till's row height, which is the densest this project
    already trusts at a counter.  */
form.tight .mrp-grid td { padding: .3rem .4rem; }
form.tight .mrp-grid th { padding: .3rem .4rem; font-size: .7rem; }

/*  The hint under the customer box only has something to say when there IS
    something — an empty one still reserved a line on every bill.  */
form.tight .hint:empty { display: none; }

form.tight .entry { gap: .5rem; }
form.tight .form-foot { margin-top: .6rem; }

/*  Save and Cancel sit inside the settlement row now, in a grid cell rather
    than a strip of their own — so they need to sit on the line the inputs sit
    on, not on the label line above it.  */
form.tight .fields .field > .btn { margin-right: .4rem; }

/*  A checkbox in the top bar. Sat on the same line as the pills and buttons,
    which are taller than a bare box.  */
.bar-check { display: inline-flex; align-items: center; gap: .35rem; margin: 0; white-space: nowrap; }
.bar-check input { margin: 0; }
.bar-check span { font-size: .8125rem; }

/*  ═══ THE ARROWS THAT WALK THE REGISTER ═══

    First on the row, before the document's own name, because that is where a
    back button lives on everything else somebody uses.

    A dead end is drawn and dimmed rather than removed: a control that vanishes
    at the ends of a list slides the other one under the cursor, and the next
    click goes somewhere nobody meant.  */
.bar-nav { display: inline-flex; gap: .2rem; margin-right: .3rem; }
.bar-nav .is-off { opacity: .35; pointer-events: none; }

/* ══════════════════════════════════════════════════════ grid row actions ══ */
/*
   ICONS, NOT WORDS. A row of "Edit / Switch off" buttons is wider than most of
   the data beside it, and on a twelve-column grid that width comes out of the
   columns that matter. Every one carries a title and an aria-label.
*/

.row-acts { display: flex; gap: .3rem; align-items: center; justify-content: flex-end; flex-wrap: nowrap; }
.row-acts form { margin: 0; display: flex; }

.btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--raised);
    color: var(--ink-soft);
    cursor: pointer;
    font: inherit;
    flex: none;
}

/*  It is an <a> as often as a <button> now - the underline a link would
    otherwise draw runs under the icon and looks like a fault.  */
.btn-icon { text-decoration: none; transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.btn-icon:hover { background: var(--sunken); color: var(--ink); border-color: var(--line-strong); text-decoration: none; transform: translateY(-1px); }
.btn-icon .ico { width: 1rem; height: 1rem; }

/*  The row's main action - edit, on a list - is tinted at rest so the eye
    lands on it; the rest stay neutral until hovered. Same tokens as the "+"
    beside a picker.  */
.btn-icon.accent { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.btn-icon.accent:hover { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
/*  Not in the bar's segmented group, where the six sit on one sunken plate
    and a tint on each would make six plates.  */
.starter-bar .btn-icon.accent { background: none; color: var(--ink-soft); }
.starter-bar .btn-icon.accent:hover { background: var(--accent-soft); color: var(--accent); border-color: transparent; transform: none; }

/*  THE ONE ACTION THE ROW IS FOR. Every other icon in a row is a thing you
    might do; this is the thing the row exists to let you do - opening the
    mart, in the owner's list - so it is filled rather than outlined. Only
    one per row, or it stops meaning anything.  */
.btn-icon.is-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
}

.btn-icon.is-primary:hover { filter: brightness(1.08); background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/*  EACH ACTION IN ITS OWN COLOUR, AT REST. Edit is the mart's colour, a
    remove is the bad tint, a good is the ok tint, and "second" - the
    theme's warm companion colour - is for the in-between action, like
    where the stock went. Grey-until-hovered made the three look like one
    thing with three glyphs; a person had to read the tooltips to tell
    them apart. On hover each deepens toward its own colour and lifts.  */
.btn-icon.danger { background: var(--bad-soft); color: var(--bad); border-color: transparent; }
.btn-icon.danger:hover { background: color-mix(in srgb, var(--bad) 22%, var(--bad-soft)); color: var(--bad); border-color: var(--bad); }
.btn-icon.good { background: var(--ok-soft); color: var(--ok); border-color: transparent; }
.btn-icon.good:hover { background: color-mix(in srgb, var(--ok) 22%, var(--ok-soft)); color: var(--ok); border-color: var(--ok); }
.btn-icon.second { background: color-mix(in srgb, var(--second) 10%, var(--raised)); color: var(--second); border-color: transparent; }
.btn-icon.second:hover { background: color-mix(in srgb, var(--second) 30%, var(--raised)); color: var(--second); border-color: var(--second); }

/*  ═══ THE ROW'S ACTIONS, AS BUTTONS A PERSON WANTS TO PRESS ═══

    In a list's action cell the icons are round, a step bigger, and each
    wears a ring of its own colour. On hover the ring fills - the button
    goes solid in its colour, the glyph turns to the surface tone, it lifts
    two pixels and throws a coloured shadow - so pressing has somewhere to
    go. --raised for the glyph on the filled button works in both rooms
    because the palettes flip together: near-white on a deep colour by day,
    near-black on a pale one at night.  */
.row-acts .btn-icon {
    --c: var(--ink-soft);
    width: 1.9rem; height: 1.9rem;
    border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--c) 35%, transparent);
    box-shadow: 0 1px 2px rgb(0 0 0 / .08);
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
                border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
}
.row-acts .btn-icon.accent { --c: var(--accent); }
.row-acts .btn-icon.second { --c: var(--second); }
.row-acts .btn-icon.danger { --c: var(--bad); }
.row-acts .btn-icon.good   { --c: var(--ok); }
.row-acts .btn-icon:hover {
    background: var(--c);
    border-color: var(--c);
    color: var(--raised);
    transform: translateY(-2px);
    box-shadow: 0 4px 10px color-mix(in srgb, var(--c) 35%, transparent);
}
.row-acts .btn-icon:active { transform: translateY(0); box-shadow: 0 1px 2px rgb(0 0 0 / .08); }
.row-acts .btn-icon:focus-visible { outline: 2px solid var(--c); outline-offset: 2px; }
.row-acts .btn-icon.is-off { box-shadow: none; }

/*  ═══ A SCREEN INSIDE A POPUP ═══

    body.embed is a page drawn for a frame (?embed=1): the rail, the bar,
    the wash and the window buttons go, the strip and the work stay, and
    the page sits flush in the frame. dialog.pop.huge is the popup that
    holds such a frame - nearly the window, with the frame filling it
    under the head.  */
body.embed .topbar,
body.embed .menu,
body.embed .footer,
body.embed .page-bar .esc,
body.embed [data-full-exit],
body.embed [data-full-enter] { display: none !important; }
body.embed:has(.shell) { padding: 0; }
body.embed:has(.shell)::before { display: none; }
body.embed .shell,
body.embed .shell.is-collapsed { grid-template-columns: minmax(0, 1fr); --page-gutter: 0px; border: 0; border-radius: 0; }
/*  THE SCREEN'S OWN STRIP GOES TOO: the popup's dark head already says
    "New item" and carries the ×, so the strip under it was the same title
    twice and a bar of nothing. The way back it held (← All items) is the ×.  */
body.embed .page-bar { display: none; }
/*  Less air inside the frame: it is a popup, not a page of its own. The
    card heads a size down, the tiles smaller, the gaps under them tight.  */
body.embed .shell-main { padding-block: .5rem .75rem; }
body.embed .card-head { margin-bottom: .4rem; padding-bottom: .3rem; gap: .45rem; }
body.embed .card-head h2 { font-size: .9rem; }
body.embed .card-head > .ico { width: 1.45rem; height: 1.45rem; padding: .3rem; }
body.embed .card-head .faint { font-size: .8125rem; }
dialog.pop.huge .pop-head { padding-block: .45rem; }
dialog.pop.huge .pop-head h3 { font-size: .9375rem; }
dialog.pop.huge .pop-head > .ico { width: 1.5rem; height: 1.5rem; padding: .32rem; }
body.embed .wrap-app { width: calc(100% - 1rem); }
body.embed .card { --pad-card: .75rem; padding: var(--pad-card); }
body.embed .entry.one { gap: .5rem; }
body.embed .entry.one .card + .card { margin-top: .5rem; }
body.embed .form-foot { margin-top: .5rem; }

dialog.pop.huge {
    /*  Compact, the owner's call: wide enough for the price grid's ten
        columns, no taller than the form needs; the frame scrolls inside.  */
    width: min(94vw, 74rem);
    height: min(92vh, 50rem);
    display: flex; flex-direction: column;
}
/*  Inside the frame, tighter still - so the whole item form, Save and all,
    stands in the popup without a scroll on a laptop screen.  */
body.embed .shell-main { padding-block: .4rem .5rem; }
body.embed .wrap-app { width: calc(100% - .75rem); }
body.embed [data-mrp-form] .card { --pad-card: .6rem; padding: var(--pad-card); }
body.embed [data-mrp-form] .entry.one { gap: .4rem; }
body.embed [data-mrp-form] .entry.one .card + .card { margin-top: .4rem; }
body.embed [data-mrp-form] .fields.g12 { row-gap: .3rem; }
body.embed [data-mrp-form] .fields.g12 input:not([type=checkbox]),
body.embed [data-mrp-form] .fields.g12 select,
body.embed [data-mrp-form] .fields.g12 .combo > input { padding-block: .25rem; }
body.embed [data-mrp-form] .mrp-grid td { padding-block: .15rem; }
body.embed [data-mrp-form] .mrp-foot { margin-top: .3rem; }
body.embed [data-mrp-form] .form-foot { margin-top: .4rem; }
body.embed [data-mrp-form] .form-foot .btn { min-height: 2rem; padding-block: .25rem; }
body.embed [data-mrp-form] .fields.g12 .field.check-row { padding-bottom: .3rem; }

/*  The tab row and the add button on one line, the button at the far
    right - the owner's call. The pills keep their width; the button is
    pinned to the end.  */
/*  ═══ ONE ROW, AT EVERY WIDTH ═══

    The masters, the page size and the add button stay on a single line from a
    phone to a desk. It used to wrap: on a narrow screen the tabs took the
    first line and "Per page" with the button dropped to a second, and the tab
    strip was left clipped mid-word with nothing to say more existed. Somebody
    looking for Unit or Customers found "Bran" and a hard edge.

    IT CANNOT BE SOLVED BY FITTING. The five tabs measure 758px and a phone
    offers 351px in this row - there is no font size or padding that closes a
    gap of four hundred pixels. So the row does not try: the strip SHRINKS and
    scrolls inside itself while the row stays one line, and the two controls
    beside it give up their words before the navigation gives up its own.  */
.tabs-row { display: flex; align-items: center; gap: var(--gap-sm); margin-bottom: .5rem; flex-wrap: nowrap; }
.tabs-row .tabs { margin-bottom: 0; }

/*  flex: 0 1 auto AND min-width: 0, and both halves matter.

    0 = do not grow, so on a wide screen the plate still hugs its five pills
    exactly as it always did rather than stretching into a bar.
    1 = but DO shrink, which is what lets it give way on a phone.
    min-width: 0 = a flex item will not shrink below its content without it,
    which is the reason this overflowed the row instead of scrolling inside it.  */
.tabs-row .tabs { flex: 0 1 auto; min-width: 0; }

/*  ═══ PILLS OR A PICKER, NEVER BOTH ═══

    Both are in the markup and this decides which one the screen gets. See the
    note in _MasterTabs for why it is done in markup and CSS rather than built
    in script.

    The picker is the narrow-screen form and is out of the way until then; the
    pills are the wide-screen form and go when the picker arrives.  */
.tabs-pick { display: none; }
.tabs-pick select { min-height: 2.25rem; font-weight: 600; }

.tabs-row .spacer { flex: 1 1 auto; }
.tabs-row .btn { flex: none; min-height: 2.25rem; }
.tabs-row .tool-pick .combo > input { min-height: 2.25rem; }

/*  ═══ WHAT GIVES WAY FIRST, AND IN WHAT ORDER ═══

    Narrow enough and something has to go. The order is deliberate: words that
    a control's own shape already says go first, and the navigation's labels go
    last, because "Groups and GST" is the entire content of that tab - an icon
    alone would put the operator back to guessing.

    The spacer goes too. It exists to push the page size and the button to the
    right end of a wide row; on a narrow one there is no spare room to push
    with, and leaving it in only takes pixels the tabs could have used.

    The narrow-screen half of this lives with the other narrow-screen rules at
    the foot of this file, where the rest of the phone layout is - not here.  */
dialog.pop.huge:not([open]) { display: none; }
dialog.pop.huge .pop-head { flex: none; border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
dialog.pop.huge iframe {
    flex: 1 1 auto; min-height: 0;
    width: 100%; border: 0;
    background: var(--ground);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

/*  ═══ A FILTER FORM AS ONE ROW ═══  (the registers)

    The fields share the row and the buttons keep their own width at its
    end; captions small with a glyph; boxes the dense height. It folds when
    a field would drop under 8rem.  */
.filter-form { --pad-card: .6rem; padding: var(--pad-card); }
.filter-form .fields.g12 { display: flex; flex-wrap: wrap; gap: .4rem .6rem; align-items: flex-end; }
.filter-form .fields.g12 .field { flex: 1 1 8rem; min-width: 0; }
.filter-form .fields.g12 .field.actions { display: flex; flex: 0 0 auto; flex-direction: row; gap: .35rem; align-items: center; }
.filter-form .fields.g12 .field > label { font-size: .65rem; }
.filter-form .fields.g12 input:not([type=checkbox]),
.filter-form .fields.g12 select,
.filter-form .fields.g12 .combo > input { min-height: 1.9rem; padding: .25rem .45rem; font-size: .8125rem; }
.filter-form .fields.g12 .actions .btn { min-height: 1.9rem; padding: .25rem .8rem; font-size: .8125rem; }
/*  Two or three ticks side by side in one cell of the row.  */
.filter-form .fields.g12 .field.checks { display: flex; flex-direction: row; flex-wrap: wrap; gap: .3rem 1rem; align-items: center; flex: 0 1 auto; }

/*  ═══ THE MASTER LISTS (brands, groups, units), COMPACT ═══

    The add form is one row with its button at the end, the heads are
    captions, and the list is the ruled grid. Same voice as the item form.  */
.card.master-form .card-head,
.card.master-list .card-head,
.card.filter-form .card-head,
.card.pref-group .card-head,
.card.cap-head .card-head,
.card.list-card .card-head { margin-bottom: .3rem; padding-bottom: .25rem; gap: .35rem; min-height: 0; }
.card.master-form .card-head h2,
.card.master-list .card-head h2,
.card.filter-form .card-head h2,
.card.pref-group .card-head h2,
.card.cap-head .card-head h2,
.card.list-card .card-head h2 { font-size: .6875rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink); }
.card.master-list .card-head > .ico,
.card.pref-group .card-head > .ico,
.card.cap-head .card-head > .ico,
.card.list-card .card-head > .ico { width: .95rem; height: .95rem; padding: 0; background: none; color: var(--accent); border-radius: 0; }
.master-form { --pad-card: .6rem; padding: var(--pad-card); }
.master-list,
.list-card { --pad-card: .6rem; padding: var(--pad-card); }
.list-card .card-head .btn { min-height: 1.85rem; padding: .15rem .6rem; font-size: .8125rem; }
/*  A flex row, not the auto-fit grid: the fields share the width and the
    button keeps its own at the end, so five fields and Add still make one
    row where the grid folded the button under.  */
.master-form .fields.compact { display: flex; flex-wrap: wrap; gap: .4rem .6rem; align-items: flex-end; }
.master-form .fields.compact .field { flex: 1 1 9rem; min-width: 0; }
.master-form .fields.compact .field > label { font-size: .65rem; }
.master-form .fields.compact input:not([type=checkbox]),
.master-form .fields.compact select,
.master-form .fields.compact .combo > input { min-height: 1.9rem; padding: .25rem .45rem; font-size: .8125rem; }
.master-form .fields.compact .field.field-btn { flex: 0 0 auto; }
.master-form .field-btn > label { display: none; }
.master-form .field-btn .btn { min-height: 1.9rem; padding: .25rem .9rem; font-size: .8125rem; }
.master-form .field > label.check { margin-top: .25rem; }
/*  The find box on a list head, as low as the caption beside it.  */
.master-list .card-head .find,
.list-card .card-head .find { padding: 0 .4rem; min-width: 10rem; min-height: 1.5rem; }
.master-list .card-head .find input,
.list-card .card-head .find input { min-height: 0; padding: .1rem 0; font-size: .75rem; }
.master-list .card-head .find .ico,
.list-card .card-head .find .ico { width: .85rem; height: .85rem; }
.master-list .card-head .faint,
.list-card .card-head .faint { font-size: .8125rem; }
.master-form + .master-list { margin-top: .5rem; }

/*  ═══ AN ON/OFF SWITCH IN A ROW ═══

    One control for active and inactive, drawn as a toggle: green with a
    tick and the knob to the right while on, grey with the knob to the left
    when off. It is a submit button - pressing it posts the row's SetActive
    and asks first through data-confirm - so nothing about what happens
    changed, only what it looks like.  */
.switch {
    --w: 2.6rem; --h: 1.4rem;
    position: relative; flex: none;
    width: var(--w); height: var(--h);
    border: 0; border-radius: 999px; padding: 0;
    background: var(--ink-faint);
    cursor: pointer;
    transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.switch-knob {
    position: absolute; top: .15rem; left: .15rem;
    width: calc(var(--h) - .3rem); height: calc(var(--h) - .3rem);
    border-radius: 999px;
    background: #FFFFFF;
    box-shadow: 0 1px 2px rgb(0 0 0 / .25);
    transition: transform var(--dur) var(--ease);
}
.switch.on { background: var(--ok); }
.switch.on .switch-knob { transform: translateX(calc(var(--w) - var(--h))); }
/*  The tick, drawn on the track where the knob is not.  */
.switch.on::before {
    content: "";
    position: absolute; left: .55rem; top: .38rem;
    width: .35rem; height: .6rem;
    border: solid rgb(255 255 255 / .95); border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}
.switch:hover { box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 25%, transparent); }
.switch:not(.on):hover { box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink-faint) 30%, transparent); }
.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.row-acts .switch { margin-inline: .15rem; }

/*  ═══ A RULED GRID ═══

    Lines between the columns as well as the rows, for a list a person reads
    across eleven columns of figures: without them the eye slips a row going
    from the name to its stock. The vertical rules are the hairline tone so
    they guide without gridding the page; the outer edge closes the box.  */
.grid.lined td { border-right: 1px solid color-mix(in srgb, var(--line) 70%, transparent); }
/*  Clean, not busy: the rules stay but fade, every second row takes the
    faintest wash so the eye keeps its line without the rules shouting,
    and the row under the pointer tints in the mart's colour with a bar
    down its left edge. Figures sit tabular; the row number is quiet.  */
.grid.lined tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--sunken) 45%, var(--raised)); }
.grid.lined tbody tr:hover td { background: var(--accent-soft); }
.grid.lined tbody tr:hover td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
.grid.lined tbody td:first-child.num { color: var(--ink-faint); }
.grid.lined td.num, .grid.lined th.num { font-variant-numeric: tabular-nums; }
.grid.lined tbody tr.off td { background: transparent; }
/*  The rules in the dark band are the band's own ink, faded - a page-grey
    hairline across it looked like a scratch.  */
.grid.lined th { border-right: 1px solid rgb(255 255 255 / .18); }
.grid.lined th:last-child,
.grid.lined td:last-child { border-right: 0; }
.grid.lined tbody tr:last-child td { border-bottom: 1px solid var(--line); }
.grid.lined { border: 1px solid var(--line); font-size: .8rem; }
/*  Tighter cells and a size down from the plain grid: a ruled table already
    separates its cells with lines, so it does not need the air the plain
    one uses for the same job - and the saved width is what keeps eleven
    columns inside the card on a 150%-scaled laptop screen (about 970px for
    the table with the rail open) instead of running off its right edge
    into a sideways scroll.  */
.grid.lined th { padding: .3rem .4rem; font-size: .65rem; }
.grid.lined td { padding: .32rem .4rem; }
/*  The action buttons a size down in a ruled table: a row with three of
    them (open, delete, cancel - the last bill in the register) was the row
    that pushed the table 15px past the card on a 150%-scaled screen.  */
/*  THE LATEST PAGE'S DAY BANDS. A row that is a heading for the rows under
    it: the date once, tinted, with the count beside it. Not sticky - the
    table's own heading already is, and two sticky rows fight.  */
/*  THE ROW IS SHARED OUT, NOT LEFT TO THE WIDEST WORD. The headings carry
    percentages (see Latest.cshtml), so the name never swallows the spare
    width and the amount and its button keep the share the owner wanted.
    The action cell's global "width: 1%" would fight that; it takes the
    heading's share here instead.  */
.latest-grid td:has(> .row-acts) { width: auto; }

/*  A thin head on the Latest card: one line, the tile a size down, the
    heading at body size, and only a hair of air under it - the table is
    the page, the heading only names it.  */
.latest-card .card-head,
[data-panel] .card-head { margin-bottom: .35rem; padding-bottom: .3rem; gap: .45rem; }
.latest-card .card-head h2,
[data-panel] .card-head h2 { font-size: .875rem; }
.latest-card .card-head > .ico,
[data-panel] .card-head > .ico { width: 1.45rem; height: 1.45rem; padding: .3rem; }
.latest-card .card-head .faint,
[data-panel] .card-head .faint { font-size: .8125rem; }
/*  The panels themselves hold less air around their table.  */
.card[data-panel] { --pad-card: .75rem; padding: var(--pad-card); }
/*  The "Manage" button on a panel head, a size down to match the thin head.  */
[data-panel] .card-head .btn { min-height: 1.75rem; padding: .15rem .6rem; font-size: .8125rem; }
.latest-grid th, .latest-grid td { white-space: nowrap; }
.latest-grid td:nth-child(4) { white-space: normal; }
.latest-grid tbody tr.day-band td {
    /*  A step darker than the sunken tone - ink folded in, with a breath of
        the mart's colour - so the band reads as a heading over its rows and
        not as one more row. The words on it stay full ink.  */
    background: color-mix(in srgb,
                    color-mix(in srgb, var(--ink) 14%, var(--sunken)) 92%,
                    var(--accent));
    padding: .45rem .6rem;
    font-size: .8125rem;
    border-bottom: 1px solid var(--line-strong);
    border-right: 0;
}
.latest-grid tr.day-band td > b { color: var(--ink); }
.latest-grid tr.day-band td > .faint { color: var(--ink-soft); }
.latest-grid tr.day-band td > .ico { width: .9rem; height: .9rem; vertical-align: -.15em; margin-right: .3rem; color: var(--accent); }
.latest-grid tbody tr.day-band:hover td,
.grid.lined tbody tr.day-band:hover td:first-child { box-shadow: none; }
.latest-grid tbody tr.day-band:hover td {
    background: color-mix(in srgb,
                    color-mix(in srgb, var(--ink) 14%, var(--sunken)) 92%,
                    var(--accent));
}
.latest-grid td.mid { text-align: center; }
.latest-grid .feed-ico .ico { color: var(--accent); }

.grid.lined .row-acts { gap: .2rem; }
.grid.lined .row-acts .btn-icon { width: 1.55rem; height: 1.55rem; }
.grid.lined .row-acts .btn-icon .ico { width: .9rem; height: .9rem; }

/* ══════════════════════════════════════════════ search inside a screen ══ */
/*
   THE SHORT LISTS FILTER AS YOU TYPE, without a round trip. Brands, groups and
   units are tens of rows, already on the page — sending a request to hide some
   of them would be slower than the typing.

   The item list is different and searches on the server: it is thousands of
   rows and it also matches barcodes, which are not on the page at all.
*/

.find {
    display: flex;
    align-items: center;
    gap: .4rem;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    background: var(--raised);
    padding: .2rem .45rem;
    min-width: 12rem;
}

.find input {
    font: inherit;
    font-size: .875rem;
    border: 0;
    background: transparent;
    color: var(--ink);
    padding: .2rem 0;
    width: 100%;
    min-width: 0;
    outline: none;
}

.find .ico { width: 1rem; height: 1rem; color: var(--ink-faint); flex: none; }

.find-none { padding: var(--gap); text-align: center; color: var(--ink-soft); font-size: .875rem; }

/* ══════════════════════════════════════════════════ the confirm dialog ══ */
/*
   REPLACES window.confirm.

   The native one cannot be styled, says the site's hostname to the customer's
   staff, and on some browsers can be suppressed entirely by a checkbox — at
   which point a destructive action stops asking at all.

   Built rather than pulled from a CDN: this product has two NuGet packages and
   no front-end framework on purpose, and a confirm box that fails to load on a
   shop's connection is a confirm box that does not appear. Same shape as the
   library everybody knows — icon, title, body, two buttons.
*/

dialog.ask {
    border: 0;
    border-radius: 12px;
    background: var(--raised);
    color: var(--ink);
    padding: 0;
    width: min(23rem, calc(100vw - 2rem));
    box-shadow: var(--shadow);
    text-align: center;
}

dialog.ask::backdrop { background: rgba(0, 0, 0, .5); }

dialog.ask .ask-in { padding: var(--gap-lg) var(--gap) var(--gap); display: grid; gap: .5rem; justify-items: center; }

.ask-mark {
    width: 3.25rem;
    height: 3.25rem;
    border-radius: 50%;
    display: grid;
    place-items: center;
    margin-bottom: .25rem;
}
.ask-mark .ico { width: 1.6rem; height: 1.6rem; }

.ask-mark.warn { background: var(--warn-soft); color: var(--warn); }
.ask-mark.ask  { background: var(--accent-soft); color: var(--accent); }

dialog.ask h3 { margin: 0; font-size: 1.05rem; text-wrap: balance; }
dialog.ask p  { margin: 0; color: var(--ink-soft); font-size: .875rem; max-width: 32ch; }

dialog.ask .ask-foot {
    display: flex;
    gap: var(--gap-sm);
    padding: var(--gap) var(--gap) var(--gap-lg);
    justify-content: center;
}
dialog.ask .ask-foot .btn { min-width: 6.5rem; justify-content: center; }

/* ══════════════════════════════════════════════════ the inline add popup ══ */
/*
   ADDING A BRAND WITHOUT LEAVING THE ITEM.

   The alternative — save the half-keyed item, go to Brands, add one, come
   back, find the item — is four screens and a lost train of thought, and it is
   why people key items with the brand left blank.

   A NATIVE <dialog>. It gets focus trapping, Escape, and the backdrop for
   free, and it works if the JavaScript that opens it is the only script that
   ran. Enter-navigation is already scoped to dialog[open] in site.js, so the
   walk stays inside the popup rather than falling through to the form behind.
*/

dialog.pop {
    border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line-strong));
    border-radius: var(--radius-lg);
    background: var(--raised);
    color: var(--ink);
    padding: 0;
    width: min(24rem, calc(100vw - 2rem));
    box-shadow: var(--shadow-lg), 0 0 0 4px color-mix(in srgb, var(--accent) 10%, transparent);
    /*  Opens with a short rise rather than appearing - the difference
        between a popup and a glitch.  */
    animation: pop-in var(--dur-slow, .25s) var(--ease) both;
}
@keyframes pop-in {
    from { opacity: 0; transform: translateY(8px) scale(.985); }
    to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { dialog.pop { animation: none; } }

dialog.pop::backdrop { background: rgba(0, 0, 0, .45); backdrop-filter: blur(2px); }

/*  THE HEAD: an icon tile in the mart's colour, the name, and a × at the
    far end. The × is the Cancel button under a second name - value=cancel,
    formnovalidate - so closing takes the popup's own path.  */
/*  A DARK HEAD - the same theme-tinted grey the list tables' heading band
    wears (rail dark lifted toward the surface with a little accent in it),
    not black - so the popup's name is unmistakably its title. White on it,
    like the band.  */
dialog.pop .pop-head {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .6rem .7rem .6rem var(--gap);
    background: color-mix(in srgb,
                    color-mix(in srgb, var(--rail) 78%, var(--raised)) 88%,
                    var(--accent));
    color: #FFFFFF;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
dialog.pop .pop-head h3 { margin: 0; font-size: 1rem; letter-spacing: -.01em; color: inherit; }
/*  What else a head carries - the item a batch popup is about, the net on
    the tender - reads on the dark too.  */
dialog.pop .pop-head .faint,
dialog.pop .pop-head .muted { color: rgb(255 255 255 / .78); }
dialog.pop .pop-head > b { color: #FFFFFF; }
dialog.pop .pop-head > .ico {
    width: 1.85rem; height: 1.85rem; padding: .42rem; flex: none;
    border-radius: var(--radius-sm);
    background: rgb(255 255 255 / .16); color: #FFFFFF;
}
/*  A small link-button on the dark head ("Full form"): outlined in white.  */
dialog.pop .pop-head .pop-link {
    min-height: 1.75rem; padding: .15rem .6rem; font-size: .75rem;
    background: rgb(255 255 255 / .12); border-color: rgb(255 255 255 / .35); color: #FFFFFF;
}
dialog.pop .pop-head .pop-link:hover { background: rgb(255 255 255 / .22); border-color: #FFFFFF; color: #FFFFFF; transform: none; box-shadow: none; }
dialog.pop .pop-x {
    flex: none;
    width: 1.9rem; height: 1.9rem;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid transparent; border-radius: 999px;
    background: transparent; color: rgb(255 255 255 / .85);
    cursor: pointer; font: inherit; padding: 0;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
dialog.pop .pop-x .ico { width: 1rem; height: 1rem; }
dialog.pop .pop-x:hover { background: rgb(255 255 255 / .18); color: #FFFFFF; transform: rotate(90deg); }
dialog.pop .pop-x:focus-visible { outline: 2px solid #FFFFFF; outline-offset: 2px; }

dialog.pop .pop-body { padding: var(--gap); display: grid; gap: .6rem; }

/*  COMPACT CONTROLS IN A POPUP. The page's comfortable 40px boxes made a
    seven-field popup a wall of white; these are the item form's height,
    which is what a popup keyed in passing wants.  */
dialog.pop .field { gap: .2rem; }
dialog.pop .field input:not([type=checkbox]),
dialog.pop .field select,
dialog.pop .field textarea { min-height: 0; padding: .34rem .55rem; font-size: .875rem; }

/*  CAPTIONS IN A POPUP: small, dark, with a glyph in the mart's colour -
    the same voice as the item form's captions, so a popup keyed from a
    screen looks like part of it.  */
dialog.pop .field > label {
    display: flex; align-items: center; gap: .35rem;
    font-size: .6875rem; font-weight: 700; letter-spacing: .03em;
    text-transform: uppercase; color: var(--ink);
}
dialog.pop .field > label > .ico { width: .85rem; height: .85rem; flex: none; color: var(--accent); }
dialog.pop .field > label.check {
    font-size: .8125rem; font-weight: 500; letter-spacing: 0; text-transform: none;
}
dialog.pop .pop-body > p.hint { margin: 0; font-size: .75rem; color: var(--ink-faint); line-height: 1.4; }

dialog.pop .pop-foot {
    display: flex;
    gap: var(--gap-sm);
    padding: .7rem var(--gap);
    border-top: 1px solid var(--line);
    background: var(--sunken);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

dialog.pop .pop-err { color: var(--bad); font-size: .8125rem; }

/*  ═══ A WIDE POPUP ═══

    The "new item" popup keyed from a purchase: seven fields in one column
    was a popup somebody scrolled. Wide, its body is a four-column grid -
    the name takes two, the rest one each - so it is two rows and every
    field is on screen. The error line spans the row. Under 48rem it falls
    back to the single column.  */
dialog.pop.wide { width: min(46rem, calc(100vw - 2rem)); }
dialog.pop.wide .pop-body {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: .45rem .6rem;
    align-items: start;
    padding: .75rem var(--gap);
}
dialog.pop.wide .pop-foot { padding-block: .55rem; }
dialog.pop.wide .pop-body > .pop-err,
dialog.pop.wide .pop-body > p { grid-column: 1 / -1; }
dialog.pop.wide .pop-body > .field.span2 { grid-column: span 2; }
@media (max-width: 48rem) {
    dialog.pop.wide .pop-body { grid-template-columns: minmax(0, 1fr); }
    dialog.pop.wide .pop-body > .field.span2 { grid-column: auto; }
}

/* ═══════════════════════════════════════════════════════════ the MRP grid ══ */
/*
   THE PART THAT MAKES THIS A GROCERY SCREEN. One item, several printed prices,
   each its own row with its own barcode. Dense on purpose — somebody keying a
   price list wants to see all of it at once.
*/

.mrp-grid { width: 100%; border-collapse: collapse; font-size: .8125rem; min-width: 40rem; }

.mrp-grid th {
    text-align: left;
    font-size: .65rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--ink);          /* full ink, the same as .grid th */
    font-weight: 700;
    padding: .4rem .45rem;
    background: var(--sunken);
    border-bottom: 1px solid var(--line-strong);
    white-space: nowrap;
}

.mrp-grid td { padding: .25rem .45rem; border-bottom: 1px solid var(--line); vertical-align: middle; }

.mrp-grid input[type=text],
.mrp-grid input[type=number],
.mrp-grid input[type=date] {
    font: inherit;
    font-size: .8125rem;
    width: 100%;
    min-width: 0;
    padding: .3rem .4rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--raised);
    color: var(--ink);
}

.mrp-grid input:focus { border-color: var(--accent); outline: 2px solid var(--accent-soft); }

.mrp-grid .num-in { font-family: var(--font-num); text-align: right; font-variant-numeric: tabular-nums; }

.mrp-grid td.mid { text-align: center; }
.mrp-grid input[type=radio], .mrp-grid input[type=checkbox] { width: 1.05rem; height: 1.05rem; accent-color: var(--accent); cursor: pointer; }

/*  A row switched off stays visible and reads as switched off. Hiding it would
    lose the price a bill from last month still points at.  */
.mrp-grid tr.off td { opacity: .45; }

.mrp-grid .margin { font-family: var(--font-num); font-size: .75rem; color: var(--ink-soft); white-space: nowrap; }

/*  ── the row you are keying ──
    A price grid is three or four rows that look alike, and the one with the
    cursor in it should say so. Lifts under the pointer; when a box in it has
    the keyboard it takes the accent edge as well, so a rate typed into the
    wrong row is caught before it is saved rather than after.  */
.mrp-grid tbody tr { transition: background var(--dur) var(--ease); }
.mrp-grid tbody tr:hover td { background: var(--sunken); }
.mrp-grid tbody tr:focus-within td { background: var(--sunken); }
.mrp-grid tbody tr:focus-within td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

.mrp-grid .margin.loss { color: var(--bad); font-weight: 700; }

.mrp-foot { display: flex; align-items: center; gap: var(--gap-sm); margin-top: var(--gap-sm); flex-wrap: wrap; }

/*  "+ ANOTHER PRICE" IN THE MART'S COLOUR, like the "+" beside each picker:
    it is the one thing on the grid somebody presses, and grey made it look
    like a footnote. Tinted at rest, solid on hover.  */
.mrp-foot .btn {
    background: var(--accent-soft);
    border-color: transparent;
    color: var(--accent);
    font-weight: 600;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
                transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.mrp-foot .btn:hover {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
    transform: translateY(-1px);
    box-shadow: 0 2px 6px rgb(0 0 0 / .14);
}
.mrp-foot .btn:active { transform: none; box-shadow: none; }

/* ═══════════════════════════════════════════════════════ purchase entry ══ */
/*
   THE SALE-RATE COLUMNS ARE TINTED, and it is the one place in this product a
   group of columns is given its own ground.

   A purchase line runs left to right through two different conversations: what
   it COST (quantity, rate, discount, tax) and what it will SELL at (MRP, the
   discount off it, the profit, the rate). They are keyed by the same person in
   the same row, and without a break the eye loses which half it is in.
*/

.mrp-grid th.setup,
.mrp-grid td.setup { background: var(--accent-soft); }

.mrp-grid th.setup { color: var(--accent); }

/*  The first and last of the group get the edge, so the block reads as one
    thing rather than four tinted cells.  */
.mrp-grid th.setup:first-of-type,
.mrp-grid td.setup:first-of-type { border-left: 2px solid var(--accent); }

.mrp-grid .item-cell {
    background: var(--sunken) !important;
    font-weight: 600;
    cursor: default;
}

/* ─────────────────────────────────────────────────── the item search ── */

.find-hits {
    margin-top: var(--gap-sm);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    background: var(--raised);
    max-height: 16rem;
    overflow-y: auto;
    display: grid;
}

.find-hit {
    display: flex;
    align-items: baseline;
    gap: var(--gap-sm);
    padding: .45rem .6rem;
    border: 0;
    border-bottom: 1px solid var(--line);
    background: transparent;
    color: var(--ink);
    font: inherit;
    font-size: .875rem;
    text-align: left;
    cursor: pointer;
}

.find-hit:last-child { border-bottom: 0; }
.find-hit:hover, .find-hit:focus-visible, .find-hit.on { background: var(--accent-soft); }
/*  The row the arrow keys are on. Marked on the left as well as tinted, so it
    stays visible on a theme whose soft accent is close to the card colour.  */
.find-hit.on { box-shadow: inset 3px 0 0 var(--accent); }
.find-hit .faint { font-size: .78rem; }
.find-hit .num { margin-left: auto; }

/* ───────────────────────────────────────────────────── the bill total ── */

table.totals { font-size: .875rem; }
table.totals td { padding: .3rem .5rem; border-bottom: 1px solid var(--line); }
table.totals td:last-child { text-align: right; font-family: var(--font-num); font-variant-numeric: tabular-nums; }
table.totals tr:last-child td { border-bottom: 0; }

/*  The one figure anybody actually reads off this panel.  */
table.totals tr.grand td {
    border-top: 2px solid var(--line-strong);
    padding-top: .5rem;
    font-size: 1.05rem;
}
table.totals tr.grand td:last-child { color: var(--accent); }

/* ══════════════════════════════════════════════════════════════ the till ══ */
/*
   A counter screen, not a form. Two columns: what is being sold, and what it
   comes to. The money column is fixed and never scrolls away — a cashier
   glances at the total between every item, and a total that has scrolled off
   is a total nobody trusts.
*/

.till {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 20rem;
    gap: var(--gap);
    align-items: start;
    /*  Fills the window under the full-screen bar. The grid scrolls inside
        itself rather than the page scrolling, so the scan box stays put.  */
    height: calc(100vh - 7rem);
}

@media (max-width: 60rem) { .till { grid-template-columns: minmax(0, 1fr); height: auto; } }

.till-main { display: flex; flex-direction: column; min-width: 0; height: 100%; }

.till-scan { display: flex; gap: var(--gap-sm); margin-bottom: var(--gap-sm); }

/*  ═══ THE ONE BOX THAT MATTERS ═══

    BIG, because it is used a thousand times a day. Monospace so a barcode reads
    as digits and a mis-scan is visible, and outlined in the accent so the eye
    lands on it without looking.

    THE ITEM MASTER'S SEARCH SHARES IT, and shares this rule rather than a copy
    of it. It is the same job — find a thing by typing or by scanning it — and
    two boxes doing one job should not drift apart because somebody adjusted the
    padding on whichever screen they had open. Layout stays with each screen;
    only the look is common.  */
.till-scan input,
.head-search input[name="Q"] {
    font: inherit;
    font-family: var(--font-num);
    /*  The same height as the buttons beside it. At 1.1rem type in .6rem
        padding and a 2px border the box stood 50px tall next to 40px
        buttons and read as a banner, not a field.  */
    font-size: 1rem;
    min-height: var(--ctl-h);
    padding: .35rem .65rem;
    border: 1.5px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--raised);
    color: var(--ink);
}

.till-scan input:focus,
.head-search input[name="Q"]:focus { outline: 3px solid var(--accent-soft); }

.till-scan input { flex: 1; }

.till-msg {
    margin: 0 0 var(--gap-sm);
    padding: .5rem .7rem;
    border-radius: var(--radius-sm);
    font-size: .875rem;
    background: var(--bad-soft);
    color: var(--bad);
    border: 1px solid var(--bad);
}
.till-msg.ok { background: var(--ok-soft); color: var(--ok); border-color: var(--ok); }

/*  --pull: 0 CANCELS .scroller's BLEED, AND THIS BOX MUST NOT BLEED.

    The till grid carries both .scroller and .till-grid-wrap. .scroller is
    built for a table that runs to the edges of the card around it: it pulls
    itself out by the card's padding and pads the same amount back, so the rows
    reach the panel edge while the header stays aligned. That only nets to zero
    when there IS a card with that padding to be pulled out of.

    Here there is not. This is a bordered, rounded box of its own, and on a
    phone the negative margin had nothing to absorb it - the wrapper came out
    391px wide inside a 375px viewport and the whole counter scrolled sideways
    by 8px. On a till that is the screen jittering under the cashier's thumb
    between every scan.

    The wide table still scrolls: overflow-x stays on from .scroller, and
    .till-grid's own min-width is what makes it wider than the box.  */
.till-grid-wrap { --pull: 0px; flex: 1; min-height: 0; overflow-y: auto; border: 1px solid var(--line); border-radius: var(--radius); }

.till-grid { min-width: 34rem; font-size: .9375rem; }
.till-grid th { position: sticky; top: 0; z-index: 2; }
.till-grid td { padding: .35rem .45rem; }

/*  The newest line is what was just scanned, and it is the one the cashier
    checks. Marked for a moment rather than permanently.  */
.till-grid tr.just-added td { background: var(--accent-soft); }

.till-item { line-height: 1.25; }
.till-item b { display: block; font-weight: 600; }
.till-item .faint { font-size: .75rem; }

.till-price { width: 100%; font: inherit; font-size: .8125rem; padding: .2rem .3rem;
              border: 1px solid var(--line); border-radius: var(--radius-sm);
              background: var(--raised); color: var(--ink); }

.till-why { display: block; font-size: .6875rem; margin-top: .1rem; }

.till-empty { padding: var(--gap-lg); text-align: center; }

/* ---- the money column --------------------------------------------------- */

.till-side {
    display: flex;
    flex-direction: column;
    gap: var(--gap-sm);
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--gap);
    position: sticky;
    top: 0;
}

.till-total {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding-bottom: var(--gap-sm);
    border-bottom: 2px solid var(--accent);
}
.till-total span { font-size: .8125rem; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-faint); font-weight: 700; }

/*  THE ONE FIGURE THE WHOLE SCREEN EXISTS FOR. Sized so it is readable across
    a counter, which is where the customer is standing.  */
.till-total b {
    font-family: var(--font-num);
    font-size: 2.2rem;
    font-weight: 700;
    letter-spacing: -.03em;
    line-height: 1;
}

.till-bits { display: grid; grid-template-columns: 1fr auto; gap: .15rem var(--gap); margin: 0; font-size: .8125rem; }
.till-bits dt { color: var(--ink-soft); }
.till-bits dd { margin: 0; font-family: var(--font-num); font-variant-numeric: tabular-nums; }
.till-bits.big { font-size: 1rem; gap: .35rem var(--gap); }
.till-bits.big dd { font-size: 1.15rem; font-weight: 600; }

.till-tender { justify-content: center; font-size: 1rem; padding: .7rem; margin-top: auto; }
.till-tender kbd { background: rgba(255,255,255,.18); border-color: transparent; color: inherit; }

.till-note { font-size: .6875rem; line-height: 1.4; margin: 0; }

/* ---- the tender dialog -------------------------------------------------- */

dialog.till-pop { width: min(30rem, calc(100vw - 2rem)); }
dialog.till-pop .pop-head b { font-family: var(--font-num); font-size: 1.4rem; }

/*  The notes a customer actually hands over, and "exact". One press instead of
    typing — this is most of the difference between a fast till and a slow one. */
.till-quick { display: flex; flex-wrap: wrap; gap: .3rem; margin: .2rem 0 .4rem; }
.till-quick button {
    font: inherit; font-size: .8125rem; font-weight: 600;
    padding: .3rem .6rem; border-radius: var(--radius-sm);
    border: 1px solid var(--line-strong); background: var(--sunken); color: var(--ink);
    cursor: pointer; font-family: var(--font-num);
}
.till-quick button:hover { border-color: var(--accent); color: var(--accent); }
.till-quick button.exact { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }

.till-found { display: grid; gap: 2px; margin-top: var(--gap-sm); max-height: 16rem; overflow-y: auto; }
.till-found button {
    text-align: left; font: inherit; font-size: .875rem;
    padding: .4rem .5rem; border: 1px solid transparent; border-radius: var(--radius-sm);
    background: transparent; color: var(--ink); cursor: pointer;
}
.till-found button:hover, .till-found button:focus-visible { background: var(--sunken); border-color: var(--line); }
.till-found small { display: block; color: var(--ink-faint); font-size: .75rem; }

/* ═════════════════════════════════════════════ the live item list ══ */
/*
    WHAT MATCHED, UNDER THE BOX, AS YOU TYPE.

    Not a dropdown floating over the grid: it pushes the lines down instead of
    covering them. An operator picking an item is usually checking it against
    what is already on the bill, and a panel that hides those lines makes them
    scroll to compare.

    Capped and scrolled so twenty matches cannot push the bill off the screen.
*/
.find-live {
    margin-bottom: var(--gap-sm);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--sunken);
    max-height: 15rem;
    overflow-y: auto;
}

.find-live-grid { width: 100%; }
.find-live-grid th { position: sticky; top: 0; background: var(--sunken); z-index: 1; }
.find-live-grid td { padding: .35rem .5rem; }

.find-live-grid tbody tr { cursor: pointer; }

/*  One highlight for the mouse and the arrow keys both, so the row Enter would
    take is always the row that looks chosen.  */
.find-live-grid tbody tr.on td { background: var(--accent-soft); }

.find-name b { display: block; font-weight: 600; }
.find-name .faint { font-size: .75rem; color: var(--ink-faint); }

/*  The recall list puts the voucher, who it was for and what it came to on one
    row, so the operator picks by reading rather than by opening each one.  */
.till-found button b { display: inline-block; min-width: 10rem; }
.till-found button .faint { color: var(--ink-faint); }
.till-found button .num { float: right; font-variant-numeric: tabular-nums; }

/* ══════════════════════════════════════════ goods coming back ══ */
/*
    A LINE GOING THE OTHER WAY HAS TO LOOK IT. The whole point of one screen
    doing both is that the cashier can see at a glance which way each thing is
    travelling; two lines that look identical and mean opposite things is worse
    than two screens.

    Coloured with the bad tone rather than a green or a grey: money leaving the
    drawer is the thing worth catching before the customer walks off with it.
*/
tr.coming-back td { background: color-mix(in srgb, var(--bad, #b91c1c) 7%, transparent); }
tr.coming-back [data-amount] { color: var(--bad, #b91c1c); font-weight: 600; }
tr.coming-back [data-name]::after {
    content: " · coming back";
    color: var(--bad, #b91c1c);
    font-weight: 600;
    font-size: .75rem;
}

/*  The credit note screen: a line with nothing against it yet is dimmed but
    stays put, because the operator is reading the bill against the goods on the
    counter and a disappearing line breaks that reading.  */
tr.idle { opacity: .55; }
tr.idle:focus-within { opacity: 1; }

/*  A quantity held at what is still returnable. It flashes rather than
    argues — SQL refuses the rest with 54012 and this is what stops the operator
    ever meeting that sentence.  */
tr.at-cap [data-f="Quantity"] {
    border-color: var(--bad, #b91c1c);
    background: color-mix(in srgb, var(--bad, #b91c1c) 10%, transparent);
}

/* ═════════════════════════════════════════════════════════════ printing ══ */

@media print {
    .topbar, .menu, .form-foot, .drawer-toggle, .footer { display: none !important; }

    /*  A FIXED PANEL WOULD PRINT ONE PAGE AND THROW THE REST AWAY. On paper
        there is no window to fit: the shell is as tall as what it holds and
        nothing scrolls or clips.  */
    .shell {
        grid-template-columns: minmax(0, 1fr);
        height: auto;
        border: 0;
        border-radius: 0;
        overflow: visible;
    }

    .shell-right > main { overflow: visible; }
    body:has(.shell) { padding: 0; }
    .card { border: 0; box-shadow: none; padding: 0; }
    body { background: #fff; }
}

/* ─────────────────────────────────────────────── chips you can tick ── */
/*  A scheme is aimed at a handful out of a thousand items, so the picker is
    a filtered wall of chips rather than a multi-select: a ticked one has to be
    findable at a glance, which a highlighted <option> in a scrolling box never
    is.  */

.chip:has(input:checked) {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--ink);
    font-weight: 600;
}

.chip input[type="checkbox"] { accent-color: var(--accent); }

a.chip { text-decoration: none; }
a.chip:hover { border-color: var(--accent); color: var(--ink); }

/*  A thousand items would otherwise push the Save button off the bottom of
    the page.  */
[data-pick-list] {
    max-height: 18rem;
    overflow-y: auto;
    padding: .25rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--ground);
}

/* ─────────────────────────────────────────── help, and what to try ── */
/*  The advice under a refusal is set apart from the refusal itself. One says
    what went wrong and the other says what to do about it, and running them
    together turns both into a wall of red nobody reads to the end.  */

.notice .advice {
    margin: .55rem 0 0;
    padding-top: .55rem;
    border-top: 1px solid currentColor;
    opacity: .85;
    font-size: .875rem;
}

/*  Bottom right, out of the way of the work. A help button in the page flow
    moves every time the content does, and then nobody knows where it is.  */
.help-btn {
    position: fixed;
    right: var(--gap);
    bottom: var(--gap);
    z-index: 30;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .5rem .9rem;
    border: 1px solid var(--line);
    border-radius: 2rem;
    background: var(--raised);
    color: var(--ink-soft);
    font: inherit;
    font-size: .8125rem;
    cursor: pointer;
    box-shadow: 0 2px 10px rgb(0 0 0 / .12);
}

.help-btn:hover { border-color: var(--accent); color: var(--ink); }

.help-pop { max-width: 34rem; }
.help-pop .auth-points { margin: 0 0 var(--gap); }

@media print { .help-btn { display: none; } }

/* ══════════════════════════════════ number inputs, without the little arrows ══ */
/*
    THE SPINNERS ARE REMOVED EVERYWHERE, ON PURPOSE.

    A grocery bill is keyed at speed on a keyboard, and the up/down arrows are
    worse than useless there: they eat horizontal room in a grid already short
    of it, they land under the mouse when somebody meant to click the cell, and
    a stray scroll over a focused box silently changes a quantity or a rate.
    That last one is the real reason - a number that changed because the page
    scrolled is a number nobody can explain afterwards.

    Typing still works exactly as before, and so do the arrow KEYS, which are
    the version people actually use.
*/
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

input[type=number] { -moz-appearance: textfield; appearance: textfield; }

/* ═══════════════════════════ the collapsed batch cell and the GST button ══ */
/*
    Both are buttons that have to read as CELLS, not as controls. A grid with
    twenty real buttons in it looks like a toolbar; these carry a value, and the
    fact that they can be clicked shows on hover where it is wanted rather than
    competing with the figures all the time.
*/
.mrp-grid .batch-btn,
.mrp-grid .gst-btn {
    width: 100%;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    padding: .25rem .35rem;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.mrp-grid .gst-btn { text-align: center; font-family: var(--font-num); }

.mrp-grid .batch-btn:hover,
.mrp-grid .gst-btn:hover {
    border-color: var(--line);
    background: var(--sunken);
}

.mrp-grid .batch-btn:focus-visible,
.mrp-grid .gst-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/*  A perishable line with no expiry date. Marked rather than refused: the
    carton may genuinely not carry one, and the operator is the one who can
    see it. But it will never reach the expiry report, so it does not pass
    silently either.  */
.mrp-grid tr.wants-expiry .batch-btn {
    border-color: var(--warn, #b45309);
    border-style: dashed;
}

.mrp-grid td.batch-cell { padding: .15rem .2rem; }

/* ══════════════════════════════ sortable headings, paging, dead actions ══ */

/*  A heading that sorts still has to read as a heading. Same weight and colour
    as one that does not; the arrow is the only thing that says which column is
    doing the work, because a row of blue links across the top of a table is a
    navigation bar nobody asked for.  */
.grid thead .sort {
    color: inherit;
    text-decoration: none;
    display: inline-block;
    width: 100%;
}

/*  The sorted column and the hovered one stay white too - the arrow and
    the underline say which is which, so no theme colour is needed on the
    band.  */
.grid thead .sort:hover { color: #FFFFFF; text-decoration: underline; text-underline-offset: .2em; }
.grid thead .sort.on { color: #FFFFFF; font-weight: 800; text-decoration: underline; text-underline-offset: .2em; }

/*  Right-aligned headings need their arrow on the same side as the figures,
    or the column looks broken while it is the active sort.  */
.grid th.num .sort { text-align: right; }

.pager {
    display: flex;
    align-items: center;
    gap: .35rem;
    flex-wrap: wrap;
    padding: var(--gap-sm);
    border-top: 1px solid var(--line);
}

.pager .sep {
    width: 1px;
    height: 1.25rem;
    background: var(--line);
    margin-inline: .4rem;
}

.pager .pg {
    min-width: 2rem;
    padding: .2rem .55rem;
    border: 1px solid var(--line);
    border-radius: 999px;
    text-align: center;
    text-decoration: none;
    color: var(--ink);
    font-size: .8125rem;
    font-family: var(--font-num);
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.pager .pg:hover { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); text-decoration: none; }

/*  ═══ THE LIST'S SEARCH ROW ═══

    The search used to share the page-head with the window buttons and "New
    item", and the box got what was left. Now the header is the strip and
    this row is the search's own: the group chooser, the box, Search in the
    mart's colour, and the filter button with a badge saying how many filters
    are on. Filters that are on tint the button, so a narrowed list is never
    mistaken for the whole one.  */
/*  ═══ THE MASTERS' TAB ROW ═══  (see Master/_MasterTabs.cshtml)

    Items · Groups and GST · Brand · Unit · Customers and suppliers, each
    with its count in a small pill, on one hairline. The current one is
    ink with the mart's colour underlining it; the rest are soft and take
    the colour on hover. On a phone the row scrolls sideways rather than
    wrapping into a block.  */
/*  A SEGMENTED ROW OF PILLS on one sunken plate - the same device the
    bar's quick buttons use, so the two read as one product. The chosen
    tab is filled in the mart's colour with its count in a white pill; the
    rest are plain on the plate and take a tint on hover, lifting a hair.  */
.tabs {
    display: inline-flex;
    align-items: center;
    gap: .2rem;
    margin-bottom: .5rem;
    padding: .2rem;
    border-radius: 999px;
    border: 1px solid var(--line);
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;

    /*  ═══ IT SAYS WHEN THERE IS MORE ═══

        The strip scrolls sideways and hides its scrollbar, which on a phone
        left it indistinguishable from a strip that was simply cut off. The
        last tab was sliced through the middle of its word and nothing
        suggested a gesture would bring the rest.

        FOUR LAYERS, AND THE TRICK IS background-attachment. The two SHADOWS
        are attached `local`, so they travel with the content: the right-hand
        one sits at the far end of everything and is therefore only in view
        while there is something further to scroll to. The two COVERS are
        attached `scroll`, so they stay pinned to the element's own edges and
        paint over the shadow whenever that end has been reached.

        The upshot is an edge that darkens only in the direction there is more
        to see, at both ends, with no script and nothing to keep in step.  */
    background:
        linear-gradient(to right,  color-mix(in srgb, var(--ink) 16%, transparent), transparent)
            left / 1.25rem 100% no-repeat local,
        linear-gradient(to left,   color-mix(in srgb, var(--ink) 16%, transparent), transparent)
            right / 1.25rem 100% no-repeat local,
        linear-gradient(to right,  var(--sunken), transparent)
            left / 1.25rem 100% no-repeat scroll,
        linear-gradient(to left,   var(--sunken), transparent)
            right / 1.25rem 100% no-repeat scroll,
        var(--sunken);
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
    display: inline-flex; align-items: center; gap: .45rem;
    padding: .35rem .85rem;
    border-radius: 999px;
    border: 1px solid transparent;
    color: var(--ink-soft);
    font-weight: 600; font-size: .875rem;
    text-decoration: none; white-space: nowrap;
    transition: color var(--dur) var(--ease), background var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.tab .ico { width: 1rem; height: 1rem; flex: none; }
.tab:hover { color: var(--accent); background: var(--accent-soft); text-decoration: none; transform: translateY(-1px); }
.tab.on {
    color: var(--accent-ink);
    background: var(--accent);
    box-shadow: 0 2px 8px -2px color-mix(in srgb, var(--accent) 55%, transparent);
}
.tab.on:hover { color: var(--accent-ink); background: var(--accent); }
.tab.on .ico { color: inherit; }
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tab-count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 1.35rem; height: 1.25rem; padding-inline: .35rem;
    border-radius: 999px;
    background: var(--raised); color: var(--ink-soft);
    border: 1px solid var(--line);
    font-size: .6875rem; font-weight: 700;
}
.tab.on .tab-count { background: rgb(255 255 255 / .22); color: inherit; border-color: transparent; }

/*  A captioned chooser on a tool row - "Per page" over its box, the way the
    owner's reference draws it. The caption is the form's small uppercase.  */
/*  INLINE, BESIDE THE BUTTONS - the owner's call: the caption sits to the
    left of its box on the same line, the pair the same height as the
    buttons, so the row is one row.  */
.tool-pick { display: inline-flex; align-items: center; gap: .45rem; flex: none; }
.tool-pick > span { font-size: .6875rem; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; color: var(--ink); white-space: nowrap; }
.tool-pick .combo { width: 6.5rem; flex: none; align-self: auto; }
.tool-pick .combo > input { min-height: var(--ctl-h); padding: .35rem .5rem; font-size: .875rem; }

.list-tools { display: flex; align-items: center; gap: var(--gap-sm); margin-bottom: .5rem; }
/*  COMPACT, the owner's call: the tool row's controls a step under the
    page's 40px, so the strip, the tools, the tabs and the table stack
    without the air of three headers.  */
.list-tools .btn,
.list-tools .head-search input[name="Q"],
.list-tools .combo > input,
.list-tools .tool-pick .combo > input { min-height: 2.25rem; }
.list-tools .head-search > .combo { height: 2.25rem; }
.list-tools .head-search input[name="Q"] { padding-block: .25rem; }
.list-tools .head-search { flex: 1 1 auto; max-width: 56rem; gap: .4rem; }
.list-tools .btn { position: relative; }
.list-tools .btn.is-on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.list-tools .btn .count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 1.15rem; height: 1.15rem;
    /*  .count is also the dashboard's tile class, with a card's padding and
        border; the badge sets both back or it stands 33px tall.  */
    padding: 0 .3rem; border: 0;
    border-radius: 999px;
    background: var(--accent); color: var(--accent-ink);
    font-family: var(--font-num); font-size: .6875rem; font-weight: 700;
    margin-left: .1rem;
}

.pager .pg.on {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
    font-weight: 700;
}

/*  ═══ SHOWN AND DEAD, NOT HIDDEN ═══

    Applies to a page link at the end of the list and to an action somebody's
    role does not carry. Removing either would make the screen change shape
    depending on who is looking at it and where they are - and somebody who can
    SEE that editing exists and is shut to them knows what to ask the owner
    for, where somebody who sees nothing concludes the software cannot do it.  */
.pager .pg.is-off,
.btn-icon.is-off {
    opacity: .4;
    pointer-events: none;
    cursor: default;
}

.btn-icon.is-off { display: inline-flex; align-items: center; justify-content: center; }

/*  A customer past their credit limit. Coloured, never blocked — whether to
    sell to them is the shop's call and often yes.  */
.hint.over-limit { color: var(--bad, #b91c1c); font-weight: 600; }

/*  The rest of the boxes that are not inside a .field - the price grid's
    cells, the find boxes, the list search - answer the pointer the same way.  */
.mrp-grid input:hover,
.find:hover,
.head-search input[name="Q"]:hover,
.page-bar input:hover,
.till-scan input:hover { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
/*  Thicker, not wider: the second pixel is a ring drawn outside the box,
    so nothing beside it moves. Focus keeps its own soft ring.  */
.field input:focus, .field select:focus, .field textarea:focus,
.combo > input:focus { box-shadow: 0 0 0 3px var(--accent-soft); }


/* ═══════════════════════════════════════════════════ pick chips ══ */
/*  A tick-box drawn as a button. The box itself is kept for the browser
    and the screen reader but hidden from the eye; the pill carries the
    state - quiet until picked, accent-filled once it is - with the same
    lift and ring every other button here has on hover.  */
.chip.pick {
    position: relative;
    cursor: pointer;
    user-select: none;
    padding: .38rem .8rem .38rem .7rem;
    background: var(--raised);
    color: var(--ink);
    font-weight: 500;
    transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
                color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}
.chip.pick input[type="checkbox"] {
    position: absolute; inset: 0; width: 100%; height: 100%;
    margin: 0; opacity: 0; cursor: pointer;
}
.chip.pick::before {
    content: "";
    width: .95rem; height: .95rem;
    border: 1.5px solid var(--line-strong, var(--line));
    border-radius: 50%;
    background: var(--ground);
    flex: none;
    transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.chip.pick:hover {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent);
    transform: translateY(-1px);
}
.chip.pick:has(input:checked) {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
    font-weight: 600;
}
.chip.pick:has(input:checked)::before {
    background: var(--accent-ink);
    border-color: var(--accent-ink);
}
/*  The tick is two borders turned, in the accent itself - so it follows the
    theme rather than a colour written here.  */
.chip.pick::after {
    content: "";
    position: absolute;
    left: calc(.7rem + .475rem);
    top: 50%;
    width: .28rem; height: .5rem;
    border-right: 2px solid var(--accent);
    border-bottom: 2px solid var(--accent);
    transform: translate(-50%, -62%) rotate(45deg);
    opacity: 0;
    transition: opacity var(--dur) var(--ease);
    pointer-events: none;
}
.chip.pick:has(input:checked)::after { opacity: 1; }
.chip.pick:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

/*  A filter row's wide field: the layout picker on the labels page reads
    "Roll 50 x 25 mm - name, MRP, price", and it wants twice the room of a
    date box.  */
.filter-form .fields.g12 .field.c6 { flex: 2 1 16rem; }

/*  ═══ THE NOTE CARDS ("How the counter uses this", "Two things worth
    knowing"), COMPACT ═══

    Every one of them is the same shape - an icon, a heading, a dotted list -
    so the card is recognised by the list it holds rather than by a class
    each view would have to remember. The head becomes the same thin caption
    the list cards wear, the icon a small accent mark beside it, and the
    bullets close up.  */
.card:has(> ul.auth-points) { --pad-card: .6rem; padding: var(--pad-card); }
.card:has(> ul.auth-points) > .card-head { margin-bottom: .3rem; padding-bottom: .25rem; gap: .35rem; min-height: 0; }
.card:has(> ul.auth-points) > .card-head h2 { font-size: .6875rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink); }
.card:has(> ul.auth-points) > .card-head > .ico { width: .95rem; height: .95rem; padding: 0; background: none; color: var(--accent); border-radius: 0; }
.card:has(> ul.auth-points) > .auth-points { gap: .3rem; }
.card:has(> ul.auth-points) > .auth-points li { font-size: .78rem; line-height: 1.4; }

/*  The preference groups (Stock, Billing, Loyalty): the caption head above,
    and the setting rows closed up under it.  */
.pref-group { --pad-card: .6rem; padding: var(--pad-card); }
.pref-group .spec-row { padding-block: .45rem; gap: .2rem var(--gap-sm); }

/*  A row of tiles or figures followed by a card: the same breath the cards
    keep between themselves, so nothing sits on the row above it.  */
.tiles + .card, .kpis + .card, .tiles + .kpis, .kpis + .tiles { margin-top: var(--gap-sm); }

/*  A note card that carries a paragraph rather than a list ("Codes this shop
    issues") wears the same caption head as the listed ones.  */
.card.note-card > .card-head { margin-bottom: .3rem; padding-bottom: .25rem; gap: .35rem; min-height: 0; }
.card.note-card > .card-head h2 { font-size: .6875rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink); }
.card.note-card > .card-head > .ico { width: .95rem; height: .95rem; padding: 0; background: none; color: var(--accent); border-radius: 0; }
.card.note-card > p.muted { font-size: .8125rem; line-height: 1.45; }
.card.note-card > p.notice { margin-bottom: 0; font-size: .8125rem; padding: .5rem .7rem; }

/*  A card that only wants the caption head (the API key panels): the same
    thin line, and the one sentence under it kept small. The foot of its form
    sits close under the fields rather than a rule and a band away.  */
.card.cap-head { --pad-card: .6rem; padding: var(--pad-card); }
.card.cap-head > p.muted.small { font-size: .8125rem; line-height: 1.4; }
.card.cap-head .card-head .pill { font-size: .7rem; }
.form-foot.tight { margin-top: .5rem; padding-top: .5rem; }
.form-foot.tight .btn { min-height: var(--ctl-h-sm); padding-block: .3rem; }

/*  THE TOTAL UNDER A RULED LIST is the line the owner reads first, and the
    ruled grid's small dense heading size was reaching it (the ".lined th"
    rule outranks "tfoot th"). Stated here so it comes out bold and a size up
    from the rows, on a quiet band with a heavier rule above - a conclusion,
    not one more row.  */
.grid.lined tfoot th {
    font-size: .9rem;
    font-weight: 800;
    color: var(--ink);
    padding: .55rem .5rem;
    background: color-mix(in srgb, var(--sunken) 70%, var(--raised));
    border-top: 2px solid color-mix(in srgb, var(--accent) 45%, var(--line-strong));
    border-right: 0;
    text-transform: none;
    letter-spacing: 0;
}
.grid.lined tfoot th.num { font-size: 1rem; }

/*  A card followed by a row of tiles or figures keeps the same breath the
    other way round too - the schemes overview had its table sitting on the
    two doors under it.  */
.card + .tiles, .card + .kpis { margin-top: var(--gap-sm); }

/*  Two doors on a wide screen share the row rather than huddling at the
    left of it: auto-FIT stretches what is there across the width, where the
    compact grid's auto-fill kept empty columns for tiles that never came.  */
.tiles.fill { grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); }

/*  The same firm, theme-coloured edge the item screen's cards wear, for any
    entry form that asks for it (the scheme form): boxes somebody types into,
    with a lift on hover so each one answers the pointer like a card should.  */
form.firm > .card {
    border-color: color-mix(in srgb, var(--accent) 45%, var(--line-strong));
    box-shadow: var(--shadow-sm), 0 0 0 3px color-mix(in srgb, var(--accent) 8%, transparent);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
form.firm > .card:hover {
    border-color: var(--accent);
    box-shadow: var(--shadow), 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent);
}
form.firm > .card + .card { margin-top: .6rem; }
form.firm .card.cap-head .card-head { margin-bottom: .45rem; }
form.firm .fields.g12 { row-gap: .45rem; }
form.firm .chips { gap: .35rem; }
form.firm .field > .chips { margin-top: .1rem; }

/*  A card that is itself the form (the mart's own details): the same firm
    theme-coloured edge and hover as the entry forms' cards.  */
.card.firm {
    border-color: color-mix(in srgb, var(--accent) 45%, var(--line-strong));
    box-shadow: var(--shadow-sm), 0 0 0 3px color-mix(in srgb, var(--accent) 8%, transparent);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.card.firm:hover {
    border-color: var(--accent);
    box-shadow: var(--shadow), 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent);
}
.card.firm .fields.g12 { row-gap: .45rem; }

/*  A one-row add form inside a card (the stores list): the fields share the
    width and the button keeps its own at the end, as the filter rows do.  */
.row-form .fields.g12 { display: flex; flex-wrap: wrap; gap: .4rem .6rem; align-items: flex-end; }
.row-form .fields.g12 .field { flex: 1 1 8rem; min-width: 0; }
.row-form .fields.g12 .field.actions { display: flex; flex: 0 0 auto; flex-direction: row; gap: .35rem; align-items: center; }

/*  A pager sitting in a card head beside the caption: no rule or padding of
    its own, the head's own row carries it, and it stays small.  */
.card-head > .pager.inline { border: 0; padding: 0; margin: 0 0 0 .75rem; gap: .3rem; font-size: .8125rem; }
.card-head > .pager.inline .tool-pick { margin-inline: .35rem; }
.card-head > .pager.inline .tool-pick .combo > input,
.card-head > .pager.inline .tool-pick select { min-height: 1.75rem; padding-block: .1rem; font-size: .8125rem; }
.card-head > .pager.inline .tool-pick > span { font-size: .6rem; }
.card-head > .pager.inline .pg { min-width: 1.75rem; padding: .1rem .5rem; font-size: .8125rem; }

/*  ═══ THE MARK ANSWERS THE POINTER ═══

    A logo that does nothing under the mouse reads as a picture; one that
    lifts a little and glows in the mart's own colour reads as the door
    home, which is what it is. The image sits in a soft rounded plate so the
    glow has an edge to hold; at rest the plate is invisible.  */
.brandmark {
    display: inline-flex; align-items: center;
    padding: .2rem .35rem;
    margin: -.2rem -.35rem;
    border-radius: var(--radius);
    transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}
.brandmark img {
    transition: transform var(--dur) var(--ease), filter var(--dur) var(--ease);
}
.brandmark:hover {
    background: var(--accent-soft);
    box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 22%, transparent),
                0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
    transform: translateY(-1px);
}
.brandmark:hover img {
    transform: scale(1.04);
    filter: drop-shadow(0 2px 4px color-mix(in srgb, var(--accent) 35%, transparent));
}
.brandmark:active { transform: translateY(0); }
.brandmark:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/*  ═══ A LIGHT PASSES OVER THE MARK ═══

    A soft band of light sweeps across the logo every few seconds - the
    glint a printed sign gets from a passing headlamp. It is drawn on a
    layer above the image and MASKED BY THE IMAGE ITSELF, so the light
    shows only on the cart and the lettering, never as a bar across the
    plate behind them. Cut for anyone who has asked for less motion.  */
@keyframes logo-shine {
    0%   { background-position: 140% 0; }
    60%  { background-position: -40% 0; }
    100% { background-position: -40% 0; }
}
/*  The rail's brand is already sticky, which positions it; the public mark
    needs the relative for the layer to sit on.  */
.brandmark { position: relative; overflow: hidden; }
.menu .rail-brand { overflow: hidden; }
.brandmark::after, .menu .rail-brand::after {
    content: "";
    position: absolute;
    pointer-events: none;
    /*  A wide, bright band with a faint accent fringe on each side, so the
        pass reads as light and not as a scratch: the owner could not see
        the thin one go by.  */
    background: linear-gradient(105deg,
        transparent 30%,
        color-mix(in srgb, var(--accent) 35%, transparent) 38%,
        rgb(255 255 255 / .75) 45%,
        rgb(255 255 255 / 1) 50%,
        rgb(255 255 255 / .75) 55%,
        color-mix(in srgb, var(--accent) 35%, transparent) 62%,
        transparent 70%);
    background-size: 260% 100%;
    background-repeat: no-repeat;
    -webkit-mask: url("../img/keymart-logo.png") center / contain no-repeat;
            mask: url("../img/keymart-logo.png") center / contain no-repeat;
    animation: logo-shine 3s ease-in-out infinite;
}
/*  The layer sits exactly over the image: the plate's own padding is the
    inset on each side.  */
.brandmark::after { inset: .2rem .35rem; }
.menu .rail-brand::after { inset: .85rem var(--gap); }
/*  On hover the light comes straight away and a touch brighter.  */
.brandmark:hover::after, .menu .rail-brand:hover::after { animation-duration: 1.4s; }
/*  Put away, the rail shows the square cart on its own - a different image,
    so the wordmark's mask would show nothing and the light is simply off.  */
.shell.is-collapsed .menu .rail-brand::after { display: none; }
@media (prefers-reduced-motion: reduce) {
    .brandmark::after, .menu .rail-brand::after { animation: none; display: none; }
}

/*  ═══ THE PUBLIC BAR'S CONTROLS, AS PILLS WITH A MARK ═══

    Theme, Owner sign in, Staff sign in and Register wear the same dress as
    the landing page's section nav: a pill, the icon in a soft accent disc,
    dark text; under the pointer the pill tints, the disc fills, a glow in
    the mart's colour. Register is the filled one - the only thing a first
    visitor is asked to do - and keeps its solid accent with the disc cut
    from white. A light passes over all four, as it does on the mark.  */
.topbar-actions .btn,
.themebox > summary {
    display: inline-flex; align-items: center; gap: .45rem;
    min-height: 2.25rem;
    padding: .3rem .85rem .3rem .4rem;
    font-size: .8125rem; font-weight: 700;
    color: var(--ink);
    background: var(--raised);
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    position: relative; overflow: hidden; isolation: isolate;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
                border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.topbar-actions .btn .ico,
.themebox > summary .ico {
    width: 1.5rem; height: 1.5rem; padding: .3rem;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent);
    flex: none;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.topbar-actions .btn:hover,
.themebox > summary:hover,
.themebox[open] > summary {
    background: var(--accent-soft);
    color: var(--ink);
    border-color: color-mix(in srgb, var(--accent) 35%, transparent);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 18%, transparent);
    transform: none;
    text-decoration: none;
}
.topbar-actions .btn:hover .ico,
.themebox > summary:hover .ico,
.themebox[open] > summary .ico { background: var(--accent); color: var(--accent-ink); }

/*  The filled one.  */
.topbar-actions .btn-primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.topbar-actions .btn-primary .ico { background: color-mix(in srgb, var(--accent-ink) 22%, transparent); color: var(--accent-ink); }
.topbar-actions .btn-primary:hover {
    background: color-mix(in srgb, var(--accent) 88%, black);
    color: var(--accent-ink);
    border-color: color-mix(in srgb, var(--accent) 88%, black);
    box-shadow: 0 3px 10px color-mix(in srgb, var(--accent) 35%, transparent);
}
.topbar-actions .btn-primary:hover .ico { background: var(--accent-ink); color: var(--accent); }

/*  The chevron on Theme keeps its place after the word.  */
.themebox > summary .chev { margin-left: .1rem; }

/*  The passing light - the landing buttons' sweep, on the sign-in and
    register buttons. Not on Theme: that one is a menu, dressed like the
    section nav, and stays still.  */
@keyframes bar-shine {
    0%   { transform: translateX(-140%) skewX(-18deg); }
    55%  { transform: translateX(240%) skewX(-18deg); }
    100% { transform: translateX(240%) skewX(-18deg); }
}
.topbar-actions .btn::before {
    content: "";
    position: absolute;
    top: -20%; bottom: -20%;
    left: 0; width: 45%;
    background: linear-gradient(105deg,
        transparent 0%,
        color-mix(in srgb, var(--accent) 22%, transparent) 30%,
        color-mix(in srgb, var(--accent) 38%, transparent) 50%,
        color-mix(in srgb, var(--accent) 22%, transparent) 70%,
        transparent 100%);
    pointer-events: none;
    z-index: -1;
    animation: bar-shine 3.4s ease-in-out infinite;
}
.topbar-actions .btn-primary::before {
    background: linear-gradient(105deg,
        transparent 0%,
        rgb(255 255 255 / .35) 30%,
        rgb(255 255 255 / .7) 50%,
        rgb(255 255 255 / .35) 70%,
        transparent 100%);
}
.topbar-actions .btn:hover::before { animation-duration: 1.2s; }
@media (prefers-reduced-motion: reduce) {
    .topbar-actions .btn::before { animation: none; display: none; }
}

/*  ═══ THE DOORS, DRESSED ═══

    The three sign-in / register screens. The ground behind the panel gets
    two soft washes of the mart's colour so the card sits in light rather
    than on grey; the card itself takes a bigger radius, a coloured shadow
    and a hairline of accent along its top; the fields grow a little, round
    off, and their icons sit in accent discs that fill when the field is
    focused; the button is a pill with a glow that lifts under the pointer.
    Nothing here changes a name, a binding or a post.  */
.auth {
    background:
        radial-gradient(60% 50% at 8% 10%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 70%),
        radial-gradient(50% 45% at 95% 90%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 70%);
}
.auth-panel {
    border-radius: calc(var(--radius-lg) + .4rem);
    border-color: color-mix(in srgb, var(--accent) 25%, var(--line));
    box-shadow: var(--shadow-lg), 0 24px 60px -20px color-mix(in srgb, var(--accent) 35%, transparent);
    position: relative;
}
.auth-panel::before {
    content: "";
    position: absolute; inset: 0 0 auto 0; height: 3px;
    background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 40%, transparent));
    z-index: 1;
}

/*  The badge and the heading on the picture side.  */
.auth-door { box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 35%, transparent); letter-spacing: .1em; }
.auth-aside h2 { font-size: 1.3rem; letter-spacing: -.015em; }

/*  The points: a tick in an accent disc rather than a dot. The disc is the
    ::before the list already draws, grown; the tick is the ::after, two
    borders turned, in the accent's own ink - no image, so it takes the
    theme's colours and cannot fail to load.  */
.auth-aside .auth-points li { padding-left: 1.55rem; color: var(--ink); position: relative; }
.auth-aside .auth-points li::before {
    top: .15em; width: 1.05rem; height: 1.05rem;
    background: var(--accent);
    border-radius: 50%;
}
.auth-aside .auth-points li::after {
    content: "";
    position: absolute;
    left: .37rem; top: calc(.15em + .2rem);
    width: .28rem; height: .5rem;
    border-right: 2px solid var(--accent-ink);
    border-bottom: 2px solid var(--accent-ink);
    transform: rotate(45deg);
}

/*  The form side.  */
.auth-form h1 { font-size: 1.5rem; letter-spacing: -.025em; }
.auth-form .field > label {
    font-size: .6875rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
    color: var(--ink);
}
.auth-form .in-ico > input {
    min-height: 2.75rem;
    padding-left: 2.9rem;
    border-radius: calc(var(--radius) + .15rem);
    font-size: .95rem;
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), background var(--dur) var(--ease);
}
.auth-form .in-ico > .ico {
    left: .55rem;
    width: 1.75rem; height: 1.75rem; padding: .4rem;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent);
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.auth-form .in-ico:focus-within > .ico { background: var(--accent); color: var(--accent-ink); }
.auth-form .in-ico > input:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
    background: var(--raised);
}
.auth-form .in-ico > .reveal { right: .45rem; border-radius: 50%; }

/*  The back link as a small pill.  */
.auth-back {
    display: inline-flex; align-items: center; gap: .35rem;
    padding: .25rem .7rem .25rem .45rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    font-size: .8125rem; font-weight: 600;
    color: var(--ink-soft);
    background: var(--raised);
    justify-self: start;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.auth-back:hover { background: var(--accent-soft); color: var(--ink); border-color: var(--accent); text-decoration: none; }
.auth-back .ico { width: .95rem; height: .95rem; }

/*  The button: a pill, a glow, a lift.  */
.auth-form .btn-primary {
    min-height: 2.9rem;
    border-radius: var(--radius-pill);
    font-size: 1rem; font-weight: 700;
    gap: .5rem;
    background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 92%, white), var(--accent));
    box-shadow: 0 8px 20px -8px color-mix(in srgb, var(--accent) 65%, transparent);
    transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), filter var(--dur) var(--ease);
}
.auth-form .btn-primary:hover {
    transform: translateY(-1px);
    box-shadow: 0 12px 26px -8px color-mix(in srgb, var(--accent) 75%, transparent);
    filter: brightness(1.04);
}
.auth-form .btn-primary .ico { width: 1.1rem; height: 1.1rem; }

/*  The other door, as a quiet pill link under the button.  */
.auth-switch a {
    display: inline-block;
    margin-left: .2rem;
    padding: .15rem .6rem;
    border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
    border-radius: var(--radius-pill);
    font-weight: 600;
    color: var(--accent);
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.auth-switch a:hover { background: var(--accent); color: var(--accent-ink); text-decoration: none; }
/*  Under the form now, centred with the other two foot lines.  */
.auth-form .auth-back { justify-self: center; margin-top: .1rem; }

/*  ═══ THE PICTURE SIDE TAKES THE PAGE, THE FORM SITS IN A SMALL CARD ═══

    The owner's shape for the doors: the panel fills the window edge to
    edge, the left half - mark, words, picture - runs the full height, and
    the form is a card of its own, floating in the middle of the right half
    on the page's off-white ground. Small, but not cramped: 27rem wide, the
    same fields and rhythm as before. Below a tablet width the two halves
    stack as they always did.  */
.auth { padding: var(--gap-sm); }
.auth-panel {
    width: 100%;
    min-height: calc(100svh - var(--gap-sm) * 2);
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
    background: var(--ground);
    border-radius: var(--radius-lg);
}
.auth-aside {
    padding: clamp(1.5rem, 3vw, 2.75rem);
    gap: .85rem;
    justify-content: flex-start;
}
.auth-aside .auth-art { margin-top: auto; }
.auth-form {
    align-self: center;
    justify-self: center;
    width: min(27rem, 100% - 2rem);
    margin-block: 2rem;
    padding: clamp(1.4rem, 2.5vw, 2rem);
    background: var(--raised);
    border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--line));
    border-radius: calc(var(--radius-lg) + .2rem);
    box-shadow: var(--shadow-lg), 0 18px 45px -18px color-mix(in srgb, var(--accent) 35%, transparent);
}
@media (max-width: 52rem) {
    .auth-panel { min-height: 0; }
    .auth-form { width: min(27rem, 100% - 2rem); margin-block: 1.25rem; }
}
/*  The page's column is let out to the window's width for these screens,
    so the panel really does reach the edges rather than a reading column.  */
body:has(.auth) > main > .wrap { width: 100%; max-width: none; }

/*  ═══ THE FORM CARD, CLEANED ═══

    No rules across it: the two hairlines above and below the button cut a
    small card into three slices. One rhythm from the heading to the
    copyright; the labels back to a quiet caption; the browser's autofill
    blue painted over so a remembered name does not arrive in a colour the
    palette never chose; the foot lines gathered close and light.  */
.auth-form { --row: .8rem; gap: var(--row); }
.auth-form h1 { font-size: 1.45rem; margin-bottom: .1rem; }
.auth-form > .muted { margin: -.55rem 0 .2rem; font-size: .875rem; color: var(--ink-soft); }
.auth-form .field { gap: .3rem; }
.auth-form .field > label { font-size: .6875rem; font-weight: 700; letter-spacing: .06em; color: var(--ink-soft); }
.auth-form .in-ico > input {
    background: var(--raised);
    border-color: var(--line-strong);
}
.auth-form .in-ico > input::placeholder { color: var(--ink-faint); }
.auth-form .in-ico > input:hover { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
/*  Autofill: the field keeps its own white and its own ink.  */
.auth-form input:-webkit-autofill,
.auth-form input:-webkit-autofill:hover,
.auth-form input:-webkit-autofill:focus {
    -webkit-box-shadow: 0 0 0 1000px var(--raised) inset;
    -webkit-text-fill-color: var(--ink);
    caret-color: var(--ink);
    transition: background-color 9999s ease-out 0s;
}
.auth-form .form-foot { margin-top: .35rem; padding-top: 0; border-top: 0; }
.auth-form .auth-switch { padding-top: .1rem; border-top: 0; font-size: .8125rem; }
.auth-form .auth-back { margin-top: -.15rem; padding: .2rem .65rem .2rem .4rem; font-size: .78rem; }
.auth-form .auth-copy { margin-top: .1rem; }

/*  ═══ ONE GROUND, NOT TWO HALVES ═══

    The tint used to stop dead at the column line, which read as two pages
    set side by side. The washes now belong to the whole panel: the mart's
    colour lifts out of the top-left, pools low behind the picture, and
    thins across the width until the form's side is the page's own ground -
    a gradient, not an edge - so the card floats on one surface.  */
.auth-panel {
    background:
        radial-gradient(70% 60% at 10% 0%,
            color-mix(in srgb, var(--accent) 16%, transparent), transparent 70%),
        radial-gradient(60% 65% at 40% 100%,
            color-mix(in srgb, var(--accent) 18%, transparent), transparent 72%),
        radial-gradient(45% 55% at 100% 20%,
            color-mix(in srgb, var(--accent) 8%, transparent), transparent 70%),
        linear-gradient(100deg,
            var(--accent-soft) 0%,
            color-mix(in srgb, var(--accent-soft) 55%, var(--ground)) 52%,
            var(--ground) 78%);
}
.auth-aside { background: transparent; }
/*  The picture dissolves before it reaches the column line as well as
    before it reaches the words: a second fade runs in from the right, so
    there is no hard edge of photograph down the middle of the page - the
    other thing that made the halves read as two.  */
.auth-aside .auth-art {
    width: 118%;
    margin: auto -14% -10% -6%;
    -webkit-mask-image:
        radial-gradient(110% 105% at 60% 85%, #000 40%, rgb(0 0 0 / .55) 64%, transparent 84%),
        linear-gradient(90deg, #000 62%, transparent 96%);
            mask-image:
        radial-gradient(110% 105% at 60% 85%, #000 40%, rgb(0 0 0 / .55) 64%, transparent 84%),
        linear-gradient(90deg, #000 62%, transparent 96%);
    -webkit-mask-composite: source-in;
            mask-composite: intersect;
}

/*  ═══ THE REGISTER DOOR FITS THE WINDOW ═══

    Five fields, one under the other as they were; what gives is the air.
    For the form that has five fields (and only that one) the rows sit
    closer, the inputs are a touch shorter, the heading and the foot lines
    smaller - and the card stays inside a laptop window without a scroll.
    Same fields, same names, same posting.  */
.auth-form:has(.field:nth-of-type(5)) { --row: .5rem; margin-block: .75rem; padding: clamp(1rem, 1.8vw, 1.3rem) clamp(1.2rem, 2vw, 1.6rem); }
.auth-form:has(.field:nth-of-type(5)) h1 { font-size: 1.3rem; }
.auth-form:has(.field:nth-of-type(5)) > .muted { margin-top: -.4rem; font-size: .8125rem; }
.auth-form:has(.field:nth-of-type(5)) .field { gap: .2rem; }
.auth-form:has(.field:nth-of-type(5)) .field > label { font-size: .65rem; }
.auth-form:has(.field:nth-of-type(5)) .in-ico > input { min-height: 2.3rem; font-size: .9rem; }
.auth-form:has(.field:nth-of-type(5)) .in-ico > .ico { width: 1.55rem; height: 1.55rem; padding: .35rem; }
.auth-form:has(.field:nth-of-type(5)) .form-foot { margin-top: .2rem; }
.auth-form:has(.field:nth-of-type(5)) .btn-primary { min-height: 2.5rem; font-size: .95rem; }
.auth-form:has(.field:nth-of-type(5)) .auth-switch { font-size: .78rem; padding-top: 0; }
.auth-form:has(.field:nth-of-type(5)) .auth-back { margin-top: -.3rem; }
.auth-form:has(.field:nth-of-type(5)) .auth-copy { margin-top: -.1rem; }

/*  ═══ THE THEME MENU, COMPACT ═══

    The dropdown was a panel: two wide tiles a row, a paragraph of note, a
    labelled room picker with a rule above it. It is a menu now - three
    small tiles a row, a swatch strip and a name each (the mood and the
    dark-first tag stay, small), the note gone, the room picker on one line
    tight under the tiles. About half the height, and no wider than it needs.  */
.themebox-pop {
    width: min(21rem, calc(100vw - 2rem));
    padding: .6rem;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg), 0 0 0 1px color-mix(in srgb, var(--accent) 18%, transparent);
}
.themebox-pop .theme-pick { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .35rem; }
.themebox-pop .theme-opt { padding: .35rem; gap: .2rem; border-radius: var(--radius-sm); }
.themebox-pop .theme-opt .swatch { height: 1.1rem; }
.themebox-pop .theme-opt .nm { font-size: .72rem; }
.themebox-pop .theme-opt .md { font-size: .6rem; line-height: 1.15; }
.themebox-pop .theme-opt .dk { font-size: .55rem; }
.themebox-pop .theme-opt:hover { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.themebox-pop .theme-note { display: none; }
.themebox-pop .form-foot { margin-top: .5rem; padding-top: .5rem; border-top: 1px solid var(--line); }
.themebox-pop .form-foot > div { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.themebox-pop .form-foot .muted { margin: 0 !important; font-size: .7rem !important; white-space: nowrap; }
.themebox-pop .mode-pick { margin: 0; }
.themebox-pop .mode-pick button { font-size: .7rem; padding: .2rem .55rem; }
/*  A head on the menu with its own close: a <details> stays open until it
    is told otherwise, and a menu nobody can shut without picking something
    is a trap. Script also shuts it on a click outside or on Esc.  */
.themebox-head { display: flex; align-items: center; justify-content: space-between; margin: -.1rem 0 .45rem; }
.themebox-head > span { font-size: .6875rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-soft); }
.themebox-x {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.6rem; height: 1.6rem; padding: 0;
    border: 1px solid var(--line); border-radius: 50%;
    background: var(--raised); color: var(--ink-soft); cursor: pointer;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.themebox-x:hover { background: var(--bad-soft); color: var(--bad); border-color: var(--bad); }
.themebox-x .ico { width: .85rem; height: .85rem; }
/*  The two foot links, plain: no pill, no border - an accent word that
    underlines under the pointer, as a link in a sentence should.  */
.auth-form .auth-switch a,
.auth-form .auth-back {
    border: 0; background: transparent; padding: 0; margin: 0;
    border-radius: 0; box-shadow: none;
    color: var(--accent); font-weight: 600;
}
.auth-form .auth-switch a { margin-left: .25rem; }
.auth-form .auth-back { font-size: .8125rem; gap: .3rem; margin-top: -.2rem; }
.auth-form .auth-switch a:hover,
.auth-form .auth-back:hover { background: transparent; color: var(--accent); text-decoration: underline; text-underline-offset: .2em; }

/*  ═══ THE NEWER ROWS ON A SMALL SCREEN ═══

    Everything added in the redesign - the one-row filters, the tab rows,
    the tool rows, the pager in a card head, the preference rows - was laid
    out as a flex or grid row. Rows wrap; this is where they are told to,
    and what they become on a phone. The tables were already inside a
    scroller and slide sideways; the twelve-column forms already fold to six
    and then to one. Nothing is hidden - it stacks.  */

/*  Tool rows wrap; the tab strip scrolls sideways rather than breaking its
    pills on to two lines.

    THE TAB ROW IS THE EXCEPTION AND NO LONGER WRAPS. It used to, and the
    result was the masters clipped mid-word on the first line with the page
    size and the add button stranded on a second - which read as a broken
    row rather than a folded one, because half a word is not a fold. It stays
    one line at every width now; what gives way instead is set out beside
    .tabs-row, and the strip's own edges say when there is more.  */
.list-tools { flex-wrap: wrap; }
.tabs { max-width: 100%; overflow-x: auto; scrollbar-width: none; }
.tabs::-webkit-scrollbar { display: none; }
.tab { white-space: nowrap; }

/*  A pager inside a card head drops to its own line when the head is too
    narrow for the caption, the pages and the total together.  */
.card-head > .pager.inline { flex-wrap: wrap; min-width: 0; }

@media (max-width: 62rem) {
    /*  The one-row filters: each box at least a third of the row, the
        button row full width so it never sits alone in a corner.  */
    .filter-form .fields.g12 .field,
    .row-form .fields.g12 .field { flex-basis: calc(33.333% - .6rem); }
    .filter-form .fields.g12 .field.c6 { flex-basis: calc(66.666% - .6rem); }

    /*  The preference rows stack their label over their control.  */
    .pref-group .spec-row { grid-template-columns: minmax(0, 1fr); gap: .25rem; }

    /*  The huge popup (the item form in a frame) takes the window.  */
    dialog.pop.huge { width: calc(100vw - 1rem); height: calc(100vh - 1rem); }
}

@media (max-width: 48rem) {
    .filter-form .fields.g12 .field,
    .row-form .fields.g12 .field { flex-basis: calc(50% - .6rem); }
    .filter-form .fields.g12 .field.c6,
    .filter-form .fields.g12 .field.actions,
    .filter-form .fields.g12 .field.checks,
    .row-form .fields.g12 .field.actions { flex-basis: 100%; }
    .filter-form .fields.g12 .field.actions .btn,
    .row-form .fields.g12 .field.actions .btn { flex: 1 1 auto; justify-content: center; }

    /*  The pager takes the whole head row under the caption.  */
    .card-head > .pager.inline { flex-basis: 100%; margin-left: 0; }

    /*  Tab rows stay ONE line. The spacer goes because there is no spare room
        left to push with, and the picker and the button hold the right end
        with a margin instead - which costs nothing when space is tight.  */
    .tabs-row .spacer { display: none; }
    .tabs-row > .tool-pick { margin-left: auto; }

    /*  WORDS GO BEFORE THE NAVIGATION DOES. The per-page select is a short
        list of numbers beside a list of rows, and the + is already the shape
        of add - both survive losing their label.  */
    .tabs-row .tool-pick > span { display: none; }
    .tabs-row .btn.btn-primary > span { display: none; }
    .tabs-row .btn.btn-primary { padding-inline: .6rem; }

    /*  AND THE FIVE PILLS BECOME ONE PICKER. The labels themselves cannot be
        dropped - "Groups and GST" IS the tab, and an icon alone puts the
        operator back to guessing - so the row changes shape instead of
        squeezing. See _MasterTabs.  */
    .tabs-row .tabs { display: none; }
    .tabs-pick { display: block; flex: 1 1 auto; min-width: 0; }
    .tabs-pick select { width: 100%; }

    /*  KPI cards two abreast, the figure a step smaller.  */
    .kpis.kpis-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .kpi-num { font-size: 1.25rem; }

    /*  The master add form: boxes two to a row, the Add button its own row.  */
    .master-form .fields.compact .field { flex-basis: calc(50% - .6rem); }
    .master-form .fields.compact .field.field-btn { flex-basis: 100%; }
    .master-form .fields.compact .field.field-btn .btn { width: 100%; justify-content: center; }
}

@media (max-width: 34rem) {
    .filter-form .fields.g12 .field,
    .row-form .fields.g12 .field,
    .master-form .fields.compact .field { flex-basis: 100%; }
    .kpis.kpis-row { grid-template-columns: minmax(0, 1fr); }

    /*  The card heads' captions and their right-hand notes stack.  */
    .card.cap-head .card-head, .card.filter-form .card-head, .card.list-card .card-head { flex-wrap: wrap; }

    /*  Pick chips and the switch stay tappable.  */
    .chip.pick { padding: .45rem .9rem .45rem .75rem; }
}

/* ═════════════════════════════════════════════ a save in flight ══ */
/*
    THE BUTTON SAYS IT HEARD. A page here can take a second or two to come
    back, and a second of nothing looks like a keypress that missed - so the
    operator presses Enter again, the second submission aborts the first, and
    the save is lost with a message about a severe database error. See
    oneSubmitOnly in site.js.

    The flag is what stops the second press; this is what stops them WANTING
    to press it. Both are needed: a guard nobody can see still feels broken.

    Not [disabled], deliberately - see the note in site.js about losing which
    button was pressed. The pointer-events line is what makes the click go
    nowhere while it still submits its own value.
*/
.btn.is-sending,
[type=submit].is-sending {
    pointer-events: none;
    opacity: .72;
    cursor: progress;
}

/*  A thread of the button's own text colour, travelling. Cheap, and it says
    "working" without a spinner image to load at the moment the network is
    already busy.  */
.btn.is-sending::after,
[type=submit].is-sending::after {
    content: "";
    width: .85em;
    height: .85em;
    margin-inline-start: .5em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: kcm-spin .6s linear infinite;
}

@keyframes kcm-spin { to { transform: rotate(360deg); } }

/*  Somebody who has asked for less motion gets the dimming and the cursor,
    which already say enough, without anything turning.  */
@media (prefers-reduced-motion: reduce) {
    .btn.is-sending::after,
    [type=submit].is-sending::after { animation: none; }
}

/*  ═══ THE ROUND BADGE IS SHOWN PLAIN ═══

    The passing light above was made for the old wordmark, where only the
    letters caught it. The KeyMart Pro badge is a solid white disc, so the
    same band washed over the whole badge and faded the name and tagline -
    the owner asked for the logo to be clearly visible. The light is off; a
    soft shadow lifts the white disc off the light header instead.  */
.brandmark::after, .menu .rail-brand::after { display: none; }
.brandmark img, .menu .rail-brand img {
    filter: drop-shadow(0 1px 2px rgb(0 0 0 / .20)) drop-shadow(0 0 1px rgb(0 0 0 / .12));
}
.brandmark:hover img {
    filter: drop-shadow(0 2px 5px color-mix(in srgb, var(--accent) 40%, transparent))
            drop-shadow(0 0 1px rgb(0 0 0 / .15));
}
