/* PHENOMINAL-METRC — application styles
 *
 * Load order (see resources/views/partials/head.blade.php):
 *   1. Bootstrap 5.3   /vendor/bootstrap-5.3.3/
 *   2. adminkit.css    AdminKit v3.4.0 (MIT) — the admin shell and theme
 *   3. THIS FILE       character only
 *
 * There is no build step: edit, save, refresh.
 *
 * Rule of thumb — Bootstrap and AdminKit do ARRANGEMENT, this file does CHARACTER.
 * Layout (grid, flex, spacing, ordering, responsive visibility) uses Bootstrap
 * utilities; the shell (.wrapper/.sidebar/.main/.content) is AdminKit's. Anything
 * here should be brand colour, typography rhythm, or component-specific visual
 * treatment neither of them has an opinion about.
 */

:root {
    /* Brand — placeholder values, replace when the palette is settled */
    --pm-accent: #1f6f54;
    --pm-accent-hover: #185741;

    /* ⚑ THE MARK'S OWN CHARCOAL, read from cannaflow-mark.svg rather than guessed.
     * The brand README states the production geometry carries "explicit charcoal or white
     * fills" — this is that charcoal, available to CSS so a heading and the logo beside it
     * are the same colour by construction rather than by coincidence. */
    --cf-charcoal: #111111;

    /* Semantic states used across compliance surfaces. These are deliberately
     * distinct from Bootstrap's success/warning/danger, because "compliant",
     * "drifting" and "breached" are domain states, not UI severities. */
    --pm-state-ok: #1f6f54;
    --pm-state-drift: #b8860b;
    --pm-state-breach: #a4262c;
    --pm-state-unknown: #6c757d;

}

/* ---------------------------------------------------------- alpine
 *
 * ⚑⚑ `x-cloak` DOES NOTHING WITHOUT THIS RULE, AND IT WAS MISSING FOR THE WHOLE PROJECT.
 *
 * Alpine strips the attribute once it boots; it never hides anything itself. So six places
 * carrying `x-cloak` — the label-run pause panel, the sorting bench's two derivation arms,
 * the form picker, the table row confirmations — were **rendering their hidden content on
 * every page load and snapping it away** once Alpine initialised.
 *
 * ⚑ It reads as a slow page rather than as a defect, which is why it survived: the flash is
 * ~100ms and the content is correct a moment later. Found 2026-08-12 while adding the seventh
 * `x-cloak`; `grep -rn cloak public/css/` returned nothing.
 *
 * `!important` because it must beat Bootstrap's own `d-flex`/`d-block` display utilities,
 * which several of these elements also carry.
 */

[x-cloak] {
    display: none !important;
}

/* ---------------------------------------------------------- typography */

body {
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    font-size: 0.9375rem;
}

/* ⚑ THE BRAND FACE IS FOR HEADINGS, NOT FOR BODY TEXT — and that restraint is the decision,
 * not a limitation.
 *
 * Garet is a geometric display face; it is the "Flow" half of the wordmark and it carries the
 * identity well at 1.25rem and up. At the 0.9375rem body size this application actually runs —
 * dense tables of tags, quantities and variances that an operator scans for eight hours — a
 * geometric face with a single available weight is measurably worse to read than the system
 * stack, which ships optical sizes, real italics and hinting the browser already trusts.
 *
 * The system stack therefore keeps the working text. This is the same reasoning the file's own
 * header states: character, never arrangement — and legibility of a reconciliation screen is
 * not decoration to be spent on brand.
 *
 * ⚑ `font-display: swap` on every face (see fonts/cannaflow/fonts.css) means a slow or failed
 * font shows the fallback rather than blank headings. The stack after Garet is the same one
 * `body` uses, so a failure degrades to the current appearance exactly.
 *
 * ⚑ LICENSING IS NOT SETTLED. Both READMEs are explicit — the Rozanova webfont kit is a DEMO
 * build and the Garet licence is to be verified before commercial release. Only Garet is
 * referenced here, and only for headings; Rozanova appears solely as outlined paths inside the
 * logo artwork, which is a separate licence question the brand README already flags. */
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
    font-family: "Garet", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    letter-spacing: -0.01em;
}

/* ---------------------------------------------------------- the mark */

/* The sidebar wordmark. Sized on the theme's own brand rhythm rather than a new one;
 * `width: auto` preserves the artwork's 520×76 ratio, and `display: block` drops the inline
 * baseline gap that would otherwise offset the padding AdminKit sets on `.sidebar-brand`. */
.sidebar-brand-mark {
    display: block;
    height: 1.5rem;
    width: auto;
}

/* The horizontal wordmark on the signed-out screens.
 *
 * ⚑ SIZED BY WIDTH, NOT HEIGHT, and that is the difference between this and the sidebar rule
 * above. The artwork is 520×76 — a 6.8:1 ratio — so a height of 4.5rem would render it 490px
 * wide and burst a `col-lg-5` card. Width is the constrained dimension here, so width is what
 * the rule sets; `height: auto` follows.
 *
 * 200px is comfortably above the ~120px floor the brand README sets for switching to the
 * standalone mark. */
.cf-auth-mark {
    display: block;
    width: 200px;
    max-width: 100%;
    height: auto;
    margin-inline: auto;
}

/* Tabular figures for anything a human compares column-to-column — weights,
 * quantities, variances. Misaligned digits make a reconciliation screen
 * genuinely harder to read. */
.pm-numeric {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
}

/* UIDs, tags and licence numbers are scanned character-by-character when
 * someone is checking a discrepancy. */
.pm-uid {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 0.875em;
    letter-spacing: 0.01em;
    word-break: break-all;
}

/* ---------------------------------------------------------- domain state */

.pm-state {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.8125rem;
    font-weight: 600;
}

.pm-state::before {
    content: "";
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: currentColor;
}

.pm-state--ok      { color: var(--pm-state-ok); }
.pm-state--drift   { color: var(--pm-state-drift); }
.pm-state--breach  { color: var(--pm-state-breach); }
.pm-state--unknown { color: var(--pm-state-unknown); }

/* ---------------------------------------------------------- dock / floor UI
 *
 * Screens used standing up, possibly gloved, on a dock or in a grow room.
 * Larger hit targets than Bootstrap's defaults; see the operator reviews.
 */

.pm-touch .btn {
    min-height: 3rem;
    font-size: 1rem;
}

.pm-touch .form-control,
.pm-touch .form-select {
    min-height: 3rem;
    font-size: 1rem;
}

/* ---------------------------------------------------------- the notice region
 *
 * ⚑⚑ DELETED 2026-08-16, TASK #198. `--cf-notice-region-min-height` and the two
 * `.cf-notice-region` rules that lived here are GONE, and this note is what stands in
 * their place so the next person does not re-derive them from the same evidence.
 *
 * ⚑ THE REASONING THAT IS SUPERSEDED, kept rather than edited away: #191 reserved a
 * 3.5rem FLOOR above the form on seven repeat-work boards so a one-line confirmation
 * appearing or clearing cost zero layout shift. That was measured, it was real, and it
 * removed the walker's actual failure.
 *
 * ⚑ WHY IT IS GONE ANYWAY: a floor is not a ceiling. Anything taller than 3.5rem still
 * moved the form — a wrapped banner on a narrow bench screen, a second validation
 * message (measured at -23px), a transmission notice beside a confirmation. And the
 * empty state charged 3.5rem of dead page on every render of all seven boards to buy a
 * partial fix.
 *
 * ⚑ THE REPLACEMENT NEEDS NO CUSTOM CSS AT ALL, which is the point. Bootstrap's own
 * `.toast-container.position-fixed` takes the notices out of document flow, so zero
 * shift is structural rather than measured, at any height, on any viewport. There is
 * nothing left for this stylesheet to do. See components/toast-region.blade.php.
 */

/*
 * ⚑⚑ THE SEMANTIC EDGE ON A TRANSLUCENT TOAST — task: payroll walk 5, 2026-09-07.
 *
 * ⚑ THE LINE DIRECTLY ABOVE — *"needs no custom CSS at all"* — WAS TRUE OF THE OPAQUE
 * TOAST AND IS NOT TRUE OF THE TRANSLUCENT ONE. It is kept rather than edited away, per
 * the house rule on superseded reasoning, because its argument (Bootstrap owns the
 * POSITIONING, and this stylesheet must not re-derive it) still governs everything else
 * here. Only the colouring moved.
 *
 * ⚑ WHY THE COLOUR LEFT THE SURFACE. `text-bg-success` painted an opaque green block,
 * and payroll walk 5 met the consequence: the toast covered the middle of a warning
 * banner naming the one thing the bookkeeper had to do, and *"the part naming the action"*
 * was unreadable underneath it. Bootstrap's `--bs-toast-bg` is 85% alpha by design (the
 * "translucent" example Brandon linked); painting an opaque utility over it throws that
 * away.
 *
 * ⚑⚑⚑ THE CONTEXTUAL BACKGROUND AND THE TRANSLUCENCY COMPOSE — Brandon, 2026-09-07:
 * *"toasts should use the background color of the context not just a left border"*,
 * citing the colour-schemes section of Bootstrap's toast docs.
 *
 * ⚑ SUPERSEDED, AND KEPT RATHER THAN EDITED AWAY: an earlier build here moved the
 * semantics to a `border-left: 3px solid` edge and left the surface neutral, on the
 * argument that `text-bg-success` painted the opaque block which covered the walker's
 * banner. **That was right about the opacity and wrong to blame the colour.**
 *
 * ⚑⚑ THE RESOLUTION IS IN BOOTSTRAP'S OWN COMPILED UTILITY, read rather than assumed:
 *
 *     .text-bg-success { background-color: RGBA(var(--bs-success-rgb), var(--bs-bg-opacity, 1)) }
 *
 * The alpha is a VARIABLE, so setting `--bs-bg-opacity` on the toast gives the contextual
 * colour at whatever translucency we choose. `.85` is not an invented number — it is
 * exactly what Bootstrap's own `--bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85)` uses
 * for the translucent example Brandon linked.
 *
 * ⚑ SO NEITHER REQUIREMENT OVERRIDES THE OTHER, and nothing here re-derives a colour:
 * the hue is the framework's semantic token and the alpha is the framework's own figure.
 *
 * ⚑ THE BLUR IS WHAT MAKES 85% ACTUALLY READABLE over dense content. Without it, text
 * under the toast shows through as legible competing glyphs rather than a soft wash —
 * translucency that makes BOTH layers hard to read is worse than either alone.
 */

.cf-toast {
    --bs-bg-opacity: .85;
    backdrop-filter: blur(3px);
}

/*
 * ⚑⚑ THE SLIDE-IN — Brandon, 2026-09-07: *"they dont animate slide in"*.
 *
 * ⚑ BOOTSTRAP'S `.fade` ANIMATES OPACITY AND NOTHING ELSE — `transition: opacity .15s
 * linear`. There is no slide in the framework, so a toast appearing top-right simply
 * materialises in place. That is what he is describing, and it is stock behaviour rather
 * than a defect in the wiring.
 *
 * ⚑ SO THE TRANSFORM IS ADDED HERE AND COMPOSED WITH BOOTSTRAP'S OPACITY RATHER THAN
 * REPLACING IT.
 *
 * ⚑⚑ THE START STATE IS `.showing`, NOT `:not(.show)`, AND THAT COST A ROUND OF DEBUGGING.
 * Sampling the class list once per animation frame showed Bootstrap adding BOTH classes in
 * the same frame:
 *
 *     frame 1   (no state classes)
 *     frame 2   show showing        ⚑ both at once
 *     frame 8   show                (`showing` removed when the transition ends)
 *
 * `.showing` is the class that holds `opacity: 0` while the transition runs, so it — not
 * the absence of `.show` — is the state to hang the offset on. A `:not(.show)` rule stops
 * matching the instant `.show` lands, which is the same frame it arrived, so the transform
 * jumped to its end value with nothing to animate from. ⚑ `.hiding` is the mirror class on
 * the way out, so it takes the same offset and the toast drifts back out as it fades.
 *
 * ⚑⚑ `translateX`, FROM THE RIGHT EDGE, because that is the corner these live in. A toast
 * that slides in from the direction of its own container reads as arriving; one that slides
 * up or down reads as the page moving.
 *
 * ⚑ THE OPACITY TRANSITION IS RESTATED because declaring `transition` here would otherwise
 * REPLACE Bootstrap's own rule rather than extend it, and the fade would be lost. The
 * duration is left at Bootstrap's .15s for the way IN; the 2s slow fade-out is applied as
 * an inline `transition-duration` at `hide.bs.toast` time (see `toasts.js`) and overrides
 * both properties together, so the toast drifts out as it fades.
 */

.cf-toast.fade {
    transition: opacity .15s linear, transform .15s ease-out;
    transform: translateX(0);
}

.cf-toast.fade.showing,
.cf-toast.fade.hiding {
    transform: translateX(1rem);
}

/*
 * ⚑⚑⚑ THE THEME'S `text-bg-*` FOREGROUNDS ARE CORRECT AND THIS STYLESHEET LEAVES THEM ALONE.
 *
 * ⚑ RECORDED BECAUSE THE OPPOSITE WAS BUILT FIRST AND REMOVED, so the next person does not
 * re-derive it from the same evidence. A pair of rules stood here forcing `#fff` on a success
 * toast and `#000` on a warning one, on the argument that `adminkit.css` redefines
 * `.text-bg-success` with `color: #000 !important`, loads after Bootstrap, and therefore wins
 * on source order — producing black text on green.
 *
 * ⚑⚑ THE MEASUREMENT WAS RIGHT; THE CONCLUSION WAS NOT. Brandon, 2026-09-07: *"we dont need to
 * override adminkit's text-bg-* variants.... those are correct"*. The theme's foregrounds are a
 * DESIGN DECISION, not a contrast bug, and a component that disagrees with the theme is the
 * component that is wrong.
 *
 * ⚑ AND IT WAS THE WORKAROUND SHAPE CLAUDE.md WARNS ABOUT — a call-site rule fighting a global
 * one, scoped narrowly enough to look disciplined. Scoping a disagreement to `.cf-toast` does
 * not make it less of a disagreement; it makes the toast the one place in the product where
 * `text-bg-success` means something different.
 *
 * ⚑ SO THE TOAST TAKES THE THEME'S COLOURS EXACTLY AS EVERY OTHER SURFACE DOES. Only
 * `--bs-bg-opacity` is set above, which is Bootstrap's own documented knob for translucency and
 * changes no foreground at all.
 *
 * ⚑⚑⚑ AND THE ONE FOREGROUND THAT WAS GENUINELY WRONG WAS FIXED AT ITS SOURCE, NOT HERE.
 * Brandon, 2026-09-07: *"if anything we should actually change adminkit to be white on success
 * instead of overriding in one place"*. `adminkit.css`'s `.text-bg-success` now declares
 * `color: #fff`, changed in that file.
 *
 * ⚑⚑ THE THEME'S OWN FAMILY IS WHAT SETTLED IT — `info` (#17a2b8) and `danger` (#dc3545) both
 * carry white, `warning` (#fcb92c) carries black and is right to on amber. Success (#1cbb8c) was
 * the only mid-dark background carrying black, so it was an inconsistency WITHIN the theme rather
 * than a disagreement with Bootstrap. **That is the difference between a design decision to
 * respect and a slip to fix** — and it is why the answer is one line in the theme rather than a
 * rule per component.
 */

/* ---------------------------------------------------------- sync status */

.pm-sync-stale {
    border-left: 3px solid var(--pm-state-drift);
}

.pm-sync-offline {
    border-left: 3px solid var(--pm-state-breach);
}

/* ---------------------------------------------------------- shell character
 *
 * AdminKit supplies the shell's ARRANGEMENT (.wrapper/.sidebar/.main/.content) and
 * its dark palette. What follows is only the handful of marks it has no opinion
 * about. Nothing here positions anything.
 */

/* The group-collapse chevron. Rendered as the `dot` icon (a circle) and rotated
 * into a caret by CSS, so the sidebar needs no second SVG and no icon font. The
 * open/closed class is toggled by Alpine. */
.pm-sidebar-chevron {
    transition: transform .2s ease-in-out;
    opacity: .55;
}

.pm-sidebar-chevron--open {
    transform: rotate(90deg);
}

/* Section headers double as collapse toggles, so they arrive as <button>. Strip
 * the button chrome back to the plain AdminKit header treatment. */
.sidebar-header.btn {
    font-size: .75rem;
    letter-spacing: .04em;
    color: #ced4da;
}

.sidebar-header.btn:hover {
    color: #f8f9fa;
}

/* Initials avatar in the header's user menu. A circle with two letters — no image
 * upload, no gravatar request, no third-party call. */
.pm-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 50%;
    background: var(--pm-accent);
    color: #fff;
    font-size: .75rem;
    font-weight: 600;
    letter-spacing: .02em;
    flex-shrink: 0;
}

/* Mobile sidebar reveal.
 *
 * AdminKit's own `.sidebar.collapsed` cannot be used for this: it ships that
 * selector twice with equal specificity — once unscoped (margin-left: -260px)
 * and once inside `@media (max-width: 991.98px)` (margin-left: 0) — and the
 * unscoped rule is LATER in the file, so it wins in both contexts. Adding the
 * class on a phone therefore leaves the sidebar off-canvas. Verified in-browser:
 * computed margin-left stayed -260px.
 *
 * Our own class inside the same breakpoint has no such collision. It sits in
 * app.css, which loads after adminkit.css, so it also beats the unscoped rule.
 */
@media (max-width: 991.98px) {
    .sidebar.pm-sidebar-open {
        margin-left: 0;
    }
}

/* ---------------------------------------------------------- label binding table
 *
 * ① Label design's binding table has three content columns — the label field, the
 * catalogue field it is bound to, and THE VALUE THAT WILL PRINT. The third is the
 * one the screen exists for, so it must never be the one that gets scrolled out of
 * sight.
 *
 * These are MINIMUM widths for readability, not layout: `.table-responsive` still
 * owns the horizontal scroll, and the row/column arrangement is Bootstrap's. They
 * are declared per-breakpoint because the useful minimum genuinely differs — on a
 * phone a 12rem floor on every column forces 34rem of table into a 390px viewport
 * and pushes the value column off-screen, which is the exact failure the widths
 * were meant to prevent. Measured in-browser at 390px.
 */
.pm-bind-field  { min-width: 8rem; }
.pm-bind-source { min-width: 9rem; }
.pm-bind-value  { min-width: 8rem; }

@media (min-width: 768px) {
    .pm-bind-field  { min-width: 11rem; }
    .pm-bind-source { min-width: 12rem; }
    .pm-bind-value  { min-width: 11rem; }
}

/* ─── The shell must scroll ────────────────────────────────────────────────────────
 *
 * AdminKit's stock shell is built for a fixed-viewport dashboard: `html, body` are
 * pinned to `height: 100%`, `.main` carries `overflow: hidden`, and the theme expects
 * an inner element to own the scroll. This app doesn't have one — every page is a
 * document that grows past the fold, and Today, Reconciliation, Records and the ship
 * gate all routinely run to two or three viewports.
 *
 * With the stock rules in force, NOTHING scrolls at any level. Measured on /today at
 * a 757px viewport: document 2129px, `scrollTo(0, 1500)` leaves `scrollY` at 0, and
 * `.main.scrollTop` is 0 with `overflow-y: visible`. Roughly 1,400px of every long
 * page — including the reconciliation attestation and the lower half of the gate —
 * is simply unreachable. This is the single defect from the design pass rated
 * "blocks the job", and it blocks all of them.
 *
 * The correction is to let the document be a document: `height` becomes a floor
 * rather than a cap, and `.main` stops clipping. `min-height: 100vh` on the shell
 * keeps the sidebar full-bleed on short pages, which is the only thing the original
 * rule was buying.
 *
 * Overridden here rather than in `adminkit.css` — the vendor file stays a pristine
 * v3.4.0 drop so it can be re-pulled without re-applying patches by hand.
 */
html {
    height: auto;
    min-height: 100%;
    overflow-y: scroll;
}

/*
 * `overflow-y` is declared on `html` above and left neutral here. AdminKit puts it on
 * `body`; with `height` no longer pinned, the scrollbar belongs on the element that
 * actually owns the scrollport.
 *
 * ── A note on how this was verified, because it nearly went the other way ──────────
 *
 * `window.scrollY` is NOT a reliable witness under browser automation. Throughout the
 * investigation it read 0 on this page under every mechanism — `scrollTo`, `scrollTop`,
 * `scrollIntoView`, the End key — including AFTER the defect was fixed, and including
 * on runs where a real mouse wheel visibly moved the page 1,300px. A control document
 * at the same origin in the same tab reported correct values, so the reading is
 * situational rather than uniformly broken, which is what makes it dangerous.
 *
 * The defect and the fix were both confirmed by WHEEL EVENTS AND SCREENSHOTS: with
 * these rules the page reaches the foot of Today; with them removed the identical
 * gesture moves nothing. That A/B is the evidence, not any scrollY reading.
 *
 * It also does not reproduce at desktop width — Brandon has never encountered it. It
 * bites at the ~600px viewport the automation profile drives, where the shell collapses
 * to its mobile arrangement and `height: 100%` plus `.main { overflow: hidden }` traps
 * the content. Receiving and Sorting are phone/tablet screens, so that width is a real
 * target, not just a harness artifact.
 */
body {
    height: auto;
    min-height: 100%;
    overflow-y: visible;
}

.wrapper {
    min-height: 100vh;
}

.main {
    overflow: visible;
}

/*
 * THE SHELL HOLDS STILL; ONLY THE CONTENT MOVES.
 *
 * The sidebar and the top bar are chrome — they are the thing an operator navigates WITH,
 * so scrolling a long room board out of reach of its own navigation is the shell behaving
 * like content. Sticky rather than fixed, because fixed would take the sidebar out of flow
 * and the `.main` column would slide underneath it.
 *
 * `overflow: auto` on the sidebar is what makes a long nav usable on a short viewport: the
 * nav scrolls WITHIN its own pinned column instead of pushing the page taller.
 *
 * ── Why this does not undo the mobile-scroll fix above ────────────────────────────
 *
 * That fix moved the scrollport off `body` and onto `html` and stopped `.main` trapping its
 * content, because at ~600px the shell collapses and `height: 100%` plus
 * `.main { overflow: hidden }` made the page unscrollable. Nothing here re-pins `body`,
 * re-hides `.main`, or changes which element owns the scrollport — `position: sticky` needs
 * a scrolling ancestor and takes the one that fix established. The two are compatible, and
 * the mobile case is the one to re-check first if this ever has to be revisited.
 */
.wrapper > nav#sidebar > .sidebar-content {
    position: sticky;
    top: 0;
    overflow: auto;
    height: 100vh;
}

/*
 * THE TOP BAR IS CHROME TOO, and it needs its own rule.
 *
 * Measured before this was added: after scrolling 800px on Today the sidebar held at 0 while
 * the navbar sat at -800 — entirely gone. The sidebar rule cannot pin it, because they are
 * siblings in different columns: the sidebar sticks inside `nav#sidebar`, the navbar sticks
 * inside `.main`.
 *
 * `z-index` above the content so cards, tables and the sticky table headers inside `.content`
 * pass UNDER it rather than through it. Background is inherited from the theme's `.navbar-bg`
 * rather than restated here — a transparent sticky bar shows the content sliding beneath it,
 * which is worse than not pinning it at all.
 */
.wrapper > .main > .navbar {
    position: sticky;
    top: 0;
    z-index: 1020;
}

/* ─── min-w-0 · THE MISSING UTILITY THAT BOOTSTRAP DOES NOT SHIP ───────────────────
 *
 * ⚑ 13 uses across 8 views referenced `.min-w-0` and NOTHING DEFINED IT. The name is
 * Tailwind's; Bootstrap 5.3 has no min-width utility at all. So every one of those was
 * a no-op, and the layouts they were meant to protect had no protection.
 *
 * ⚑ WHAT IT ACTUALLY FIXES, because it is not obvious: a flex item defaults to
 * `min-width: auto`, which refuses to shrink below its CONTENT's intrinsic width. So
 * `text-truncate` on a child cannot work — there is nothing forcing the box narrower,
 * and instead the whole row grows. Reported by Brandon 2026-08-07 on the drying board:
 * a long harvest name pushed the "day 21" badge past the card edge and the page gained
 * horizontal scroll on mobile.
 *
 * `min-width: 0` is the standard release valve — it lets the item shrink so the
 * truncation the markup already asked for can happen.
 *
 * ⚑ KEPT AS A UTILITY, NOT INLINED PER CARD. It is a layout primitive used in 8 places;
 * the Bootstrap-idiomatic move is a utility class, and the views already spell it this
 * way. Defining it makes the existing markup mean what it says.
 */
.min-w-0 { min-width: 0; }

/* ─── The picker must not read as a thing that does not exist ──────────────────────
 *
 * ⚑⚑ MEASURED IN THE BROWSER 2026-08-15 (task #103). Tom Select's bootstrap5 theme makes
 * `.ts-control` a flex row, so on a SINGLE select the chosen `.item` and the search input
 * are siblings on one line. With *Coastal Nine* selected and `ridge` typed, the two sat 4px
 * apart at the same y — item x=1385 w=87, input x=1476 — and the control read
 * **"Coastal Nine ridge"**, a strain no licensee owns.
 *
 * ⚑ NOTHING WAS RECORDED WRONGLY: the stored value stayed Coastal Nine and the dropdown
 * correctly offered Ridge Runner. The defect is that the BOX asserted a third thing, and a
 * strain is what rides to the label and to the state.
 *
 * ⚑⚑ THE CLASS IS OURS, AND IT HAS TO BE. The obvious hook is Tom Select's own
 * `input-active`, and it is WRONG — it is set from `isFocused && !isInputHidden`, so it is
 * already present on a merely-focused control with an empty box. Keying on it blanked the
 * selection on first click, which states a falsehood in the other direction. `picker.js`
 * toggles `cf-picker-searching` from the actual search string instead; see it for why the
 * value itself is never cleared.
 *
 * ⚑ SPECIFICITY, NOT LOAD ORDER, IS WHAT MAKES THIS WIN. The Tom Select sheet loads AFTER
 * app.css (verified in the browser: index 5 vs 3), so an equal-weight selector would lose.
 * This one carries an extra class over the theme's `.ts-control .item`.
 */
.ts-wrapper.single.cf-picker-searching .ts-control > .item { display: none; }

/* ─── A chosen value is ONE LINE, and a long one truncates ─────────────────────────
 *
 * Brandon, 2026-08-26: *"one consistent problem with the tompicker selects ive noticed
 * accross the board is that it adds a trailing space on select and allows for text
 * wrapping when it should be nowrap... i think tompicker knows how to truncate text that
 * overflows"*.
 *
 * ⚑⚑ TWO CAUSES, BOTH IN THE THEME, AND NEITHER IS TOM SELECT KNOWING BETTER. Measured
 * against `tom-select.bootstrap5.min.css` 2.6.2:
 *
 *   `.ts-control      { flex-wrap: wrap; }`   — the row is allowed to break
 *   `.ts-control .item{ display: flex;  }`    — the item is a flex CONTAINER
 *   **`text-overflow` appears ZERO times in the entire sheet.**
 *
 * So nothing truncates, and a label longer than the control wraps to a second line and
 * grows the box. *"Shake (Packaged Gram - each)"* — 27 characters, an ordinary Metrc
 * category — does it in a half-width column.
 *
 * ⚑ THE "TRAILING SPACE" IS THE SECOND LINE, NOT A SPACE. `picker.js`'s `item` renderer
 * emits `<div>{label}{meta}</div>`, and `renderMeta()` returns a `<div class="small
 * text-body-secondary">`. A BLOCK element inside the item forces a line break even when
 * the meta is empty — which is why it reads as space the operator did not type.
 *
 * ⚑⚑ THE FIX IS THREE DECLARATIONS AND NO JAVASCRIPT. The item becomes a block that
 * ellipsises; `min-width: 0` is the load-bearing one, because a flex CHILD defaults to
 * `min-width: auto` and refuses to shrink below its content — without it `text-overflow`
 * never fires, which is the classic flexbox truncation trap.
 *
 * ⚑ SPECIFICITY, NOT LOAD ORDER, per the block above: the Tom Select sheet loads AFTER
 * app.css, so these selectors each carry one class more than the theme's.
 */
.ts-wrapper.single .ts-control { flex-wrap: nowrap; }

/*
 * ⚑⚑ THE ITEM TAKES THE ROW, AND THE IDLE SEARCH INPUT STOPS RESERVING 7rem.
 *
 * Brandon, on the first attempt: *"truncates too short now... it doesnt take up the
 * available space of the input"*. Correct — ellipsising alone was half the fix.
 *
 * ⚑ MEASURED IN THE THEME: `.ts-control > input { flex: 1 1 auto; min-width: 7rem; }`.
 * On a SINGLE select carrying a value that input is idle — the operator is not typing —
 * yet it still claims **112px** of a flex row, so the item ellipsised ~7rem short of the
 * box edge rather than at it.
 *
 * ⚑ `flex: 1 1 auto` ON THE ITEM is what makes it consume the remainder; `min-width: 0`
 * is what lets it shrink below its content so `text-overflow` can fire at all — the
 * classic flexbox truncation trap, and the reason the naive fix does nothing.
 *
 * ⚑ THE INPUT IS SHRUNK, NEVER HIDDEN. Hiding it would break typing-to-search, which is
 * the picker's whole purpose; `cf-picker-searching` already handles the other half of this
 * pair by hiding the ITEM while a query is live (see the block above). Between them the
 * row shows exactly one of the two at full width, and neither state wraps.
 */
.ts-wrapper.single .ts-control > .item {
    display: block;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ts-wrapper.single.has-items:not(.cf-picker-searching) .ts-control > input {
    flex: 0 1 auto;
    min-width: 0;
}

/* The meta line inside a chosen item joins the label rather than dropping below it. */
.ts-wrapper.single .ts-control > .item > div > div {
    display: inline;
}

/* ─── A picker is a form field, so it has a form field's background ────────────────
 *
 * Brandon, 2026-08-26: *"the tompickers appear to inherit a gray bg sometimes and a white
 * bg other times... they should all be the white bg that form fields use"*, then, when the
 * first attempt did not take: *"no they still show gray while in a modal"*.
 *
 * ⚑⚑⚑ THE FIRST ATTEMPT WAS WRONG TWICE, AND BOTH ERRORS ARE WORTH KEEPING.
 *
 * ① It targeted `.ts-wrapper.form-select`. **Our wrappers carry no such class** — read from
 *    the live DOM: `class="ts-wrapper single plugin-clear_button"`. `picker.blade.php` puts
 *    `form-select` on the ORIGINAL `<select>`, and Tom Select does not copy it to the
 *    wrapper it builds. The selector matched nothing.
 *
 * ② It painted `var(--bs-body-bg)`. **That token is `#f5f7fb` here — the app's grey page
 *    ground, not white.** So even where it applied it would have painted the exact grey
 *    being complained about. Measured: `.ts-control` computed `rgb(245,247,251)` while a
 *    plain `input.form-control` beside it computed `rgb(255,255,255)`.
 *
 * ⚑ BOOTSTRAP DOES NOT ROUTE `.form-control`'s BACKGROUND THROUGH A VARIABLE — measured,
 * `--bs-form-control-bg` is unset and the input is a literal `#fff`. So matching a field
 * means saying `#fff`, not borrowing a token that means something else.
 *
 * ⚑ `!important` IS REQUIRED, NOT PREFERRED. The theme declares
 * `.ts-control { background: var(--bs-body-bg); }` and, for the form-control/select
 * variants, `background: transparent !important` — no specificity beats the second.
 *
 * ⚑ DISABLED IS LEFT ALONE. The theme paints it `--bs-secondary-bg`, Bootstrap's own
 * disabled ground, which is the right signal for a control nobody may touch.
 */
.ts-wrapper:not(.disabled) .ts-control,
.ts-wrapper:not(.disabled).single.input-active .ts-control {
    /**
     * ⚑⚑⚑ `background-color`, NEVER THE `background` SHORTHAND — Brandon, 2026-08-26:
     * *"you changed background not background-color"*, after *"your previous fix got rid of
     * the carrot"*.
     *
     * ⚑ THE CARET IS A `background-image` ON THIS SAME ELEMENT:
     * `.ts-wrapper:not(.form-control,.form-select).single .ts-control { background-image:
     * url("data:image/svg+xml,…") }` — the chevron every single select draws on its right.
     * The SHORTHAND resets every background property it does not name, so
     * `background: #fff` silently set `background-image: none` and the caret vanished from
     * every picker in the product.
     *
     * ⚑ THE LONGHAND IS ALSO WHAT THE THEME USES for the rule this overrides —
     * `.full .ts-control { background-color: var(--bs-body-bg) }` — so matching its property
     * is what makes the override a swap rather than a reset.
     */
    background-color: #fff !important;
}

/* ─── A helper line that must not move the button beneath it ───────────────────────
 *
 * ⚑⚑ MEASURED ON THE 2026-08-15 BLIND DAY-ONE CULTIVATOR WALK (task #139).
 *
 * Two helper lines sit under the tag box on Harvest capture and Package out — the tag
 * COUNTER (`<x-tag-counter>`) and the pre-fill CHECK line (`<x-form.tag-input>`). Both
 * appeared and disappeared as the operator worked, so the tag COLUMN changed height; and
 * because that row is `align-items-end` with the tag column the TALLEST in it, the row
 * grew and the primary button moved with it. Measured: the Record button dropped **57px**
 * the moment the first character reached the tag box.
 *
 *   *"Stop the Record/Create buttons moving down the page as I use them… I mis-clicked
 *    twice, and the failure is silent — the weight box just empties, no error, and the
 *    last success banner is still on screen telling me it worked."*
 *
 * ⚑ 4 CCR §15049.1(b)(1) requires the wet weight be obtained *"immediately after
 * harvest"*. There is no second chance at that number once the material dries, so a
 * control that shifts under a wet hand on that field is not cosmetic.
 *
 * ⚑ SO EACH LINE ALWAYS OCCUPIES ITS BOX and Bootstrap's own `.invisible` hides the
 * CONTENT without removing it from flow — `d-none`, `x-show` and `<template x-if>` would
 * each reflow exactly as the conditional did. This class supplies only the height the
 * empty box must keep, which is why it is named for the RESERVATION and not for either
 * caller.
 *
 * ⚑ `1lh` IS ONE LINE OF THE ELEMENT'S OWN TYPE, not a pixel guess, so it cannot drift
 * when the type scale changes. Measured in Chrome: 18px at the current `.form-text small`.
 */
.cf-reserve-line { min-height: 1lh; }

/**
 * ⚑⚑⚑ A VALIDATION TOOLTIP CLEARS WHEN THE OPERATOR ENGAGES THE FIELD — NO LISTENER.
 *
 * Brandon, 2026-08-24, after two JavaScript attempts had failed: *"you could probably easilly
 * solve it with css using something like :input.is-invalid:focus ~ .error-tooltip"*.
 *
 * ══ ⚑⚑ WHY THIS BEATS THE HANDLER IT REPLACED ══════════════════════════════════════
 *
 * The dismissal was first written as a `blur` listener bound to the input in `$nextTick`, and it
 * never fired. Measured in the browser: the handler existed, `field()` resolved to the right
 * input, `document.activeElement` had already moved to the next field — and the tooltip stayed.
 * **Livewire had morphed the input during the round trip**, so the listener sat on a node no
 * longer in the document while the visible field was a fresh one with no handler.
 *
 * ⚑ A DELEGATED `focusout` ON `document` WOULD HAVE SURVIVED THAT, and is still more moving parts
 * than the problem deserves. CSS re-evaluates against the LIVE DOM: there is nothing to go stale,
 * nothing to unbind on teardown, and a node swapped in a millisecond ago is matched by the same
 * rule as the one it replaced. **The element is what changes; the selector is what survives.**
 *
 * ══ ⚑⚑⚑ THE MESSAGE BELONGS TO THE FOCUSED FIELD — hidden by default, shown on focus ══
 *
 * Brandon's rule, 2026-08-24, and it is the third attempt at this pairing because the first two
 * each traded away half the feature:
 *
 *   1. `.is-invalid:focus ~ … { display: none }` — hid the message the instant the elected
 *      tooltip focused its own field. The two were mutually exclusive by construction.
 *   2. Dropping the focus to keep the message — kept the wrong half. *"but its not autofocusing
 *      the one that gets the tooltip"*.
 *   3. A `.cf-correcting` class added on the first keystroke — worked, and needed a delegated
 *      `input` listener plus a class Livewire had to clear. **It was also ONE-WAY:** once typed
 *      over, the message was gone until the next submit.
 *
 * ⚑⚑ INVERTING IT REMOVES ALL OF THAT. Hidden by default, visible while focused: the elected
 * tooltip focusing its field is what SHOWS the message rather than what hides it, so the two
 * halves stop fighting and no JavaScript is involved at all.
 *
 * ⚑ AND IT IS REVERSIBLE, WHICH THE KEYSTROKE VERSION WAS NOT. Tab away and the message goes;
 * come back to check what was wrong and it is there again. An operator who cannot re-read a
 * refusal has to submit again to see it.
 *
 * ⚑ `display`, FOR PARITY WITH BOOTSTRAP — Brandon, 2026-08-24: *"doesnt have to be
 * visible/hidden.... it could be display:block/none in the css for parity"*. `.invalid-tooltip`
 * ships as `display:none` and is revealed by an `.is-invalid ~` adjacency Livewire's markup does
 * not produce; stating the same property is what the component already expects.
 *
 * ⚑ A `visibility` PAIR WAS TRIED FIRST, on the theory that `display:none` gives the element a
 * zero box and Popper's next reposition would compute against nothing. **Measured in the browser
 * and it does not happen** — blur then refocus returns the message correctly placed, because
 * `strategy: 'fixed'` recomputes on the next tick against an element that has real geometry again
 * by then. The simpler property wins.
 *
 * ⚑ THE REFUSAL IS NOT WEAKENED. The Livewire error stays in `$errors`, the control keeps
 * `is-invalid`, and the next submit re-renders the tooltip and re-runs the election in
 * `public/js/field-error.js`. What this hides is a REMINDER.
 *
 * ⚑ IT VERIFIED AS A REAL SIBLING BEFORE BEING WRITTEN. The component renders
 * `div.mb-2 > label + input.form-control + div.invalid-tooltip`, so `~` reaches it. ⚑ A call site
 * that WRAPS its input breaks the adjacency and this rule silently does nothing there — the JS
 * click dismissal remains the fallback for those, which is one reason it was kept.
 *
 * ⚑ `!important` BECAUSE ALPINE WRITES `display` AS AN INLINE STYLE. `x-bind:style` sets
 * `display: block` on the element itself, and an inline declaration outranks any stylesheet rule
 * without it. This is the one place in this file where it is load-bearing rather than lazy.
 */
.is-invalid ~ .invalid-tooltip { display: none !important; }
.is-invalid:focus ~ .invalid-tooltip { display: block !important; }
.ts-wrapper.is-invalid.focus ~ .invalid-tooltip { display: block !important; }

/*
 * ⚑⚑ A MONOSPACE PLACEHOLDER IN TAG FORMAT READS AS A FILLED FIELD.
 *
 * Brandon, 2026-08-24: *"the placeholder text is darker than it should be... it does actually
 * look like it was filled in."*
 *
 * Measured the hard way. `<x-form.tag-input>` renders `class="form-control font-monospace"` with
 * `placeholder="1A4FF…0001"` — an elided tag, in the same typeface the real value uses, at
 * Bootstrap's default placeholder contrast. The assistant read it as a value, saved the move,
 * and only found `first_plant_tag` was NULL by querying the database. A blind walker had made
 * the identical mistake on the identical field the day before.
 *
 * ⚑ THE HAZARD IS SPECIFIC TO THIS SHAPE. A placeholder reading "Search lots…" is obviously
 * prompt text. One reading "1A4FF…0001" is indistinguishable from the thing it is asking for,
 * and the monospace face is what completes the illusion — every real tag on screen is monospace.
 *
 * ⚑ SO THE FIX IS CONTRAST, NOT COPY. Removing the example would cost more than it saves: the
 * shape of a 24-character tag is genuinely useful to someone holding one. It just has to look
 * like a suggestion. `--bs-secondary-color` is the token Bootstrap already uses for muted text,
 * so this stays theme-correct in light and dark.
 */
.form-control.font-monospace::placeholder {
    color: var(--bs-secondary-color);
    opacity: .55;
    font-style: italic;
}

/* ---------------------------------------------------------- day-one welcome
 *
 * ⚑⚑ THE MARK ON THE EMPTY-FARM WELCOME, TINTED TO THE BRAND GREEN.
 *
 * `cannaflow-mark.svg` carries a single `#111111` fill, and it is loaded through `<img>` rather
 * than inlined — so `currentColor` and `fill` cannot reach it. A CSS filter recolours the
 * rendered pixels instead, which keeps ONE asset on disk rather than a second green copy that
 * would drift from the first the next time the mark is redrawn.
 *
 * ⚑ THE FILTER CHAIN IS THE STANDARD BLACK-TO-ARBITRARY-COLOUR RECIPE: `brightness(0)` forces
 * every pixel to pure black first — so the result does not depend on what colour the source
 * happens to be — and `saturate(100%)` plus the `sepia`/`hue-rotate` pair carries it to the
 * accent. Measured against `--pm-accent` (#1f6f54) in the browser, not calculated.
 *
 * ⚑ IT IS SCOPED TO ONE CLASS used on one screen. The sidebar's reversed wordmark and the
 * signed-out lockups are separate files with their own fills and must not be touched by this.
 */

.cf-welcome-mark {
    filter: brightness(0) saturate(100%) invert(31%) sepia(28%) saturate(1093%)
            hue-rotate(118deg) brightness(94%) contrast(90%);
    opacity: .9;
}

/* ⚑⚑ THE ONE BUTTON ON THE WELCOME WEARS THE BRAND GREEN, NOT BOOTSTRAP BLUE.
 *
 * ⚑ MEASURED IN THE BROWSER: with the mark tinted to `--pm-accent` and the button left on stock
 * `btn-primary`, the screen carried TWO accent colours and the blue was the louder of them — so
 * the first impression's strongest colour belonged to the CSS framework rather than to the
 * product. On a screen whose whole job is introducing CannaFlow, that is the wrong owner.
 *
 * ⚑ SCOPED, NOT GLOBAL. Re-theming `btn-primary` across the application is a real decision with
 * a real blast radius — every board, every dialog, every confirm — and it is not one an empty
 * state gets to make on its own. This class exists so the welcome can be right today without
 * pre-empting that. **If the palette is settled later, delete this rule rather than adding a
 * second one beside it.**
 */

.btn.cf-welcome-cta,
.btn.cf-welcome-cta:visited {
    background-color: var(--pm-accent);
    border-color: var(--pm-accent);
    color: #fff;
}

.btn.cf-welcome-cta:hover,
.btn.cf-welcome-cta:focus,
.btn.cf-welcome-cta:active {
    background-color: var(--pm-accent-hover);
    border-color: var(--pm-accent-hover);
    color: #fff;
}

/* ⚑ THE FOCUS RING MUST FOLLOW THE BUTTON'S OWN COLOUR, or a keyboard user gets a blue halo
 * around a green control — Bootstrap's default ring is derived from its primary, not from ours. */
.btn.cf-welcome-cta:focus-visible {
    box-shadow: 0 0 0 .25rem rgba(31, 111, 84, .4);
    outline: 0;
}

/* ══ THE DAY ROWS IN AN HOURS DRILL-DOWN ════════════════════════════════════════════════════
 *
 * Brandon, 2026-09-14: *"the accordian days need a hover state and a bottom border to give
 * visual seperation.... right now its hard to visually identify which regular/ot/totals belong
 * to which day because they are so far apart"*.
 *
 * ⚑ THE DATE SITS LEFT AND THE FIGURES SIT RIGHT, about a thousand pixels apart on a wide
 * screen, with nothing between them. The eye has to track across empty space and loses the row —
 * exactly what a table's zebra striping or rules exist to prevent.
 *
 * ⚑⚑ A BORDER AND A HOVER, NOT STRIPES. Striping would fight the amber tint a day with an
 * unclosed shift already carries, and the tint is a signal rather than decoration. A rule
 * separates every row without competing for the same channel.
 *
 * ⚑ CUSTOM CSS HERE IS CHARACTER, NOT ARRANGEMENT — the layout is Bootstrap's flex utilities on
 * the element itself. Hover cannot be expressed as a utility class, which is what makes this a
 * stylesheet rule rather than more markup.
 *
 * ══ ⚑⚑ WHY NOT `.accordion` — ASKED AND ANSWERED 2026-09-14 ════════════════════════════════
 *
 * Brandon raised it: *"theres already a native bootstrap accordian header style that handles the
 * visual representation"* — correct, and it is the right instinct. **He then looked at this and
 * said keep it** (*"but yours didnt look bad"*), so the note is here to stop the question being
 * re-derived as an oversight rather than to defend the choice.
 *
 * ⚑ WHAT THE SWAP WOULD HAVE COST, all of it deliberate:
 *
 *   - **The amber tint surviving hover.** `.accordion-button` owns its own background across
 *     every state, so the "never clocked out" signal would vanish under the cursor.
 *   - **The Livewire-driven open state.** `.accordion-collapse` is Bootstrap JS with its own
 *     idea of what is open; this list is server-rendered from `$openDays`, and several days stay
 *     open at once because a supervisor compares them.
 *   - **The figure row.** `.accordion-button` ends in a chevron pseudo-element at the right edge,
 *     which is precisely where Regular / Overtime / Total sit.
 *
 * ⚑⚑ **THE RULE IT LOOKS LIKE IT BREAKS IS STILL THE RULE.** Reach for the framework primitive
 * first; this is a case where the primitive was measured against the requirement and lost, which
 * is different from not having looked.
 */
.cf-punch-day-row {
    border-bottom: 1px solid var(--bs-border-color);
    transition: background-color .12s ease-in-out;
}

/* ⚑ THE LAST DAY NEEDS NO RULE — the list already ends, and a trailing border reads as a row
 * that failed to render. */
.cf-punch-day:last-child > button > .cf-punch-day-row {
    border-bottom: 0;
}

.cf-punch-day:hover > button > .cf-punch-day-row {
    background-color: var(--bs-secondary-bg);
}

/* ⚑⚑ AN AMBER DAY KEEPS ITS COLOUR ON HOVER, and deepens rather than reverting to grey. The
 * tint says "a shift here was never clocked out"; losing it under the cursor would remove the
 * signal at the exact moment somebody is reaching for the row. */
.cf-punch-day:hover > button > .cf-punch-day-row.bg-warning-subtle {
    background-color: var(--bs-warning-border-subtle);
}

/* ══ OPTGROUP HEADINGS IN A TOM SELECT ══════════════════════════════════════════════════════
 *
 * Brandon, 2026-09-14: *"have the optgroup headings bold and the options for a group be slightly
 * indented so it visually reads as a group heading instead of a disabled option"*.
 *
 * ⚑⚑ THE BOOTSTRAP 5 THEME GIVES A HEADING `color: #6c757d` AND NO WEIGHT — which is the same
 * treatment a DISABLED OPTION gets. A row that is dim, unclickable and no heavier than its
 * neighbours reads as "you may not pick this" rather than as "these belong together", and that is
 * the opposite of what a heading is for.
 *
 * ⚑ CUSTOM CSS HERE IS CHARACTER, NOT ARRANGEMENT. Tom Select ships the structure; weight and
 * indentation are the two signals that turn a line into a heading, and neither can be expressed
 * as a Bootstrap utility on markup a library generates.
 */
.ts-dropdown .optgroup-header {
    color: var(--bs-body-color);
    font-weight: 600;
}

/* ⚑⚑ THE INDENT IS ON THE OPTIONS, NOT THE HEADING. Pushing the heading LEFT would need a
 * negative margin fighting the dropdown's own padding; pushing its members right says the same
 * thing and leaves the heading where every other first-level row sits.
 *
 * ⚑ AND ONLY INSIDE A GROUP. `.optgroup` wraps a heading and its members, so an ungrouped list —
 * which is nearly every picker in the application — keeps its existing alignment untouched. */
.ts-dropdown .optgroup .option {
    padding-left: 1.5rem;
}

/* ⚑ A RULE BETWEEN GROUPS, so the second heading reads as a new section rather than as another
 * row. The first has nothing above it to separate from. */
.ts-dropdown .optgroup + .optgroup .optgroup-header {
    border-top: 1px solid var(--bs-border-color);
    margin-top: .25rem;
    padding-top: .5rem;
}
