/* HOUSE OVERRIDES ON TABLER, and nothing else.

   Tabler owns the look now -- the palette in both themes, the type scale off
   the one root size base.html renders, the cards, buttons, alerts, badges,
   tables, forms and empty states. What is left here is what the house needs
   and Tabler cannot express: the centred table rule (Chad 2026-08-13), the
   write-screen idioms built for a gloved thumb, the swatch tiles, the camera
   overlay, and the classes the screens' own JavaScript hooks by name.

   Every rule Tabler now provides was DELETED, not left dead: the top bar, the
   brand palette, the tile grid, the chips, the pull-sheet tabs, the message
   boxes, the results cards, the pager links, the buttons, the form controls,
   the badge colours, the hand-drawn toggle switch and the zebra striping are
   all Tabler's now.

   Anything below that looks like a taste decision is not one -- it is the
   behaviour that was already there, kept. New taste is Chad's, against live
   pages. Colours are named through Tabler's own variables so both themes
   answer, rather than through the hex codes that used to live here. */

@font-face {
    font-family: "Hanken Grotesk";
    /* Relative so ManifestStaticFilesStorage can rewrite it to the hashed name. */
    src: url("../HankenGrotesk.c501f578bf3f.woff2") format("woff2");
    font-weight: 100 900;
    font-display: swap;
}

/* The company face, handed to Tabler in its own variable rather than in a
   rule that fights it: the two apps read as one company and the type SCALE
   stays Tabler's. */
:root {
    --tblr-font-sans-serif: "Hanken Grotesk", -apple-system, BlinkMacSystemFont,
                            "Segoe UI", sans-serif;
}

/* Data-table pages opt out of the reading-width cap: a wide table should use
   the monitor it is on rather than sit in a column with a scrollbar. */
.page-wrapper.wide .container-xl { max-width: none; }

/* The light/dark switch shows the theme it will GIVE you. In CSS, not in
   JavaScript, so the right glyph is painted on the first frame like the
   theme itself. */
.gf-when-dark { display: none; }
[data-bs-theme="dark"] .gf-when-light { display: none; }
[data-bs-theme="dark"] .gf-when-dark { display: inline-block; }

/* --- home tiles and the Tools cards ------------------------------------- */
/* The emoji is the navigation registry's own; block so the title sits under
   it, which is how the tile has always read.

   NAMED .glyph, NOT .icon: Tabler owns .icon and sizes it 1.25rem square, so
   a ~30px emoji in one is clipped -- on the home page, which is the first
   screen anybody sees. tests/arch/test_class_collisions.py is the gate. */
.tile .glyph { display: block; font-size: 1.9rem; line-height: 1; margin-bottom: .45rem; }
.tile.soon { opacity: .45; pointer-events: none; }
.tile.soon::after {
    content: "coming soon";
    display: block; font-size: .68rem; font-weight: 600;
    color: var(--tblr-secondary);
}
.toolcard .glyph { font-size: 1.8rem; line-height: 1; }
.toolcard .body { display: flex; flex-direction: column; gap: .25rem; min-width: 0; }
.toolcard .foot { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .3rem; }
.toolcard.soon { opacity: .5; }
.lede { max-width: 46rem; line-height: 1.5; }

/* --- login -------------------------------------------------------------- */
.login-wrap {
    min-height: 100dvh;
    display: flex; align-items: center; justify-content: center;
    padding: 1rem;
}
.login-card { max-width: 360px; width: 100%; }
.login-card img { height: 44px; margin-bottom: 1.1rem; }

/* --- tables ------------------------------------------------------------- */
/* ALIGNMENT RULE (Chad 2026-08-13), and it is a DEFAULT, not an opt-in:
   columns here are wide, so a left header sitting over right-aligned values
   reads as two different columns. Everything is CENTRED -- header and value
   together, so the value always sits under its own title. The ONLY things
   left-aligned are the genuinely readable long text columns, which mark
   themselves .col-name (wrapping, min-width) or .col-text (free text that is
   read, not scanned: notes, details, addresses). Short labels centre with
   everything else. Do not re-introduce a per-page opt-in; this lives here
   once.

   NOT an override of Tabler, which is what an earlier version of this
   comment claimed. Checked: tabler.min.css right-aligns nothing; its only
   cell-level text-align is Bootstrap's reboot th{text-align:inherit}, which
   table.data th beats on specificity, and .table-center exists only as a
   per-table opt-in with no per-column exceptions. The house rule makes
   centring the default and lets .col-name/.col-text opt out. (The spec's "numbers right-aligned is
   Tabler's convention" is about its documentation, not about a rule in the
   sheet; the sheet has none.) .num keeps only the tabular figures it was
   really for. */
table.data th, table.data td { text-align: center; white-space: nowrap; }
table.data th.col-name, table.data td.col-name,
table.data th.col-text, table.data td.col-text { text-align: left; }
table.data td.num { font-variant-numeric: tabular-nums; }

/* 1px column rules on EVERY table (Chad: easier to see which column you are
   in). The last visible column drops its rule so there is no edge line
   against the card border. */
table.data th, table.data td { border-right: 1px solid var(--tblr-border-color); }
table.data th:last-child, table.data td:last-child { border-right: none; }

/* A real frozen header needs the SCROLL to happen inside the table's box,
   not the whole page. So the stock table gets its own scroll region with a
   cap; the header pins to the top of it as the rows scroll under. */
.stockscroll { max-height: calc(100vh - 16rem); overflow: auto; }
.stockscroll table.data thead th {
    position: sticky; top: 0; z-index: 3;
    background: var(--tblr-bg-surface);
}

/* Product names are the one column allowed to wrap: they are long, and
   nowrap on them is what forced the whole table sideways. */
table.data th.col-name, table.data td.col-name { white-space: normal; min-width: 14rem; }
/* On a phone or a narrow tablet, drop the two columns that are filters
   anyway -- manufacturer and type are both in the toolbar above the table. */
table.data th .sm { display: none; }
@media (max-width: 780px) {
    table.data th.col-drop, table.data td.col-drop { display: none; }
    table.data th.col-name, table.data td.col-name { min-width: 0; }
}
/* Phones: the three balances are the whole point, so the unit column goes
   and the headings shorten, which hands the width back to the product name. */
@media (max-width: 520px) {
    table.data th.col-unit, table.data td.col-unit { display: none; }
    table.data th .lg { display: none; }
    table.data th .sm { display: inline; }
}

/* Short of stock. Toggled by stock_edit.js as an on-hand number is corrected,
   so it is a class and not a utility written into the markup. */
.neg { color: var(--tblr-danger); font-weight: 700; }

/* Inline On-Hand edit: the number reads as tappable, and clicking swaps it
   for a small type-and-confirm editor (stock_edit.js posts the delta as a
   ledger adjustment). */
td.oh.editable { cursor: pointer; }
/* The dashed underline is the ONLY thing that says this number is
   tappable -- cursor:pointer says nothing on a warehouse tablet. It was
   written against --tblr-border-color-active, which Tabler 1.4.0 does
   not define, so the whole declaration unset and the affordance was
   simply gone. tests/arch/test_custom_properties_resolve.py is the gate. */
td.oh.editable .ohval { border-bottom: 1px dashed var(--tblr-border-color); padding-bottom: 1px; }
td.oh.saved { background: var(--tblr-green-lt); transition: background .2s; }
td.oh .ohedit { display: inline-flex; gap: .25rem; align-items: center; justify-content: center; }
td.oh .ohedit input { width: 4.2rem; text-align: center; font: inherit; padding: .1rem .2rem; }
td.oh .ohedit input.bad { outline: 2px solid var(--tblr-danger); }
td.oh .ohok, td.oh .ohcancel {
    border: 0; cursor: pointer; border-radius: 6px; padding: .12rem .4rem;
    font-size: .85rem; line-height: 1;
}
td.oh .ohok { background: var(--tblr-primary); color: #fff; }
td.oh .ohcancel { background: var(--tblr-bg-surface-secondary); }

/* Rows the autosave scripts paint: conversions.js, trimlengths.js and
   stocktracking.js all speak this vocabulary. */
table.data tr.rowsaving td { opacity: .55; }
table.data tr.rowsaved td { background: var(--tblr-green-lt); transition: background .2s; }
table.data tr.rowerror input { border-color: var(--tblr-danger); outline: 1px solid var(--tblr-danger); }
table.data tr.blank, table.data tr.varies, table.data.trackgrid tr.assumed {
    background: var(--tblr-bg-surface-secondary);
}
table.data.trackgrid tr.rowsaved { background: var(--tblr-green-lt); transition: background .2s; }
table.data.trackgrid tr.rowerror { background: var(--tblr-red-lt); }
table.data td.mono { font-family: var(--tblr-font-monospace); font-size: .8rem; }
/* Derived, not entered: sq yd is computed from the box figure beside it.
   stock.html's equivalent columns were migrated to text-secondary in the
   markup; these two screens were missed, which is what made it a bug
   rather than a decision. */
table.data td.sqyd { color: var(--tblr-secondary); }
table.data .varies { font-size: .78rem; color: var(--tblr-warning); font-weight: 700; }
/* Our figure and the sheet do not agree (brain note 202). The colour IS
   the message: this row is on the fix-list, and the word "differs" beside it
   only names what the amber already said. Same amber as .varies on purpose --
   both mean "two numbers claim to be the same fact". */
table.data .differs {
    font-size: .78rem; color: var(--tblr-warning); font-weight: 700;
    margin-left: .35rem;
}
table.data tr.disagrees td { background: var(--tblr-yellow-lt); }
/* Sheet says: the sheet's column, quieter than the box beside it that we type
   into -- reading it is the job, editing it is not possible here. */
table.data td.sheetsays { color: var(--tblr-secondary); }
.fillstat .differs { background: var(--tblr-yellow-lt); }
/* The child colour rows under an expanded style. The tint is what makes
   them read as children of the row above rather than as more styles. */
table.data tr.colourrow { background: var(--tblr-bg-surface-secondary); }
table.data tr.colourrow td { border-top: none; padding-top: .15rem; padding-bottom: .15rem; }
table.data td.colour { padding-left: 2.4rem; }
table.data td.col-name { position: relative; }
table.data input[type=number] { width: 6.5rem; text-align: right; }
button.expand[aria-expanded="true"] { background: var(--tblr-bg-surface-secondary); }
button.expand {
    border: 1px solid var(--tblr-border-color); background: var(--tblr-bg-surface);
    border-radius: 6px; width: 1.6rem; height: 1.6rem; line-height: 1;
    margin-right: .4rem; cursor: pointer; font-size: .9rem;
}

/* Count list mode: one row per product, a quantity box on each. Sized for a
   thumb on a tablet walking a shelf, not a mouse. */
table.countlist td { white-space: normal; }
table.countlist tr.done td { background: var(--tblr-green-lt); }
table.countlist input[type=number] {
    width: 6.5rem; padding: .5rem .6rem; font-size: 1.05rem; font-weight: 700;
    text-align: right;
}
/* The unit beside a quantity box: what the number will be read as. It is the
   sentence the count screen was missing, so it is never smaller than the
   label text around it, and the cycle button is a thumb target rather than a
   chevron on a select. */
.qtybox { display: flex; align-items: center; gap: .4rem; }
table.countlist .qtybox { justify-content: flex-end; }
.unitlabel, .unitbtn { font-size: .95rem; white-space: nowrap; }
.unitlabel { color: var(--tblr-secondary); }
.unitbtn {
    border: 1px solid var(--tblr-border-color); background: var(--tblr-bg-surface);
    border-radius: 6px; padding: .35rem .6rem; cursor: pointer; font-weight: 600;
}
.unitpicker select.unitpick { width: auto; display: inline-block; }
.unitcell { min-height: 1.2rem; }

.pill {
    display: inline-block; margin-left: .4rem; padding: .1rem .45rem;
    border-radius: 999px; background: var(--tblr-primary); color: #fff;
    font-size: .68rem; font-weight: 700; text-transform: uppercase;
    vertical-align: middle;
}   /* .pill, not .tag: Tabler's .tag carries a border and a fixed 1.5rem
       height, which this is not. */

/* --- write screens (Receive / Pick Up / Counts / Transfers) ------------- */
/* The big form is the one place the house overrules Tabler's sizing: these
   boxes are typed into with a glove on, standing up. */
.bigform { display: flex; flex-direction: column; gap: .6rem; }
/* A checkbox and its label, which butt together without this. NOT scoped to
   .bigform the way base scoped it: stock.html's filter checkbox carries the
   same class and never got the layout at all, so the unscoped rule fixes a
   site that was broken before this branch as well as the one it broke.

   Tabler's .form-check was the other option and would also work -- an earlier
   version of this comment rejected it for setting opacity:.7 on the label,
   which is FALSE and was written from memory. Checked in tabler.min.css: the
   bare rule is `.form-check-label{display:block}`, and the only opacity is on
   `.form-check-input:disabled~.form-check-label`, gated on :disabled.
   stock_tracking.html already ships form-check-label on an enabled label and
   it is not dimmed. The real reasons for the rule are that it is what base
   did, that it is one line here instead of markup edits on two screens, and
   that unscoping it fixes a third site nobody had noticed. FOR CHAD. */
.checkline { display: flex; gap: .5rem; align-items: center; font-size: .9rem; }

.bigform input[type=number], .bigform input[type=text], .bigform select {
    font-size: 1.35rem; padding: .7rem .8rem;
}
.qtyrows { display: flex; flex-direction: column; gap: .45rem; }
.qtyrow {
    display: flex; align-items: center; justify-content: space-between; gap: .8rem;
    background: var(--tblr-bg-surface-secondary); border-radius: 10px; padding: .5rem .7rem;
}
.qtyrow span { font-weight: 700; display: flex; align-items: center; }
.qtyrow em { font-style: normal; font-size: .78rem; margin-left: .3rem; }
.qtyrow input { max-width: 7.5rem; text-align: right; }
.qtyrow .unit { font-size: .8rem; font-weight: 700; margin-left: auto; white-space: nowrap; }
.qtyrow .unit + input { max-width: 6.5rem; }
.qtyrow .rowdel {
    font: inherit; font-weight: 800; color: var(--tblr-danger);
    background: none; border: 0; padding: .1rem .35rem; font-size: 1rem;
}
/* Over-entry warning: more than the ledger says is on the shelf. Warns,
   never blocks -- recording reality beats refusing it (_qty_guard.html). */
.qtyrow .overnote { display: none; }
.qtyrow.over { background: var(--tblr-red-lt); outline: 2px solid var(--tblr-danger); }
.qtyrow.over input { border-color: var(--tblr-danger); color: var(--tblr-danger); font-weight: 800; }
.qtyrow.over .overnote {
    display: block; font-style: normal; font-weight: 700;
    color: var(--tblr-danger); font-size: .75rem; white-space: nowrap;
}

/* THE DETAIL LINE, in one place (ethos 71). Three idioms across twenty
   screens put a bold title over a quiet line of context -- a card head, a
   list row, a job row -- and all three greyed that line through these rules.
   Deleting them without a replacement did not break a layout; it just made
   twenty pages state their context in the same weight and size as their
   title, which is the whole distinction the line exists to make. Restored on
   Tabler's secondary, at the sizes base used.

   Sizes differ by 0.02rem between the three because base's did. That is not
   a design; it is what was there, kept, because this pass is a re-skin. */
.stylehead { display: flex; flex-direction: column; gap: .1rem; margin-bottom: .6rem; }
.stylehead strong { font-size: 1.05rem; }
.stylehead span { color: var(--tblr-secondary); font-size: .8rem; }
.results > a span { color: var(--tblr-secondary); font-size: .82rem; }
.jobinfo strong { font-size: 1rem; }
.jobinfo > span { color: var(--tblr-secondary); font-size: .82rem; }
.pscard strong { font-size: 1.05rem; }
.actionrow form { flex: 1; display: flex; }
.actionrow form .btn, .actionrow > .btn { flex: 1; justify-content: center; }
/* Sticky so nobody fills in forty rows and loses them scrolling for a
   button. */
.savebar {
    position: sticky; bottom: 0; z-index: 2;
    display: flex; align-items: center; gap: .8rem; flex-wrap: wrap;
}
.fillstat { display: flex; gap: .5rem; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; }
.fillstat span { font-size: .8rem; font-weight: 700; padding: .25rem .6rem; border-radius: 999px; }
.fillstat .done { background: var(--tblr-green-lt); }
.fillstat .todo { background: var(--tblr-yellow-lt); }
.fillstat .of { color: var(--tblr-secondary); font-weight: 600; }
.convgroup { margin-bottom: 1.4rem; }
.convgroup h2 { font-size: 1rem; display: flex; align-items: baseline; gap: .6rem; }
.convgroup h2 .type {
    font-size: .72rem; font-weight: 800; text-transform: uppercase;
    letter-spacing: .06em;
}
.pager { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; }

/* --- colour chips ------------------------------------------------------- */
/* Tabler DOES have a select-group (.form-selectgroup, .form-selectgroup-item,
   and a :checked state) and it is the right shape for a pick list. What it
   does not do is the swatch tile: an image, a name and a code stacked and
   centred in each item. That layout is what is kept here; the checked state
   mirrors what the select-group does. */
.colorchips {
    display: grid; gap: .45rem;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.colorchip {
    display: flex; flex-direction: column; align-items: center; gap: .1rem;
    padding: .6rem .4rem; border-radius: 10px; text-align: center;
    background: var(--tblr-bg-surface-secondary); border: 1px solid var(--tblr-border-color);
    text-decoration: none; color: inherit; font-weight: 700; font-size: .9rem;
}
.colorchip em { font-style: normal; font-weight: 600; font-size: .72rem; }
/* Tap feedback. Restored rather than dropped: Tabler carries :active
   surfaces of its own -- .btn, .dropdown-item:active,
   .list-group-item-action:not(.active):active, the range thumb -- so a
   tappable chip having one is its convention, not an invention. */
.colorchip:active { background: var(--tblr-bg-surface-secondary); }
.colorchip.pick { cursor: pointer; }
.colorchip.pick input { position: absolute; opacity: 0; pointer-events: none; }
.colorchip.pick:has(input:checked) {
    background: var(--tblr-primary); border-color: var(--tblr-primary); color: #fff;
}
.vipchip { display: inline-flex; align-items: center; gap: .35rem; cursor: pointer; }
.vipchip input { position: absolute; opacity: 0; pointer-events: none; }

/* Product swatch thumbnails -- fetched by fetch_swatches, shown wherever a
   product is named. A missing file simply renders nothing. */
img.swatch {
    width: 2.1rem; height: 2.1rem; object-fit: cover;
    border-radius: .45rem; border: 1px solid var(--tblr-border-color);
    vertical-align: middle; margin-right: .45rem; flex-shrink: 0;
}
.colorchip img.swatch { width: 2.6rem; height: 2.6rem; margin-right: .15rem; }
table.data img.swatch { width: 1.7rem; height: 1.7rem; }

/* --- Match Review cards ------------------------------------------------- */
/* matchreview.js paints every one of these class names. */
.matchcard {
    background: var(--tblr-bg-surface); border: 1px solid var(--tblr-border-color);
    border-radius: var(--tblr-border-radius); padding: .7rem .85rem; margin-bottom: .6rem;
    transition: opacity .3s, height .3s, padding .3s, margin .3s; overflow: hidden;
}
.matchcard.busy { opacity: .5; }
.matchcard.done { background: var(--tblr-green-lt); }
.matchcard.bad { border-color: var(--tblr-danger); }
/* Named mc-collapse, not collapse: Bootstrap owns .collapse and would hide
   the card outright instead of letting it fold away. */
.matchcard.mc-collapse { height: 0 !important; padding-top: 0; padding-bottom: 0; margin: 0; opacity: 0; }
/* The whole-queue search box, above the cards. Named so matchreview.js can
   find it without claiming every .filters box on the site, and widened
   because what gets typed into it is a product name, not a code. */
.filters .mcq { max-width: 26rem; }
.mc-head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; margin-bottom: .5rem; }
.mc-raw { font-weight: 600; }
.mc-seen { font-size: .8rem; white-space: nowrap; }
/* Written by matchreview.js the moment a match is confirmed -- it is the
   only thing that says the click landed, so it has to read as a yes. */
.mc-ok { color: var(--tblr-green); font-weight: 600; font-size: .85rem; }
.mc-actions { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.mc-pick, .mc-result, .mc-service {
    border: 1px solid var(--tblr-border-color); background: var(--tblr-bg-surface-secondary);
    border-radius: 8px; padding: .35rem .6rem; cursor: pointer; font: inherit;
    font-size: .85rem; text-align: left;
}
.mc-pick:hover, .mc-result:hover { background: var(--tblr-green-lt); }
.mc-pick.confident { background: var(--tblr-primary); color: #fff; border-color: var(--tblr-primary); }
.mc-service { border-style: dashed; background: transparent; }
/* ARMED, not confirmed: matchreview.js paints this on the first tap and the
   second one writes. The colour IS the warning -- the label says "tap again
   to confirm" beside it, and an armed button that looked like its neighbours
   would be the accidental bind this two-tap rule exists to stop. It has to
   beat .confident, which is why it is last. */
.mc-armed, .mc-pick.confident.mc-armed {
    background: var(--tblr-orange-lt); color: var(--tblr-orange);
    border-color: var(--tblr-orange); font-weight: 600;
}
.mc-search { margin-top: .5rem; }
.mc-search input { width: 100%; max-width: 22rem; padding: .35rem .5rem; border: 1px solid var(--tblr-border-color); border-radius: 8px; font: inherit; }
.mc-results { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .4rem; }
/* The create-a-new-item form. Folded away by default: it is the third and
   rarest answer, and an open form on every card would bury the two taps that
   clear most of the queue. No script paints these -- the form is an ordinary
   POST, so the screen keeps working with JavaScript off. */
.mc-create { margin-top: .5rem; font-size: .85rem; }
/* The summary wears .btn itself, so its colour, padding and cursor come from
   Tabler and this rule only takes the disclosure triangle away -- .btn makes
   the element a flex box and the marker would otherwise sit inside it as a
   stray glyph. Declaring a colour here would beat .btn-outline-primary's on
   specificity and the button would lose its hover state. */
.mc-create > summary { list-style: none; }
.mc-create > summary::-webkit-details-marker { display: none; }
.mc-fields { display: flex; flex-wrap: wrap; gap: .5rem; margin: .5rem 0; }
.mc-fields label { display: flex; flex-direction: column; gap: .2rem; min-width: 11rem; flex: 1 1 11rem; }

/* --- camera QR scanner -------------------------------------------------- */
/* Built by scanner.js at scan time, so these names are its contract. */
.scan-overlay {
    position: fixed; inset: 0; z-index: 200; background: #000;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.scan-overlay video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.scan-frame {
    position: relative; width: min(62vw, 320px); aspect-ratio: 1;
    border: 3px solid var(--tblr-primary); border-radius: 18px;
    box-shadow: 0 0 0 100vmax rgba(0, 0, 0, .45);
}
.scan-status {
    position: relative; color: #fff; font-weight: 600;
    text-shadow: 0 1px 4px rgba(0, 0, 0, .8); margin: 1.2rem 0 0;
    padding: 0 1rem; text-align: center;
}
.scan-cancel {
    position: relative; margin-top: 1.2rem;
    background: rgba(255, 255, 255, .92); color: #000; border: none;
    border-radius: var(--tblr-border-radius); padding: .7rem 2rem;
    font-size: 1rem; font-weight: 600; cursor: pointer;
}

/* --- pull sheets -------------------------------------------------------- */
.pscard { display: flex; flex-direction: column; gap: .3rem; height: 100%; }
.pscard .btn, .pscard form { margin-top: auto; }
/* Colour IS the information on these two, which is why they are pinned by
   tests/arch/test_colour_carries_meaning.py: the date the sheet is FOR reads
   as the answer to "when", and a stray card is one whose job has dropped out
   of the window -- amber is the only thing that says so. */
.pscard .when { color: var(--tblr-green); font-weight: 600; }
.pscard .meta { color: var(--tblr-secondary); font-size: .85rem; }
.pscard .meta.stray { color: var(--tblr-yellow); }
.notebox {
    border: 1px solid var(--tblr-border-color);
    border-left: 4px solid var(--tblr-primary);
    border-radius: var(--tblr-border-radius);
    background: var(--tblr-bg-surface-secondary);
    padding: .7rem .9rem; margin-top: .6rem;
}
.notebox--situation { border-left-color: var(--tblr-warning); }
.notelabel {
    display: block; font-size: .75rem; font-weight: 700;
    letter-spacing: .06em; text-transform: uppercase; margin-bottom: .25rem;
}
.prodhint {
    border: 2px solid var(--tblr-primary); border-radius: var(--tblr-border-radius);
    background: var(--tblr-bg-surface-secondary); padding: .8rem 1rem;
    margin-top: .6rem; font-size: 1.1rem;
}
.prodhint b { display: block; font-size: 1.15rem; margin: .15rem 0; }
.prodhint span { display: block; font-size: .85rem; }

/* YOU ARE HERE (Chad, 2026-08-30). Pure black and pure white are reserved
   for exactly one meaning: the sub-view segment you are on and the sidebar
   entry for the screen you are on, inverted against the theme -- black on
   the light theme, white on the dark one. No other surface in this sheet
   may use --tblr-black or --tblr-white as a background; the test in
   apps/core/test_shell.py holds that line. The strips themselves are
   Tabler's nav-segmented, so the whole bar reads as one control before the
   inversion says which part of it is current -- on a phone the old flat
   tab words were invisible as tabs. */
.nav-segmented .nav-link.active,
.navbar-vertical .navbar-nav .nav-link.active {
    background: var(--tblr-black); color: var(--tblr-white);
    border-color: var(--tblr-black);
}
[data-bs-theme="dark"] .nav-segmented .nav-link.active,
[data-bs-theme="dark"] .navbar-vertical .navbar-nav .nav-link.active {
    background: var(--tblr-white); color: var(--tblr-black);
    border-color: var(--tblr-white);
}
.navbar-vertical .navbar-nav .nav-link.active { border-radius: var(--tblr-border-radius); }

/* SERVICE HISTORY (apps/service). A note or a description is shown with the
   line breaks its writer typed and never runs wider than the card; a photo
   is a thumbnail the size of a thumb, so twenty of them are a strip to scan
   and not a page to scroll. The ticket heading is a native details/summary:
   it opens with no script, and the pointer says it can be pressed. */
.gf-service-writing { white-space: pre-wrap; overflow-wrap: anywhere; }
.gf-service-photo {
    height: 6rem; width: 6rem; object-fit: cover;
    border-radius: var(--tblr-border-radius);
    border: var(--tblr-border-width) solid var(--tblr-border-color);
}
.gf-service-ticket { cursor: pointer; }
