.form-group{display:flex;flex-direction:column;margin-bottom:var(--space-3)}
label{font-size:.875rem;margin-bottom:.5rem;color:var(--color-muted)}
/* The border was rgba(15,23,42,.08) — a cool navy tint that read as a faint
   blue edge against the warm surfaces, and that had no dark-theme value at
   all. It is now the shared --border token, so an input outline matches a
   card edge and a table rule. */
input[type="text"],input[type="email"],select,textarea{padding:.75rem;border:1px solid var(--border);border-radius:var(--radius-md);font-size:1rem;background:var(--input-bg);color:var(--text)}

/* ========================================================
   INLINE FIELD ERRORS
   ========================================================
   The .validation-msg span already existed under every field but was never
   populated — a validation failure showed up only as a generic toast, so
   the operator had to guess which of a dozen fields was wrong. The server
   now returns per-field messages and static/js/main.js fills these in.
   ======================================================== */
.input-error,
input.input-error,
select.input-error,
textarea.input-error {
  border-color: var(--negative) !important;
  box-shadow: 0 0 0 3px rgba(var(--accent-red-rgb), .18) !important;
}

.validation-msg {
  display: block;
  font-size: 12px;
  line-height: 1.4;
  color: var(--negative-ink);
  margin-top: 4px;
  min-height: 14px;   /* reserve the space so filling it doesn't shift layout */
}

/* Only show the warning glyph when there is actually a message. */
.validation-msg:not(:empty)::before {
  content: "⚠ ";
  font-size: 11px;
}


/* ========================================================
   MONTH PICKER (v2.4.0)
   ========================================================
   The custom replacement for `<input type="month">`. Markup in
   templates/partials/_month_picker.html, behaviour in the MONTH PICKER
   section at the bottom of static/js/main.dashboard.js. Read the partial
   first — it explains why the native input is still in the document.

   THE SWAP IS ONE CLASS. `.monthpick--ready` is added by the script once
   every part of the component has been found and wired. Until then the
   native input is what is visible and the trigger is not, so a page whose
   JavaScript failed to load, was blocked, or threw on some other line still
   has a working month field rather than a button that does nothing. That is
   the whole reason the default state in this file is "native visible".

   COLOURS. Everything below is a token. The popover is a --surface-raised
   card on the parchment page with --border-strong around it (it floats, so
   WCAG 1.4.11 applies to its boundary), the selected month is --brand with
   --on-brand text, and today's month is marked with a --brand-ink ring
   rather than a fill so the two states cannot be confused at a glance.
   ======================================================== */
.monthpick { position: relative; display: inline-flex; align-items: center; }

/* Before the upgrade: the native input, styled as it always was. */
.monthpick-trigger { display: none; }
.monthpick--ready .monthpick-native { display: none; }
.monthpick--ready .monthpick-trigger { display: inline-flex; }

/* ---- The trigger ----
   Sized to match .filter-pill's own padding so the picker sits on the same
   baseline as the Monthly/Accumulative toggle beside it. It is a button and
   it therefore gets the app's focus ring rather than the browser's. */
.monthpick-trigger {
    align-items: center;
    gap: var(--space-2);
    padding: 6px 10px 6px 12px;
    min-height: 34px;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-main);
    cursor: pointer;
    white-space: nowrap;
}
.monthpick-trigger:hover { background: var(--brand-wash); }
.monthpick-trigger:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
}
.monthpick-icon { width: 15px; height: 15px; color: var(--text-muted); flex: none; }
.monthpick-value { font-variant-numeric: tabular-nums; }
.monthpick.is-empty .monthpick-value { color: var(--text-muted); font-weight: 500; }
/* The caret turns to point at the open panel. transform only, so it is
   composited and picks up the reduced-motion override in motion.css along
   with everything else. */
.monthpick-caret {
    width: 13px; height: 13px; flex: none;
    color: var(--text-muted);
    transition: transform var(--dur-fast, 120ms) var(--ease-out);
}
.monthpick.is-open .monthpick-caret { transform: rotate(180deg); }

/* ---- The popover ----
   `right: 0`, not `left: 0`. The picker is the last-but-one control in a
   filter bar that sits at the RIGHT edge of the page on a desktop, so a
   left-anchored panel 260px wide hangs off the viewport and gives the whole
   page a horizontal scrollbar — the exact regression the screenshot-width
   assertion in the verification pass exists to catch. */
.monthpick-pop {
    position: absolute;
    top: calc(100% + var(--space-2));
    right: 0;
    z-index: var(--z-modal, 1050);
    width: 268px;
    /* Belt and braces for the narrowest phone this app is read on. The panel
       can never be wider than the screen it is drawn on, whatever the
       anchoring decides. */
    max-width: calc(100vw - 2 * var(--space-3));
    padding: var(--space-3);
    background: var(--surface-raised);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    box-shadow: var(--elev-3);
    /* --radius-lg outside, --space-2 of inset, --radius-md on the children:
       the concentric scale from base/variables.css, not three numbers picked
       to look right. */
    --r-outer: var(--radius-lg);
    --r-pad: var(--space-2);
}
.monthpick-pop[hidden] { display: none; }
/* The arrival. Transform and opacity only — see components/motion.css. */
@keyframes monthpick-in {
    from { opacity: 0; transform: translateY(-6px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
.monthpick-pop:not([hidden]) {
    animation: monthpick-in var(--dur-base, 200ms) var(--ease-out);
    transform-origin: top right;
}
/* Flipped to the other edge when the right-anchored panel would hang off the
   left of the screen — which is what happens on a phone, where the filter bar
   is full width and the picker sits at its left edge rather than its right.
   The decision is made by measurement in main.dashboard.js, not by a
   breakpoint here; see the comment in open(). */
.monthpick-pop--left { right: auto; left: 0; }
.monthpick-pop--left:not([hidden]) { transform-origin: top left; }

/* ---- Year stepper ---- */
.monthpick-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
}
.monthpick-year {
    font-size: 15px;
    font-weight: 700;
    color: var(--text-main);
    font-variant-numeric: tabular-nums;
    letter-spacing: .5px;
}
.monthpick-nav {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px; height: 32px;
    padding: 0;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-main);
    cursor: pointer;
}
.monthpick-nav .icon { width: 15px; height: 15px; }
.monthpick-nav:hover:not(:disabled) { background: var(--brand-wash-strong); border-color: var(--brand); }
.monthpick-nav:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
/* Disabled rather than removed at the ends of the range — a control that
   disappears moves the two beside it, and the year label sliding sideways on
   the last click reads as a fault. 40% keeps it visibly present without
   inviting a click; it carries no information a reader needs, so the 3:1
   floor for meaningful graphics does not apply to it. */
.monthpick-nav:disabled { opacity: .4; cursor: not-allowed; }

/* ---- The 3x4 month grid ---- */
.monthpick-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-2);
}
.monthpick-month {
    padding: 9px 0;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-main);
    cursor: pointer;
    /* 44px is the tap target this does NOT quite reach, and deliberately: a
       twelve-cell grid at 44px is 200px tall and pushes the quick jumps below
       the fold on a phone in landscape. 36px with 8px of gap gives a 44px
       PITCH, which is what the guidance is actually protecting — the distance
       between adjacent targets, not the ink. */
    min-height: 36px;
}
.monthpick-month:hover:not(:disabled) { background: var(--brand-wash-strong); }
.monthpick-month:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.monthpick-month:disabled { opacity: .35; cursor: not-allowed; }
/* Today's month: a ring, not a fill. It has to be distinguishable from the
   SELECTED month at a glance, and on the landing view they are the same cell
   — two fills would just be one fill. */
.monthpick-month.is-current {
    border-color: var(--brand-ink);
    color: var(--brand-ink);
}
/* The selected month. Last, so it wins over .is-current when they coincide. */
.monthpick-month.is-selected {
    background: var(--brand);
    border-color: var(--brand);
    color: var(--on-brand);
    font-weight: 700;
}

/* ---- Quick jumps ---- */
.monthpick-foot {
    display: flex;
    gap: var(--space-2);
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px solid var(--border);
}
.monthpick-quick {
    flex: 1;
    padding: 7px 0;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-muted);
    cursor: pointer;
}
.monthpick-quick:hover { background: var(--brand-wash); color: var(--brand-ink); border-color: var(--brand); }
.monthpick-quick:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* ==========================================================================
   THE SAWN TIMBER ORDER FORM (v2.6.0)
   ==========================================================================
   A digitisation of the State Timber Corporation's printed docket. Everything
   here is built from the existing tokens — no new colours, no new radii, no
   new type scale — so it inherits the light and dark themes and the reduced
   motion rules without opting into any of them by hand.

   THE GRID IS THE WHOLE PROBLEM
   -----------------------------
   Twenty-six length columns by seven cross-sections. At a tappable cell width
   that table is roughly 1,900px wide, which is wider than a desk monitor and
   five times a phone. The three options were: shrink the cells (untappable),
   reflow into stacked cards (no longer resembles the document a clerk is
   copying from), or scroll it sideways with the row header pinned.

   The third is what is implemented. `position: sticky` on the first column
   keeps the cross-section visible while the lengths scroll past it, which is
   the only thing that makes a wide grid usable: without it, scrolling to the
   4.4m column means no longer knowing which row you are typing into. */

.sawn-order__masthead {
    text-align: center;
    padding-bottom: var(--space-3);
    margin-bottom: var(--space-4);
    border-bottom: 1px solid var(--border);
}

.sawn-order__org {
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text);
}

.sawn-order__title {
    font-size: .8125rem;
    color: var(--text-muted);
    margin-top: var(--space-1);
}

/* The scroll frame. overflow-x on a wrapper rather than the table, because a
   sticky cell positions against its nearest scrolling ancestor. */
.sawn-grid-frame {
    /* max-width, not width. The fieldset fix above is what actually contains
       this; these two lines are the guard that turns any FUTURE recurrence
       into a scrollbar rather than into a panel rendering over its neighbour.
       A containment bug that fails visibly in its own box is a bug somebody
       can see and fix; one that escapes into the next column is a bug
       reported as "the page is broken". */
    max-width: 100%;
    /* And a floor of its own, so the frame can never be sized BY its content
       even if an ancestor loses the grid/flex item guard again. max-width
       alone does not do this: it caps a box, it does not stop the box being
       measured from the inside out. v2.7.0 — see the note on the unscoped
       `.grid > *` rule in layout/mobile.css for the 1,989px frame this
       prevents. */
    min-width: 0;
    box-sizing: border-box;
    /* Declared here as well as coming from the .responsive-wrapper and
       .overflow-x-auto utilities the markup also carries. Duplication on
       purpose: the containment of this component should not depend on
       somebody remembering two utility class names in a template, and
       `overflow-x: auto` is idempotent. Drop the utilities and the grid still
       scrolls; drop this and it still scrolls — it takes both going missing
       to break, which is the point. */
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    background: var(--surface-raised);
    /* A hint that there is more to the right on a device with no visible
       scrollbar — a phone. Without it the grid looks like it ends at 5.6m
       being off screen. */
    background-image: linear-gradient(to left,
                      rgba(42, 20, 15, .06), transparent 24px);
}

/* .sawn-grid-frame .sawn-grid, not .sawn-grid, and the extra class is doing
   real work. layout/grid.css carries
       .responsive-wrapper table { width: 100%; min-width: 500px; }
   at specificity (0,1,1), which beats a lone (0,1,0) class however this file
   is ordered in the bundle — so the table was being sized to 100% of a
   container that had itself been blown out, rather than to its own content.
   Two classes is (0,2,0), which wins. */
.sawn-grid-frame .sawn-grid {
    border-collapse: separate;   /* separate, so sticky cells keep their border */
    border-spacing: 0;
    width: max-content;
    min-width: 100%;
    font-size: .8125rem;
}

.sawn-grid th,
.sawn-grid td {
    border-bottom: 1px solid var(--border);
    border-right: 1px solid var(--border);
    padding: 0;
    text-align: center;
    vertical-align: middle;
}

.sawn-grid thead th {
    background: var(--surface);
    font-weight: 700;
    color: var(--text-muted);
    padding: var(--space-2) var(--space-1);
    white-space: nowrap;
    position: sticky;
    top: 0;
    z-index: 2;
}

.sawn-grid__len { min-width: 3.5rem; }

/* The pinned column. Two rules do the work: sticky/left on every cell in the
   column, and a z-index above the ordinary cells so the scrolling ones pass
   UNDER it rather than over. The corner cell needs a higher one again — it is
   both pinned left and pinned top, so it has to clear both. */
.sawn-grid__section,
.sawn-grid__corner {
    position: sticky;
    left: 0;
    background: var(--surface);
    font-weight: 700;
    color: var(--text);
    padding: var(--space-2) var(--space-3);
    white-space: nowrap;
    text-align: left;
    /* Its own right border rather than the shared one: a sticky cell's border
       scrolls away with the table on some engines, leaving the pinned column
       bleeding into the cells passing beneath it. */
    box-shadow: 1px 0 0 var(--border);
    z-index: 3;
}

.sawn-grid__corner { z-index: 4; }

/* Same problem, worse consequences. layout/main.css styles every
       input[type="number"]
   at (0,1,1) with `width: 100%; padding: 12px 15px; min-height: 44px;
   border: 1px; border-radius: var(--radius-md)` — and it is LATER in the
   bundle than this file as well as more specific, so a bare
   `.sawn-grid__cell` lost twice over. Each cell was rendering as a full
   rounded form control 64px wide instead of a flat 3rem box, which is most
   of how a 26-column grid reached 1,989px.
   `.sawn-grid .sawn-grid__cell` is (0,2,0) and wins on class count. */
.sawn-grid .sawn-grid__cell {
    width: 3rem;
    min-width: 3rem;
    /* Still a comfortable tap target on a phone, which is what the UA rule's
       min-height: 44px was buying and this must not give back. The height
       comes from the row, so it is set here rather than inherited. */
    min-height: 2.5rem;
    border: 0;
    border-radius: 0;
    background: transparent;
    padding: var(--space-2) var(--space-1);
    text-align: center;
    font: inherit;
    font-size: inherit;
    color: var(--text);
    box-sizing: border-box;
    /* Chrome and Safari put spinner arrows on a number input, which at this
       width cover most of the cell and are useless on a touch screen. */
    -moz-appearance: textfield;
    appearance: textfield;
}

.sawn-grid__cell::-webkit-outer-spin-button,
.sawn-grid__cell::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.sawn-grid .sawn-grid__cell:focus {
    outline: 2px solid var(--brand);
    outline-offset: -2px;
    background: var(--surface-input);
}

/* A cell with a quantity in it. Set by main.entry.js as you type, so a filled
   docket shows its four occupied cells at a glance among 182 empty ones —
   which is also the fastest way to spot a number typed into the wrong row. */
.sawn-grid .sawn-grid__cell.is-filled {
    background: var(--brand-wash, var(--surface-sunken));
    font-weight: 700;
}

.sawn-grid__total {
    padding: var(--space-2) var(--space-3);
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    white-space: nowrap;
    color: var(--text);
}

.sawn-grid tfoot th,
.sawn-grid tfoot td {
    background: var(--surface);
    border-bottom: 0;
}

.sawn-grid__footnote {
    padding: var(--space-2) var(--space-3);
    text-align: left;
    font-weight: 400;
    color: var(--text-muted);
    white-space: nowrap;
}

/* The .sawn-order__copies block — the dashed box holding the paper docket's
   COPY distribution list — was removed in v2.7.0 with the markup it styled.
   Deleted rather than left behind: three orphan rules in a component
   stylesheet read as "this element exists somewhere", and the next person
   looking for it spends the time finding out that it does not. */

/* The two-way switch at the top of the Order Placement tab. */
.order-mode {
    display: flex;
    gap: var(--space-2);
    margin-bottom: var(--space-4);
    flex-wrap: wrap;
}

.order-mode__btn[aria-pressed="true"] {
    background: var(--brand);
    color: #fff;
    border-color: var(--brand);
}

@media (max-width: 640px) {
    /* One line of type smaller and one step less padding, which is what buys
       back enough width for three columns of the grid to be on screen at once
       on a 390px phone instead of two. */
    .sawn-grid-frame .sawn-grid { font-size: .75rem; }
    .sawn-grid .sawn-grid__cell { width: 2.75rem; min-width: 2.75rem; }
    .sawn-grid__section,
    .sawn-grid__corner { padding: var(--space-2); }
}
