/*
 * v1.3.5 — UI refresh (user-reported: "clean up the UI and make it more
 * appealing... give a similar look/feel and typography to food safety
 * plugin, but choose light orange/grey colours").
 *
 * This plugin is nested inside FinPress on one shared WordPress install,
 * and the site's actual page template (header/footer/theme chrome) is
 * shared with the accounting app — editing that would risk the accounting
 * UI, so per the user's own instruction this is a CSS-only reskin. Every
 * selector below targets a class or id that already exists in
 * templates/portal-dashboard.php, templates/signup-form.php or
 * assets/js/portal-script.js — nothing here required a markup change.
 *
 * The colour/spacing/shadow/radius values are deliberately matched to
 * FinPress's own design system (reviewed directly from the real
 * finpress-complete/assets/style.css and finpress-self-service/assets/css/
 * signup-style.css), just re-themed from FinPress's teal onto a light
 * orange + grey palette so the two products feel like siblings without
 * looking identical:
 *   - same system font stack, same base font sizes/weights for headings,
 *     labels and body text
 *   - same card language: white panels, 1px light-grey border, small
 *     shadow, 8px radius
 *   - same button language: 6px radius, subtle shadow, lift-on-hover
 *   - same input language: 4px radius, light-grey border, coloured focus
 *     ring
 *   - same tab-bar language: bottom-border indicator, muted inactive
 *     colour, brand-coloured active state
 *
 * v1.4.4 — BOLD/CAPS POLICY (user-reported: "the text in the orange
 * buttons should be bold. Make sure no other text is in bold. Make sure
 * no text is in CAPS."): the only bold text (font-weight: 700) anywhere
 * in this file is the label inside a solid-orange "commit" button —
 * button[type=submit], the Team/Approve/Scale/e-signature action
 * buttons, and the signup/login submit buttons, all of which share the
 * var(--fps-orange-700) background. Every other font-weight in this file
 * is 400 or 500 (never 600/700/bold) and every text-transform: uppercase
 * has been removed. Keep this invariant when adding new rules: a new
 * bold or ALL-CAPS declaration on anything other than an orange submit
 * button is a regression of this request.
 *
 * v1.5.0 — UI clean-up round 2 (user-reported, screenshots of the
 * Products tab: "heading/typesize, alignment, layout etc [is] messy...
 * neaten up the pages. Also, the text inside the orange buttons must be
 * bold. There are multiple small buttons with white backgrounds; these
 * should have a light orange fill. The colour used for the main dark
 * orange buttons is too brown. Use a brighter orange."). Four fixes:
 *
 * (1) SERIF HEADINGS/TEXT — root cause found: `.fps-portal` sets
 * font-family on the container and relies on inheritance, but a direct
 * rule on an element always wins over an inherited value regardless of
 * specificity. This site's active theme apparently styles bare `h1`-`h6`
 * and `p` directly (its own serif heading/body font), which is why
 * "Products", "Add a product" and the italic hint paragraph above the
 * product form were rendering in the theme's serif font instead of this
 * plugin's sans-serif stack, even though every button/input already had
 * its font forced this way (see the v0.4.3/v0.4.4 note above). Fixed the
 * same way `.fps-notice, .fps-notice *` already was: every element under
 * `.fps-portal`/`.fps-signup-form` now gets `font-family` forced
 * (excluding `pre`/`code`/`kbd`/`samp`, which should stay monospace).
 *
 * (2) BOLD ORANGE BUTTON TEXT WASN'T ACTUALLY BOLD — a second, subtler
 * instance of the same "important beats specificity" trap this file
 * already documents elsewhere: `.fps-portal button, .fps-signup-form
 * button { font: inherit !important; ... }` (top of file) sets the
 * *shorthand* `font` property with `!important`, which expands to set
 * font-weight (among others) to `inherit`, `!important`, on literally
 * every button. The v1.4.4 fix that added `font-weight: 700` to the
 * orange "commit" buttons had no `!important`, so it silently lost to
 * that earlier shorthand and never actually rendered bold. Every
 * `font-weight: 700` on an orange button below is now `!important` too.
 *
 * (3) SMALL WHITE BUTTONS → LIGHT ORANGE FILL — the base button style
 * (used by every button that isn't one of the solid-orange "commit"
 * buttons or a destructive red one — Open, Close, + Add row, Raise NCR/
 * complaint, Update NCR/complaint, etc.) changed from a white/grey
 * "ghost" button to a light-orange fill (var(--fps-orange-100)
 * background, var(--fps-orange-200) border, var(--fps-orange-900) text),
 * matching the light-orange panel language already used elsewhere in
 * this file (.fps-tier-badge, .fps-coming-soon, .fps-scale-calc). The
 * tab bar, the checklist run-list and the destructive (red) buttons all
 * set their own background explicitly with higher specificity, so none
 * of them are affected by this change.
 *
 * (4) BRIGHTER BRAND ORANGE — `--fps-orange-700` (#C6672B) and
 * `--fps-orange-900` (#9A4A18) read as brown/terracotta: fairly low
 * saturation (~64%) for their lightness. Replaced with a more saturated,
 * true-orange pair at the same hue family and similar lightness —
 * `--fps-orange-700: #D35400` and `--fps-orange-900: #A84300` — so the
 * change is "more vivid", not "lighter/darker". Every other use of these
 * two tokens (header underline, active tab, links, focus rings, the
 * checklist accent border, primary button hover) is a `var()` reference,
 * so it picks up the brighter colour automatically; the three focus-ring
 * `box-shadow`s that hardcoded the old colour's RGB triplet
 * (`rgba(198, 103, 43, ...)`) were updated to the new one (`rgba(211,
 * 84, 0, ...)`) to match. `--fps-orange-500`/`400`/`200`/`100`/`50`
 * (already light, desaturated accent/tint tones, not "the main dark
 * orange buttons" the user meant) are unchanged.
 *
 * v1.10.2 — Button spacing overhaul + brighter "sunrise orange"
 * (user-reported, with two screenshots of Save specification/Approve
 * specification sitting almost on top of each other: "U have to finally
 * sort out the recurring problem of inadequate spacing/padding between
 * buttons as well as the spacing/padding between the buttons and other
 * text elements above/below. Review in sandbox across the app and fix.
 * also brighten the orange colour, more like a sunrise orange."). The
 * "recurring" framing is accurate: v1.4.1-v1.5.3 repeatedly touched
 * button SIZE/COLOUR but never once gave a `<button>` its own margin —
 * every screenshot's "buttons touching" complaint up to now happened to
 * be fixable by changing height/colour instead, and this specific
 * failure mode (an orange button immediately followed by another orange
 * button, zero gap) was never actually hit until Save specification/
 * Approve specification.
 *
 * ROOT CAUSE: the base `.fps-portal button` rule (below) has always set
 * padding/border/background/font — never `margin`. Two buttons that are
 * adjacent siblings with no whitespace text node between them in the
 * source (exactly how portal-script.js builds this pair: `html +=
 * '<button ...>Save…</button>'` immediately followed by `html += '<button
 * ...>Approve…</button>'`, string-concatenated with no space) get
 * *zero* gap — not "too little", literally none, which reads as
 * overlapping once each button's own box-shadow and rounded corners are
 * drawn. The exact same gap-from-source-whitespace fragility was already
 * silently relied on everywhere else in the app (PHP-templated buttons
 * happen to have a newline between them in the template source, which
 * renders as a few px of inline-block gap) — accidentally OK in most
 * places, but never actually guaranteed, and already worked around by
 * hand in one place before this fix: the e-signature modal's Cancel
 * button had its own inline `style="margin-right:.5em;"` (portal-
 * script.js) purely to keep it off the Confirm button next to it. That
 * inline patch is now redundant and has been removed — the general fix
 * below covers it the same as everywhere else.
 *
 * FIX — every button gets real margin by default: `margin: .5em .6em
 * .5em 0` added to the base `.fps-portal button` rule, so spacing no
 * longer depends on incidental source whitespace, AND gives a button
 * breathing room from whatever block element (a `<form>`, `<h3>/<h4>`,
 * `<p class="fps-hint">`, ...) sits immediately above or below it —
 * closing the second half of the report ("spacing between buttons and
 * other text elements"). A `<form>`'s own last child (e.g. the Save
 * specification submit button) contributes this margin to the form's own
 * rendered height, so the very next sibling element still gets real
 * clearance even though the two aren't literally adjacent buttons.
 *
 * EXCEPTIONS — every established button GROUP in this file that already
 * manages its own spacing via flexbox `gap` needed an explicit `margin:
 * 0` override, or the new default margin would stack on top of that
 * `gap` and double the space: the tab bar (`.fps-tabs .fps-tab-btn` —
 * its existing `margin-bottom: -2px` underline trick is kept, just
 * folded into a full `margin: 0 0 -2px` so top/right/left are zeroed
 * too), the list toolbar's "Clear filters" button, the Prev/Next pager,
 * the printable-reports button bar, and the header's barcode-scan
 * button. The checklist run-list buttons (`width: 100%` rows, spaced by
 * their own `<li>`'s `margin-bottom`) and the signup form's full-width
 * submit button also needed `margin: 0` (keeping only the signup
 * button's existing `margin-top`) — a `width: 100%` button with a
 * nonzero *horizontal* margin would overflow its container, since
 * `width: 100%` sizes against the containing block and margin sits
 * outside that regardless of box-sizing.
 *
 * BRIGHTER "SUNRISE" ORANGE — `--fps-orange-700` (#D35400) and
 * `--fps-orange-900` (#A84300) get one more brightening pass on top of
 * v1.5.0's, per the explicit "more like a sunrise orange" request:
 * `--fps-orange-700: #E8600C` (was #D35400) and `--fps-orange-900:
 * #B34700` (was #A84300) — warmer and visibly lighter/more vivid, same
 * ~23° hue family so it reads as a brighter version of the existing
 * brand colour rather than a different one. `--fps-orange-500`/`400`/
 * `200`/`100`/`50` shifted the same direction for a consistent scale.
 * Every other use of these tokens is a `var()` reference and picks the
 * new colour up automatically; the three focus-ring `box-shadow`s that
 * hardcode the RGB triplet (same spot v1.5.0's own note above flags as
 * needing a manual update alongside the variable) were updated from
 * `rgba(211, 84, 0, ...)` to `rgba(232, 96, 12, ...)` to match.
 * Trade-off worth recording rather than glossing over: a brighter/
 * lighter background necessarily narrows the contrast margin against the
 * white bold text these buttons use (this is unavoidable — the previous
 * #D35400 was already only borderline WCAG-AA for body-sized text, and
 * #E8600C is a bit lower still). Bold weight and the button's own
 * box-shadow/hover lift keep it comfortably readable in practice, and
 * this app has always chosen "brighter, more true orange" over a stricter
 * contrast ratio at every one of its color passes (v1.4.4, v1.5.0, now
 * this one) — but if a future request is "make text easier to read on
 * the orange buttons," lowering `--fps-orange-700`'s lightness a touch
 * (not reverting the hue/saturation change) is the fix, not undoing this
 * brightening.
 */

:root {
    /* Light-orange brand scale (this plugin's equivalent of FinPress's
       --fp-teal-* scale) — deliberately prefixed --fps- (not --fp-) so
       there is no collision if a page ever loads both stylesheets. */
    /* v1.5.0: brighter, more saturated true-orange pair (was #9A4A18 /
       #C6672B — same hue family and similar lightness, but low enough
       saturation to read as brown). See the file-level v1.5.0 note
       above for the full rationale.
       v1.10.2: brightened again to a "sunrise orange" — see the
       file-level v1.10.2 note above for the full rationale and the
       readability trade-off it records. */
    --fps-orange-900: #B34700;
    --fps-orange-700: #E8600C;
    --fps-orange-500: #FF8A3D;
    --fps-orange-400: #FFA870;
    --fps-orange-200: #FFD9B3;
    --fps-orange-100: #FFEEDC;
    --fps-orange-50:  #FFF7ED;

    /* Grey scale — kept close to FinPress's own grey scale so the two
       products' body text and borders match exactly, not just the accent
       colour. */
    --fps-grey-900: #2E2E2E;
    --fps-grey-700: #4B4B4B;
    --fps-grey-500: #6B6B6B;
    --fps-grey-300: #9E9E9E;
    --fps-grey-200: #D0D5D8;
    --fps-grey-100: #F0F2F3;
    --fps-grey-50:  #F6F7F8;
    --fps-border:   #E0E4E6;

    /* Semantic status colours — unchanged from what this file already
       used (and, for pass/fail, already numerically identical to
       FinPress's own --fp-income/--fp-expense), since these convey
       meaning (pass/warn/fail) that shouldn't be recoloured just because
       the brand accent changed. */
    --fps-pass: #1A8A50;
    --fps-fail: #C0392B;
    --fps-warn: #B8860B;

    --fps-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

/*
 * Every <button>/<select>/<input>/<textarea> on this page gets the
 * surrounding text's font, full stop. Browsers don't inherit
 * font-size/font-family into form controls by default — they fall back
 * to their own UA-stylesheet default — which is why an <a> styled to
 * look like a button always matched the body text fine, while a real
 * <button> quietly didn't unless individually covered.
 *
 * Scoped to `.fps-portal`/`.fps-signup-form` rather than a bare element
 * selector, and `!important` on the font declaration specifically: a
 * bare element selector is exactly the kind of rule an active WordPress
 * theme's own stylesheet can quietly beat (equal-or-higher specificity,
 * or simply loaded later in <head>), and this plugin has no control over
 * a site's theme or load order. This was found happening for real on a
 * live WordPress install, so this rule (and every input/select/button
 * rule below it) is hardened the same way, on the one property (font)
 * that kept getting fought over.
 */
.fps-portal button,
.fps-portal select,
.fps-portal input,
.fps-portal textarea,
.fps-signup-form button,
.fps-signup-form select,
.fps-signup-form input,
.fps-signup-form textarea {
    font: inherit !important;
    font-family: var(--fps-font) !important;
}

/* ============================================================
   CONTAINER
   ============================================================ */

/* v1.13.22: explicit body-text baseline (user-reported, from a screenshot
   of the Ingredients table: "Change the type size of the body text
   throughout the app to be the same size of the text in the attached
   ingredient list"). Until now this file had no explicit `.fps-portal`
   font-size of its own — every plain, unstyled bit of text (paragraphs,
   list items, table-adjacent divs with no class of their own) simply fell
   back to whatever the active WordPress theme's own base font-size
   happened to be, which is NOT necessarily the same size this file's own
   `rem` values were written against, and is a different value on every
   site. `.9rem` is the exact size `.fps-table` already uses for its data
   rows — the Ingredients list in the screenshot — so setting it here
   makes every plain text node under `.fps-portal` match that table by
   simple inheritance, with no separate rule needed per element. Anything
   that already sets its own explicit font-size (headings, buttons, the
   nav bar, badges/pills, the dashboard stat grid) is unaffected — an
   element's own declared size always wins over an inherited one — so
   this only reaches text that had no explicit size before. A handful of
   rules THAT DID have their own explicit (and, it turns out, mismatched)
   size are fixed individually elsewhere in this file — see the v1.13.22
   changelog entry in finpress-food-safety.php for the full list. */
.fps-portal {
    max-width: 1150px;
    margin: 1.5em auto;
    padding: 1.75em;
    background: var(--fps-grey-50);
    border-radius: 10px;
    font-family: var(--fps-font);
    font-size: .9rem;
    color: var(--fps-grey-700);
    box-sizing: border-box;
}

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

/* v1.5.0: `.fps-portal` sets font-family on the container and every
   descendant was expected to simply inherit it — but a direct rule on
   an element always wins over an inherited value, no matter its
   specificity. This site's active theme apparently styles bare
   headings and paragraphs directly with its own (serif) font, which is
   why headings and hint text were rendering in the theme's font instead
   of this plugin's. Forced the same way `.fps-notice, .fps-notice *`
   already handles it, excluding `pre`/`code`/`kbd`/`samp` so any
   preformatted report text (see `.fps-template-card pre`) keeps a
   monospace-appropriate font rather than being forced sans-serif. */
.fps-portal,
.fps-portal *:not(pre):not(code):not(kbd):not(samp),
.fps-signup-form,
.fps-signup-form *:not(pre):not(code):not(kbd):not(samp) {
    font-family: var(--fps-font) !important;
}

.fps-portal h1, .fps-portal h2, .fps-portal h3, .fps-portal h4 {
    color: var(--fps-grey-900);
    font-weight: 500;
}

/* v1.14.14: direct instruction, with a screenshot showing "Add Ingredient"/
   "Create Recipe" rendering small next to the much larger product-name
   heading ("Cheesecake (CC-9910)") elsewhere on the same tab — "All the
   method headers in the app... should be the size of the Cheescake
   header." A size-matching change was drafted (an explicit, theme-
   independent font-size on a new shared class) but then explicitly
   walked back before most of the app was touched: "Wait, roll back
   heading size change. Do NOT change size, just change colour" —
   followed by "Also change the method headers' colour to Orange."
   `.fps-action-heading` therefore only sets colour, deliberately leaving
   font-size/font-weight alone (each such heading keeps whatever size its
   own <h3>/<h4> tag and the active theme already give it — unchanged
   from before this version). A "method header" is a heading that itself
   names a single action to take right here ("Add a CCP," "Create a
   production order," "Restore"), as opposed to a heading merely naming a
   table, report, or existing record ("Critical Control Points,"
   "Compliance status report," a product's own name) — those are
   unaffected. `!important` for the same reason `.fps-portal`'s own
   font-family rule above already needs it: a bare tag selector in the
   active theme's own stylesheet can still outrank a plugin class
   selector on specificity alone. */
.fps-portal .fps-action-heading {
    color: var(--fps-orange-700) !important;
}

/* ============================================================
   HEADER + TIER BADGE
   ============================================================ */

/* v1.14.29: per-instance business logo, shown above the header only for
   an instance that has uploaded one (see templates/portal-dashboard.php
   and the Site Profile tab's upload control). Capped height rather than
   width so a wide or a tall logo both stay a sane, consistent size next
   to the business name below it. */
.fps-instance-logo-wrap {
    text-align: center;
    margin-bottom: 1em;
}

.fps-instance-logo {
    max-height: 90px;
    max-width: 100%;
    height: auto;
    width: auto;
}

.fps-logo-preview-img {
    max-height: 90px;
    max-width: 260px;
    height: auto;
    width: auto;
    display: block;
    margin-bottom: .5em;
    border: 1px solid var(--fps-orange-200);
    border-radius: 4px;
    padding: .5em;
    background: #fff;
}

.fps-portal-header {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: .75em;
    border-bottom: 2px solid var(--fps-orange-700);
    padding-bottom: .75em;
    margin-bottom: 1.25em;
}

.fps-portal-header h1 {
    font-size: 1.75rem;
    font-weight: 500;
    margin: 0;
    letter-spacing: .2px;
}

.fps-portal-header > div {
    display: flex;
    align-items: center;
    gap: .75em;
    flex-wrap: wrap;
}

/* v1.10.2: the barcode-scan button lives in here (see
   templates/portal-dashboard.php) — this row already spaces itself with
   `gap`, so zero the base button rule's own margin rather than double up. */
.fps-portal-header > div button {
    margin: 0;
}

.fps-tier-badge {
    display: inline-block;
    background: var(--fps-orange-50);
    border: 1px solid var(--fps-orange-200);
    color: var(--fps-orange-900);
    font-weight: 400;
    font-size: .75rem;
    letter-spacing: .4px;
    padding: .4em .8em;
    border-radius: 999px;
    margin: 0;
}

/* ============================================================
   TABS
   ============================================================ */

/* v1.13.3: the flat ~20-tab bar (`.fps-tabs`) was reorganized into a
   grouped dropdown menu, per explicit feedback that its order "seems
   random." A lighter "just add labels/dividers, keep everything one
   click away" grouping was tried first and explicitly rejected; this
   dropdown design was confirmed against a static mockup screenshot
   before any of this real CSS was written. Category buttons
   (`.fps-nav-cat`) replace the old flat row of ~20 `.fps-tab-btn`s at
   the top level; each opens a `.fps-nav-dropdown` panel containing that
   group's actual `.fps-tab-btn`s — see the big comment above the <nav>
   markup in portal-dashboard.php for exactly which tab is in which
   group and why, and portal-script.js's own new comment block for the
   open/close + active-category-highlighting logic. */
.fps-nav-categories {
    display: flex;
    flex-wrap: wrap;
    row-gap: .2em;
    column-gap: .25em;
    margin-bottom: 1.5em;
    border-bottom: 2px solid var(--fps-border);
}

.fps-nav-cat-wrap {
    position: relative;
}

/* Scoped as `.fps-nav-categories .fps-nav-cat` (two classes), not a
   bare `.fps-nav-cat` — same reasoning as the old `.fps-tabs
   .fps-tab-btn` rule this replaces: a single class selector (0,1,0)
   would lose to the generic `.fps-portal button` rule's class+element
   specificity (0,1,1), letting the boxed default button look win over
   this flat, underlined design. Two classes (0,2,0) beats it outright,
   no !important needed. */
.fps-nav-categories .fps-nav-cat {
    background: none;
    border: none;
    border-bottom: 3px solid transparent;
    box-shadow: none;
    padding: .65em 1.05em;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: .35em;
    /* Matches the pre-v1.13.3 tab bar's own explicit choice to stay at
       the app's original 1rem, never the smaller action-button size —
       see that rule's own v1.4.2 note, carried forward unchanged. */
    font-size: 1rem !important;
    font-weight: 500;
    color: var(--fps-grey-500);
    margin: 0 0 -2px;
    white-space: nowrap;
    transition: color .15s ease, border-color .15s ease;
}

.fps-nav-categories .fps-nav-link {
    display: inline-flex; align-items: center;
    padding: .65em 1.05em; margin: 0 0 -2px;
    border-bottom: 3px solid transparent;
    font-size: 1rem; font-weight: 500; white-space: nowrap;
    color: var(--fps-grey-500); text-decoration: none;
}
.fps-nav-categories .fps-nav-link:hover,
.fps-nav-categories .fps-nav-link:focus { color: var(--fps-grey-900); text-decoration: none; }

.fps-nav-categories .fps-nav-cat:hover {
    color: var(--fps-grey-900);
}

.fps-nav-categories .fps-nav-cat.active {
    border-bottom-color: var(--fps-orange-700);
    color: var(--fps-orange-700);
}

.fps-nav-chevron {
    font-size: .65em;
    transition: transform .15s ease;
}

.fps-nav-cat-wrap.open .fps-nav-chevron {
    transform: rotate(180deg);
}

.fps-nav-dropdown {
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    display: flex;
    flex-direction: column;
    background: #fff;
    border: 1px solid var(--fps-border);
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, .12);
    padding: .4em;
    min-width: 210px;
    /* Above the sticky table-header z-index (2) elsewhere in this file,
       well below the e-signature/tier-upgrade modal overlays (9999) —
       a dropdown should never need to out-rank a full-page modal. */
    z-index: 20;
}

/* The `hidden` attribute alone isn't enough to guarantee this stays
   hidden: this file's own `.fps-nav-dropdown { display: flex; }` rule
   above is an AUTHOR-stylesheet rule, and author rules beat the
   browser's built-in `[hidden] { display: none; }` default on an
   equal-specificity tie (author origin always wins a UA-vs-author tie
   in the cascade, regardless of selector specificity) — so without this
   rule, every dropdown would render permanently open. Scoping as
   `.fps-nav-dropdown[hidden]` (class+attribute, 0,2,0) beats the bare
   `.fps-nav-dropdown` rule (0,1,0) outright, closing that gap. */
.fps-nav-dropdown[hidden] {
    display: none;
}

/* Scoped as `.fps-nav-dropdown .fps-tab-btn` (two classes) for the same
   specificity reason as `.fps-nav-categories .fps-nav-cat` above — this
   replaces the old `.fps-tabs .fps-tab-btn` rule now that the actual
   per-tab buttons live inside a dropdown panel instead of a flat bar,
   so they're styled as a vertical menu list instead of underlined tabs. */
.fps-nav-dropdown .fps-tab-btn {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    box-shadow: none;
    padding: .55em .7em;
    margin: 0;
    font-size: .92rem !important;
    font-weight: 500;
    color: var(--fps-grey-700);
    border-radius: 4px;
    cursor: pointer;
    white-space: nowrap;
}

.fps-nav-dropdown .fps-tab-btn:hover {
    background: var(--fps-grey-50);
}

.fps-nav-dropdown .fps-tab-btn.active {
    background: var(--fps-orange-50);
    color: var(--fps-orange-700);
}

.fps-badge {
    background: var(--fps-fail);
    color: #fff;
    border-radius: 999px;
    padding: .1em .55em;
    font-size: .72em;
    margin-left: .35em;
}

/* v1.13.0 — nav "upgrade roadmap" lock badge (see $fps_nav_lock() in
   portal-dashboard.php). Deliberately muted/grey rather than the brand
   orange used for real notification counts (.fps-badge above) — this is
   informational ("here's what's ahead"), not something demanding
   attention the way an overdue count is. Sits on the same tab-bar
   buttons as .fps-badge but never both at once on this build (no
   feature is both tier-gated and count-badged today), so no explicit
   spacing rule between them is needed yet. */
/* v1.13.2: icon-only now (the tier name moved to the `title` tooltip and
   a `data-tier-name` attribute portal-script.js reads for the click-
   triggered upgrade modal — see initTabs() there) — was icon + tier-name
   text through v1.13.0/v1.13.1. `cursor: help` hints there's a tooltip;
   the nav button itself already carries the real click behaviour. */
/* v2.8.7: the tier-lock badge on a restricted nav item was a very faint
   grey pill with a thin lock glyph that read as an empty box. Brightened to
   an amber "upgrade" badge using the brand orange tokens — a clearly visible
   premium/locked marker rather than a ghost. */
.fps-tier-lock {
    display: inline-block;
    background: var(--fps-orange-100);
    color: var(--fps-orange-900);
    border: 1px solid var(--fps-orange-400);
    border-radius: 999px;
    padding: .1em .45em;
    font-size: .8em;
    font-weight: 600;
    margin-left: .35em;
    white-space: nowrap;
    cursor: help;
}

/* ============================================================
   TAB PANELS AS CARDS
   ============================================================ */

.fps-tab-panel {
    background: #fff;
    border: 1px solid var(--fps-border);
    border-radius: 8px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
    padding: 1.5em;
    margin-bottom: 1.5em;
}

/* v1.4.4 fix: this used to read `.fps-tab-panel > h2:first-child,
   .fps-tab-panel > h2 { ... margin-top: 0; ... }` — since the second,
   unqualified selector matches every h2 regardless of position, EVERY
   h2 in a panel got margin-top:0, not just the first one. That's why a
   second heading in the same panel (e.g. "Recent readings" right after
   the "Log a reading" form's submit button, or "Recent submissions"
   after the checklist list) sat with no breathing room above it at all.
   Now only the first h2 in a panel zeroes its top margin; every other
   h2 gets real spacing above it. */
.fps-tab-panel > h2 {
    font-size: 1.375rem;
    font-weight: 500;
    color: var(--fps-grey-900);
    margin-top: 1.75em;
    margin-bottom: .85em;
    padding-bottom: .5em;
    border-bottom: 1px solid var(--fps-border);
}

.fps-tab-panel > h2:first-child {
    margin-top: 0;
}

/* v1.03: section headings inside a tab are 20px, in the brand orange (the
   same orange as the .fps-action-heading "method headers"). Spacing is in em,
   so it scales with the heading: 30px above, 14px below at 20px. line-height
   is fixed so the space doesn't vary with the WordPress theme's body
   line-height. font-size and colour carry !important for the same reason
   .fps-action-heading's colour does: a theme's own heading rule can otherwise
   outrank a plugin class selector. Headings that set their own margin-top
   inline (1.5em / 2em) or are the panel's first child are unchanged. */
.fps-tab-panel h3 {
    font-size: 20px !important;
    font-weight: 500;
    line-height: 1.3;
    color: var(--fps-orange-700) !important;
    margin: 1.5em 0 .7em;
}

.fps-tab-panel h3:first-child {
    margin-top: 0;
}

/* ============================================================
   BUTTONS
   ============================================================ */

.fps-portal button,
.fps-signup-form button,
.fps-portal .button,
.fps-signup-form .button,
.fps-portal a.button {
    display: inline-block;
    /* v1.10.2: every button gets real spacing by default — see the
       file-level v1.10.2 note above for the full root-cause/rationale.
       Right+bottom margin covers "button next to button" (whether they
       wrap to a new line or sit on the same one); top margin covers
       "button right after a heading/paragraph/form". Left margin is 0
       so a button that's the first thing in its row/container doesn't
       get pushed in from the edge. Every established button GROUP that
       already spaces itself via flexbox `gap` overrides this back to
       `margin: 0` below (or, for the tab bar, folds it into that rule's
       own margin) — see the file-level note for the full list. */
    margin: .5em .6em .5em 0;
    /* v1.5.3: user-reported, from a screenshot of Manage checklists
       where "Rename"/"Manage items" (this base tier) stood visibly
       taller than "Delete" (the "compact" tier below): "Buttons all
       need to be same size, irrespective of colour... I mean height,
       not width." Every button in the app — regardless of its role
       (primary orange, light-orange secondary, destructive red) or
       which of the two size "tiers" it used to belong to — now shares
       one padding/font-size, standardized on the smaller of the two
       (what used to be the "compact inline action" tier, e.g. the
       Delete button in the screenshot), per the user's explicit choice.
       This is why the separate "Compact action buttons" rule further
       below no longer sets its own padding/font-size — those buttons
       now get identical sizing from this rule directly, nothing left to
       override. Different widths across buttons are expected and fine
       (a longer label needs more horizontal room) — only HEIGHT needed
       to be made consistent, and height comes from font-size + vertical
       padding + line-height, not from the horizontal padding a longer
       label pushes wider.
       Deliberately NOT touched: the top tab bar and the checklist
       run-list (`.fps-portal .fps-checklist-list button`) — both are
       established, non-"action-button" exceptions to every button-
       sizing change in this file since v1.4.2/v1.4.3 (navigation and a
       list of things to open, not commit actions) — and the signup/
       login full-width submit buttons, which are their own separate,
       deliberately larger call-to-action element on a public-facing
       form/login card, not part of the in-app action-button family the
       screenshot and this whole audit are about. */
    padding: .38em .85em;
    /* v1.5.0: was a white/grey "ghost" button (border-grey-200,
       background #fff, grey-900 text) — user-reported "multiple small
       buttons with white backgrounds; these should have a light orange
       fill." Now a light-orange fill, matching the light-orange panel
       language already used elsewhere in this file (.fps-tier-badge,
       .fps-coming-soon, .fps-scale-calc). The solid-orange "commit"
       buttons, the destructive (red) buttons, the tab bar and the
       checklist run-list all set their own background/border/color
       explicitly with higher specificity, so none of them are affected
       by this change. */
    border: 1px solid var(--fps-orange-200);
    border-radius: 6px;
    background: var(--fps-orange-100);
    color: var(--fps-orange-900);
    /* v1.4.1 reduced this .9rem -> .675rem (25% smaller button text, per
       user request); v1.4.2 adds !important. Without it, a live site's
       active theme reliably wins this property on a bare `button`
       element — the exact same low-specificity problem this file's own
       v0.4.3/v0.4.4 notes already document for font/font-size on generic
       form controls — which is why the tab bar (already !important)
       visibly shrank while these action buttons (Save/Create/Add/
       Approve, previously with no !important here) did not.
       v1.5.2: user-reported button text too small again — raised to
       `.85rem`, i.e. 85% of this file's implicit 1rem body-text baseline
       (this stylesheet has no explicit `.fps-portal` font-size of its
       own, so its `rem` units already ARE relative to the page's body
       text size). This is between the original pre-reduction `.9rem`
       (90% of body text) and the over-reduced `.675rem` (67.5%). The
       compact-inline and signup/login submit button tiers below were
       both rescaled by the same 85%-of-body target, preserving their
       original (pre-v1.4.1) size *relative to this base tier* — compact
       buttons were always ~91% of this size (`.82rem` vs `.9rem`
       originally), signup/login submit buttons always ~111% of it
       (`1rem` vs `.9rem` originally) — rather than inventing new,
       unrelated ratios.
       v1.5.3: superseded — see the padding comment above. This tier and
       the "compact" tier are now the same size (`.775rem`), so this
       value moved from `.85rem` down to match. The signup/login ratio
       note above is now historical: that tier is no longer scaled off
       this one (see the padding comment), it's just left at its v1.5.2
       value (`.95rem`). */
    font-size: .775rem !important;
    font-weight: 500;
    text-decoration: none;
    cursor: pointer;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
    transition: transform .15s ease, box-shadow .15s ease, background-color .15s ease, border-color .15s ease, color .15s ease;
}
/* v1.13.49: this rule's own `display: inline-block` above (no !important)
   is an AUTHOR style, so — per the normal CSS cascade — it always wins
   over the browser's built-in `[hidden] { display: none }` UA rule of
   equal or lower specificity, regardless of which one appears "more
   specific" looking at either alone. In practice this means setting
   `someButton.hidden = true` in JS silently does nothing to any button
   inside `.fps-portal` — it stays fully visible. Every other element this
   codebase toggles via `.hidden` (div, pre, textarea) has no such
   conflicting `display` rule, so it was never affected by this until the
   SOP Library's Edit toggle became the first ever `<button>` hidden this
   way — found empirically (a real headless-Chromium click-through, not
   code review) after a report that the Library tab's SOP drafts were
   "read-only" after clicking Edit: the original title button was never
   actually disappearing, sitting on top of/behind the new edit fields,
   still fully clickable and still wired to its own show/hide-the-draft
   handler, so a stray click on it could re-reveal the untouched read-only
   text and make an edit in progress look like it had been discarded.
   Fixed at this shared rule's own level, not with a one-off inline style
   on that one button, so `.hidden` reliably hides ANY button in the app
   from here on — guarding against the identical bug the next time some
   other feature hides a button this way. */
.fps-portal button[hidden] {
    display: none !important;
}

.fps-portal button:hover,
.fps-signup-form button:hover,
.fps-portal .button:hover,
.fps-signup-form .button:hover,
.fps-portal a.button:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
    border-color: var(--fps-orange-400);
    /* v1.5.0: a touch darker on hover now that the resting state is
       already a light-orange fill, not white. The solid-orange/
       destructive buttons below both declare their own :hover
       background with higher specificity, so this only affects the
       light-orange secondary buttons this rule is otherwise for. */
    background: var(--fps-orange-200);
}

.fps-portal button:active,
.fps-signup-form button:active {
    transform: translateY(0);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}

.fps-portal button:disabled,
.fps-signup-form button:disabled {
    opacity: .55;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}

/* Primary ("commit this") actions: form submits, and the JS-added
   save/calculate/approve buttons — every one of these already exists as
   a class hook in portal-script.js or the templates, so no markup
   change is needed to pick them up here.
   Every non-attribute, non-id selector below is scoped under
   `.fps-portal` — the same specificity reason as the tab-bar fix above:
   a bare class like `.fps-team-save` is (0,1,0), which loses outright
   to the generic `.fps-portal button` rule's (0,1,1), regardless of
   which is declared first. `.fps-portal .fps-team-save` is (0,2,0),
   which wins. `#fps-esig-confirm` needs no such scoping — an id
   selector's specificity (1,0,0) already beats (0,1,1) on its own, and
   in any case that modal is appended to <body>, outside `.fps-portal`
   entirely, so `.fps-portal #fps-esig-confirm` would never even match.
   (.fps-run-checklist is deliberately NOT in this list — it's the same
   button `.fps-checklist-list button` below already styles as a full-
   width list item, not a small "commit" action.) */
.fps-portal button[type=submit],
.fps-signup-form button[type=submit],
.fps-portal .fps-team-save,
.fps-portal .fps-scale-btn,
.fps-portal .fps-approve-document,
.fps-portal .fps-approve-haccp-plan,
.fps-portal .fps-approve-recipe,
.fps-portal .fps-approve-bom,
.fps-portal .fps-approve-spec,
#fps-esig-confirm,
#fps-tier-upgrade-confirm,
/* v1.5.1: every selector below this point is `type="button"` (posted
   via a plain JS click handler, not a form submit) but each one directly
   performs the one main, record-changing action of its own section/
   panel — Create a backup, generate one of the three printable reports,
   start a production order, save a status change, or save a checklist/
   item — the same weight as the `.fps-scale-btn`/`.fps-approve-*`
   actions already in this list, just never added here. User-reported: a
   screenshot of the Backup & Restore panel where "Create backup now"
   (missed) sat next to "Restore from this file" (already correctly
   primary here via `button[type=submit]`) in two different button
   colours. `#fps-cancel-order` (the one other direct-POST `type=
   "button"` action nearby) is deliberately NOT added — it's the "abort"
   counterpart to `#fps-start-order`, the same correctly-de-emphasized
   relationship a Cancel button has to whatever it's cancelling elsewhere
   in this file; and `.fps-update-ncr`/`.fps-raise-capa-ncr`/
   `.fps-update-complaint`/`.fps-raise-ncr-complaint` are also
   deliberately NOT added — despite the "Update"/"Raise" label, each one
   only reveals a hidden inline form, and that form's own `Save`/`Raise
   CAPA` button is a real `button[type=submit]` already correctly
   primary. */
#fps-backup-export-btn,
#fps-print-haccp-report,
#fps-print-batch-report,
#fps-print-compliance-report,
#fps-start-order,
#fps-product-status-save,
.fps-portal .fps-cl-save-btn,
.fps-portal .fps-item-save-btn {
    background: var(--fps-orange-700);
    border-color: var(--fps-orange-700);
    color: #fff;
    /* v1.4.4: only the solid-orange "commit" buttons get bold text — see
       the file-level note near the top of this stylesheet for the full
       bold/uppercase policy.
       v1.5.0: needs !important. `.fps-portal button { font: inherit
       !important; ... }` above sets the *shorthand* `font` with
       !important, which expands to font-weight: inherit !important too
       — a non-!important font-weight here can never win against that,
       so this text was silently never actually bold. See the file-level
       v1.5.0 note for the full explanation. */
    font-weight: 700 !important;
}

.fps-portal button[type=submit]:hover,
.fps-signup-form button[type=submit]:hover,
.fps-portal .fps-team-save:hover,
.fps-portal .fps-scale-btn:hover,
.fps-portal .fps-approve-document:hover,
.fps-portal .fps-approve-haccp-plan:hover,
.fps-portal .fps-approve-recipe:hover,
.fps-portal .fps-approve-bom:hover,
.fps-portal .fps-approve-spec:hover,
#fps-esig-confirm:hover,
#fps-tier-upgrade-confirm:hover,
#fps-backup-export-btn:hover,
#fps-print-haccp-report:hover,
#fps-print-batch-report:hover,
#fps-print-compliance-report:hover,
#fps-start-order:hover,
#fps-product-status-save:hover,
.fps-portal .fps-cl-save-btn:hover,
.fps-portal .fps-item-save-btn:hover {
    background: var(--fps-orange-900);
    border-color: var(--fps-orange-900);
    color: #fff;
}

/* v1.13.24: the Lot register's Certificate column — "— none" in the
   neutral grey everything else in this cell's muted text uses,
   "(required)" called out in the same red as a failed/rejected row
   (--fps-fail) so a lot whose material calls for a certificate but
   doesn't have one yet is visible at a glance without a modal or a
   second click. */
.fps-lot-cert-cell {
    white-space: nowrap;
}
.fps-lot-cert-none {
    color: var(--fps-grey-500);
}
.fps-lot-cert-required {
    color: var(--fps-fail);
    font-weight: 600;
}
.fps-lot-cert-attach-btn,
.fps-lot-cert-remove-btn {
    margin-left: .4em;
}

/* v1.13.25: the "Log a delivery" form's certificate field stays visible
   at all times (previously hidden outright until a raw material + lot
   were selected, which made it too easy to never find) — this is just
   the visual "greyed out" state while its <input> is disabled, matching
   the native disabled-control look so it still reads as temporarily
   unavailable rather than broken. */
.fps-log-delivery-cert-wrap-disabled label {
    color: var(--fps-grey-500);
}
.fps-log-delivery-cert-wrap-disabled input[type="file"] {
    cursor: not-allowed;
}

/* Destructive-only actions — a real, permanent removal, not merely
   "close" or "cancel" (which stay neutral below, since closing a CAPA or
   cancelling a modal isn't something to visually flag as dangerous).
   v1.5.2: `.fps-checklist-delete-btn`/`.fps-item-delete-btn` (Manage
   checklists) added — user-confirmed, flagged in the v1.5.1 changelog
   as "found but not changed pending your input": both confirm-then-
   permanently-remove a record exactly like the four buttons already
   here, just missed when Manage checklists shipped in v1.4.0. */
.fps-portal .fps-remove-ccp,
.fps-portal .fps-delete-document,
.fps-portal .fps-remove-row,
.fps-portal .fps-team-remove,
.fps-portal .fps-checklist-delete-btn,
.fps-portal .fps-item-delete-btn {
    background: #fff;
    border-color: var(--fps-fail);
    color: var(--fps-fail);
}

.fps-portal .fps-remove-ccp:hover,
.fps-portal .fps-delete-document:hover,
.fps-portal .fps-remove-row:hover,
.fps-portal .fps-team-remove:hover,
.fps-portal .fps-checklist-delete-btn:hover,
.fps-portal .fps-item-delete-btn:hover {
    background: var(--fps-fail);
    border-color: var(--fps-fail);
    color: #fff;
}

/* "Compact inline action buttons" — v1.5.0 through v1.5.2, this rule
   gave a specific list of small buttons that live inline in a table row
   or a small form (Remove/Delete/Save/Open/Update/Raise/etc. — the full
   list is preserved below for reference) their own smaller padding and
   font-size than the app's page-level buttons (Save product, Create
   checklist, Create backup now, and the like).
   v1.5.3: removed. User-reported, from a screenshot of Manage checklists
   where this tier's "Delete" button visibly stood shorter than the
   page's other buttons: "Buttons all need to be same size... I mean
   height, not width." Rather than keep two tiers and pick which one
   wins, the base `.fps-portal button` rule above was standardized on
   THIS tier's own (smaller) padding/font-size — so every button in the
   app, page-level or inline, is now the same height by default, and
   this rule would just be redeclaring the same values the base rule
   already sets. Kept here as a comment, not deleted outright, so the
   list of buttons this used to specifically call out isn't lost:
   .fps-remove-ccp, .fps-delete-document, .fps-remove-row, .fps-add-row,
   .fps-team-remove, .fps-team-save, .fps-scale-btn, .fps-close-capa,
   .fps-close-audit-finding, .fps-raise-capa-ncr, .fps-raise-ncr-
   complaint, .fps-update-ncr, .fps-update-complaint, .fps-open-order,
   .fps-open-product, .fps-approve-document, #fps-product-status-save,
   .fps-cl-save-btn, .fps-item-save-btn, .fps-checklist-delete-btn,
   .fps-item-delete-btn. If a future change needs a distinct size for
   any of these again, re-introduce a rule for that specific selector
   rather than reviving this whole shared tier. */

.fps-portal .fps-checklist-list button {
    width: 100%;
    /* v1.10.7: fills the height the 3-column grid above stretches this
       button's <li> to (grid's default align-items: stretch makes every
       <li> in a row match its tallest sibling) — without this, a button
       whose own text doesn't wrap would sit at its natural (shorter)
       content height inside a taller stretched <li>, floating in a gap
       at the top of its grid cell instead of visually matching the
       taller row-mate that DID wrap onto two lines. */
    height: 100%;
    text-align: left;
    padding: .85em 1.1em;
    background: #fff;
    border: 1px solid var(--fps-border);
    border-left: 3px solid var(--fps-orange-400);
    border-radius: 6px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
    /* v1.10.2: zeroes the base button rule's new default margin. This
       row is already `width: 100%` and already spaced from its neighbours
       by its own `<li>`'s `margin-bottom` (see .fps-checklist-list li
       below) — a nonzero horizontal margin here would overflow the `<li>`
       (100% width is sized against the container; margin sits outside
       that), and a nonzero vertical margin would double up with the
       `<li>`'s own spacing. */
    margin: 0;
    /* v1.4.3: explicitly held at the original size. This is a full-width
       row in a list of things to open/run (like the tab bar above), not
       a small action button — the user asked for it excluded from the
       button-text reduction, the same way the tab bar was. Needs its own
       !important + higher specificity than the base `.fps-portal button`
       rule (which is also !important as of v1.4.2), or it would silently
       inherit that rule's reduced size. */
    font-size: .9rem !important;
}

.fps-portal .fps-checklist-list button:hover {
    border-left-color: var(--fps-orange-700);
    background: var(--fps-orange-50);
}

.fps-reports-bar {
    margin-bottom: 1.25em;
    display: flex;
    gap: .5em;
    align-items: center;
    flex-wrap: wrap;
}

/* v1.10.2: already spaced by the row's own `gap` — zero the base button
   rule's margin rather than double up. */
.fps-portal .fps-reports-bar button {
    margin: 0;
}

/* ============================================================
   FORMS — labels, inputs, selects, textareas
   ============================================================ */

form label {
    display: block;
    margin-bottom: .6em;
    color: var(--fps-grey-700);
    font-size: .9rem;
}

.fps-portal form input[type=text],
.fps-portal form input[type=number],
.fps-portal form input[type=email],
.fps-portal form input[type=date],
.fps-portal form input[type=file],
.fps-portal form input[type=password],
.fps-portal select,
.fps-signup-form form input[type=text],
.fps-signup-form form input[type=email],
.fps-signup-form form input[type=password],
.fps-signup-form select {
    /* v1.5.0: every field in this app is written as
       `<label>Caption <input …></label>`, with the label itself already
       `display: block` (see the `form label` rule below) but the input
       left at its default `inline-block` — so the caption text and the
       field sat side by side on the same line, and since caption length
       varies from field to field ("Pack size" vs. "Customer/brand (if
       private label)"), every field in a form started at a different
       horizontal position instead of lining up under one another. This
       is what "heading/typesize, alignment, layout etc [is] messy" was
       showing on the Products tab (and is true of every other form in
       the app, built the same way). `display: block` drops the field
       onto its own line below the caption — consistently, everywhere —
       which is what the existing `margin-top: .3em` below was already
       written for. `.fps-scale-calc input` and `.fps-table select`
       explicitly stay inline — see their own rules further down. */
    display: block;
    padding: .55em .7em;
    margin-top: .3em;
    width: 100%;
    max-width: 320px;
    border: 1px solid var(--fps-grey-200);
    border-radius: 4px;
    background: #fff;
    color: var(--fps-grey-900);
    /* v1.13.22: explicit now, rather than left to inherit from whatever
       wraps a given field — most fields in this app are written as
       `<label>Caption <input …></label>` so this happened to inherit the
       label's own .9rem anyway, but a bare `<select>` with no wrapping
       label (nothing else in this rule guarantees one) would otherwise
       fall back to the page's implicit base size instead. A `<select>`'s
       `<option>` list inherits its font-size from the `<select>` itself,
       so this is also what makes every dropdown's OPTION text (not just
       the closed control) match the v1.13.22 body-text baseline —
       user-reported: "Make sure u include the text in the dropdowns." */
    font-size: .9rem;
    transition: border-color .15s ease, box-shadow .15s ease;
}

/* v1.13.54 — show/hide toggle for every password field (see
   assets/js/fps-password-toggle.js, which wraps each `input[type=
   password]` with this markup). Deliberately unscoped (no `.fps-portal`/
   `.fps-signup-form` prefix) because it also has to work on the LOGIN
   form, which is WordPress core's own wp_login_form() output sitting
   inside a plain `.fps-notice` div — none of the scoped input rules
   above apply there at all, so this can't lean on them for sizing either
   and sets its own width/display instead of inheriting it. */
.fps-pw-wrap {
    position: relative;
    display: block;
    width: 100%;
    max-width: 320px;
}
.fps-pw-wrap .fps-pw-input {
    margin-top: 0;
    width: 100%;
    max-width: none;
    padding-right: 2.5em;
}
.fps-pw-toggle {
    position: absolute;
    top: 50%;
    right: .35em;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.9em;
    height: 1.9em;
    padding: 0;
    border: 0;
    border-radius: 4px;
    background: none;
    color: var(--fps-grey-500);
    cursor: pointer;
}
.fps-pw-toggle:hover,
.fps-pw-toggle:focus-visible {
    color: var(--fps-grey-900);
    background: var(--fps-grey-50);
}

/* v1.13.12: split out from the shared input/select rule above, which had
   been capping every <textarea> in the app (product specifications,
   recipe processing instructions, batch deviations, etc.) at the SAME
   320px used for a one-line field like "Pack size" — reported directly
   against the product Specification panel ("Expand the width of these
   specification panels. They way too small.") with a screenshot showing
   a narrow, 2-row box swimming in a mostly-empty wide panel. Multi-line
   content needs materially more width AND height than a single-line
   input to be usable, so textareas get their own, much larger box
   instead of inheriting the input/select sizing — this fixes every
   textarea in the app the same way, not just the Specification panel,
   since they all shared the identical cap. */
.fps-portal textarea {
    display: block;
    padding: .55em .7em;
    margin-top: .3em;
    width: 100%;
    max-width: 720px;
    min-height: 6em;
    border: 1px solid var(--fps-grey-200);
    border-radius: 4px;
    background: #fff;
    color: var(--fps-grey-900);
    transition: border-color .15s ease, box-shadow .15s ease;
}

.fps-portal form input[type=text]:focus,
.fps-portal form input[type=number]:focus,
.fps-portal form input[type=email]:focus,
.fps-portal form input[type=date]:focus,
.fps-portal form input[type=password]:focus,
.fps-portal select:focus,
.fps-portal textarea:focus,
.fps-signup-form form input:focus,
.fps-signup-form select:focus {
    outline: none;
    border-color: var(--fps-orange-700) !important;
    box-shadow: 0 0 0 3px rgba(232, 96, 12, 0.15) /* v1.10.2: matches new --fps-orange-700 #E8600C */;
}

/* Selects that live bare inside a table cell (status-change dropdowns),
   not inside a <form> — these need to stay compact, not stretch to the
   320px form-field width above. */
.fps-table select {
    /* v1.5.0: explicit now that the base rule above sets display: block
       — these are bare status-change dropdowns sitting alone in a table
       cell (not `<label>Text <select>`), and were already effectively
       inline-block before; kept that way on purpose so a lone dropdown
       in a cell stays compact rather than stretching to the cell width. */
    display: inline-block;
    width: auto;
    max-width: none;
    padding: .3em .5em;
    /* v1.13.22: was .85rem, its own bare size that didn't match the rest
       of the table it sits in — bumped to .9rem to match `.fps-table`'s
       own data-row size (see that rule and the v1.13.22 `.fps-portal`
       comment above). */
    font-size: .9rem;
}

/* v1.14.13: direct instruction, with a screenshot of the recipe editor's
   Ingredients table — "the Qty and % [inputs] are too narrow to input
   data." `width: auto` with only a max-width and no floor left these
   number inputs (Qty/`fps-ing-qty`, %/`fps-ing-pct` — same shared
   .fps-rows-table markup the BOM packaging table below it also uses)
   entirely at the mercy of the table's own auto-layout column-sizing:
   with the Name column's <select> and the Remove button both wanting
   real estate, the browser was free to shrink the Qty/% columns down to
   barely wider than their own header text, well below what's usable for
   typing an actual quantity. A min-width gives them a floor no amount of
   column-squeezing can go under, while the existing max-width still
   keeps them from stretching wider than a number field needs. */
.fps-rows-table input {
    width: auto;
    min-width: 4.5em;
    max-width: 140px;
}

/* v1.13.15: .fps-scale-calc/.fps-scale-qty/.fps-scale-result removed —
   the "Scale to quantity" calculator no longer exists on the recipe
   editor. Scaling a recipe to an actual production quantity, and the
   resulting Bill of Materials, is now generated only on the Production
   tab when a production order is created (see renderOrderDetail() in
   portal-script.js and FPS_Core::calculate_requirements()'s docblock). */

.fps-team-flag-grid {
    display: flex;
    flex-wrap: wrap;
    gap: .5em 1.25em;
    margin: .75em 0 1.25em;
    padding: .85em 1em;
    background: var(--fps-grey-50);
    border: 1px solid var(--fps-border);
    border-radius: 6px;
}

.fps-team-flag-grid label {
    display: inline-flex;
    align-items: center;
    gap: .4em;
    margin-bottom: 0;
    /* v1.13.22: was .85rem — bumped to .9rem to match every other form
       label in the app (`form label`, already .9rem) and the v1.13.22
       body-text baseline. */
    font-size: .9rem;
    width: auto;
}

/* ============================================================
   TABLES
   ============================================================ */

/* v1.14.28: real mobile review (screenshotting the actual rendered
   portal at phone widths — not just reading the CSS) found that 34 of
   this app's 35 `.fps-table` elements had no horizontal-scroll wrapper
   at all. Concretely, at a 375px-wide screen the Document Control table
   forced the ENTIRE PAGE 216px wider than the phone screen (confirmed by
   measuring document.documentElement.scrollWidth vs clientWidth) — the
   whole page shifts sideways to show one wide table, taking the nav with
   it, instead of just that table scrolling on its own. One table (the
   plan/upgrade comparison just above this section) already used the
   right idea — a wrapping `overflow-x:auto` div — but as a one-off
   inline style, and it turned out to only be half the fix: `.fps-table`
   is `width: 100%`, so even inside that wrapper the table has no reason
   to ever exceed the wrapper's width and trigger a scrollbar — it just
   lets browsers compress every column to fit instead, which is how that
   one table's own last column was rendering as an almost unreadable
   vertical letter-stack on a phone despite "already being wrapped".
   `.fps-table-wrap` below both scroll AND stop that compression: `width:
   auto` lets the table grow past 100% when its content genuinely needs
   more room, `min-width: 100%` keeps it filling the wrapper when it
   doesn't, and the wrapper's own `overflow-x: auto` is what actually
   scrolls that now-genuinely-wider table instead of the whole page (or a
   squished column) doing it. Every `.fps-table` in portal-dashboard.php
   is now wrapped in `<div class="fps-table-wrap">` — the one exception
   is the small ingredient/BOM rows table portal-script.js itself builds
   as an HTML string (rowsTableHtml()) and never wraps this way: an
   `.fps-add-row` click handler there reads `e.target.previousElementSibling`
   expecting that element to BE the table itself (for its
   data-id-field/data-has-pct attributes), so wrapping it would silently
   break "Add row" without also updating that handler — left alone
   deliberately rather than risk that regression in the same pass, and
   it's a narrower 3-5 column table sitting in a layout that already
   collapses to full width on mobile, so it's a smaller version of this
   same gap, not urgent like the rest were. */
.fps-table-wrap {
    overflow-x: auto;
}
.fps-table-wrap .fps-table {
    width: auto;
    min-width: 100%;
}

.fps-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: .75em;
    font-size: .9rem;
}

.fps-table thead {
    background: var(--fps-grey-50);
}

/* v1.10.6: sticky header, so the column labels stay visible while
   scrolling past a long table — most valuable on the three master lists
   (Suppliers, Raw Materials, Packaging Materials) that have no pager and
   can genuinely run long, but harmless and still occasionally useful on
   the 10-row-per-page paginated tables too. `position: sticky` needs its
   own painted background on the STICKY element itself (`th`), not just
   its `<thead>` ancestor, or body rows would show through as they scroll
   underneath it — hence the background repeated here rather than relying
   on the `.fps-table thead` rule above. `top: 0` sticks it to the top of
   the page's own scroll (this app has no separate scrolling container
   per tab), and `z-index` keeps it above the zebra-striped/hovered body
   rows beneath it. */
.fps-table th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--fps-grey-50);
    text-align: left;
    padding: .4em .6em;
    font-size: .78rem;
    letter-spacing: .3px;
    color: var(--fps-grey-500);
    border-bottom: 2px solid var(--fps-border);
}

/* v1.14.6x fix: the v1.14.28 `.fps-table-wrap { overflow-x: auto }` fix for
   mobile horizontal overflow silently broke the v1.10.6 sticky header above
   on every table it wraps. Setting overflow-x to anything but `visible`
   forces the browser to also compute overflow-y as `auto` (there is no way
   to keep it `visible`), which makes the wrapper div itself a new scrolling
   container — and `position: sticky; top: 0` sticks to the nearest
   scrolling ancestor, not the page, so the header silently started
   scrolling away with the rest of the table instead of staying pinned.
   Confirmed by reproducing it in isolation: the thead scrolled fully out of
   view on a 15-row table, exactly matching what was reported on the Hazard
   Analysis Worksheet. Reported against that one table specifically — fixed
   here rather than on the shared `.fps-table-wrap` rule so the other ~34
   tables using it are not changed without their own verification. Giving
   the wrapper a bounded height restores a genuine vertical scrolling
   container, which makes `position: sticky` work again, this time relative
   to the wrapper itself (a "frozen header" panel) rather than the page. */
.fps-hazard-table-wrap {
    max-height: 480px;
    overflow-y: auto;
}

/* v1.10.6: vertical padding pulled down from .65em to .4em, plus a
   tighter line-height, for a noticeably more compact row-to-row spacing
   per direct feedback — horizontal padding (.6em) is unchanged so cell
   text doesn't crowd the column edges. */
.fps-table td {
    text-align: left;
    padding: .4em .6em;
    line-height: 1.3;
    border-bottom: 1px solid var(--fps-border);
    vertical-align: middle;
}

/* v1.10.6: zebra striping — alternating light-orange/white row
   background, on brand with the app's existing "sunrise orange" palette
   (reusing --fps-orange-50, the same faint tint already used for a
   handful of other light-fill contexts in this file) rather than a new
   colour. Placed BEFORE the :hover rule below on purpose: both selectors
   have identical specificity, so hover needs to come later in the
   stylesheet to reliably win (and stay visually consistent) on a
   currently-striped row, not just an unstriped one. `.fps-out-of-spec`'s
   own background carries `!important` (see below), so a failed/rejected/
   overdue row's red highlight is unaffected by either rule regardless of
   which row index it lands on. */
.fps-table tbody tr:nth-child(even) {
    background: var(--fps-orange-50);
}

.fps-table tbody tr:hover {
    background: var(--fps-grey-50);
}

.fps-out-of-spec {
    background: #FBEAE8 !important;
}

/* v1.10.9: raw material lots previously reused .fps-out-of-spec (the same
   red used for a genuinely rejected/expired lot) for a lot that's merely
   *approaching* its expiry date within the next 30 days — reported back
   as confusing, since an "Approved" lot showing red like a rejected one
   reads as a problem rather than a routine stock-rotation reminder. This
   is a separate, lighter amber warning colour for that "expiring soon"
   case specifically; rejected/expired lots keep the red
   `.fps-out-of-spec` styling unchanged. Same `!important` reasoning as
   `.fps-out-of-spec` above: needs to win over both the zebra stripe and
   the hover background regardless of row index. */
.fps-expiring-soon {
    background: #FFF3CD !important;
}

/* ============================================================
   RECIPES & INGREDIENTS TAB (v1.13.17)

   Two small, generically-named (not fps-recipe-specific) patterns added
   for this tab's own top-of-page "start something" buttons and its
   Ingredients table's 3-button Actions column, in case either is useful
   elsewhere later the same way .fps-list-toolbar was.
   ============================================================ */

.fps-recipe-ingredients-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: .6em;
    margin: .75em 0 1.25em;
}

/* Already spaced by the row's own `gap` — zero the base button rule's
   margin rather than double up, same reasoning as .fps-list-toolbar's own
   button rule below. */
.fps-portal .fps-recipe-ingredients-toolbar button {
    margin: 0;
}

/* v1.13.17: user-reported — adding a 3rd action button (Edit/Discontinue/
   Delete) to the Ingredients table turned each row into 3 stacked lines
   instead of one. Root cause: those buttons are plain `display: inline-
   block` with the app-wide default spacing (`.fps-portal button`'s own
   `margin: .5em .6em .5em 0`), so once 3 of them didn't fit the Actions
   column's share of the table's width (auto-sized, and squeezed by the
   Allergen status column's long free text), they wrapped like words in a
   sentence — one per line, each still carrying its own top/bottom margin.
   A flex row with `flex-wrap: nowrap` instead forces the table's own
   auto-layout to reserve enough width for all 3 buttons together on one
   line, so a long-text neighbour column wraps its OWN content instead —
   the right column for that, since ingredient names/codes/status are
   always short but allergen notes can genuinely run long. */
.fps-row-actions {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: .35em;
    white-space: nowrap;
}

.fps-portal .fps-row-actions button {
    margin: 0;
}

/* v1.13.19: direct instruction — "recipe creation section should appear
   side by side with Add Ingredient section (except for mobile)." Ingredients
   (table + its collapsible Add-an-ingredient form) and Recipes (product
   picker + quick-add-product form + recipe editor) sit in their own column
   each, side by side above the existing 600px mobile breakpoint this
   stylesheet already uses elsewhere (see the checklist grid above), and
   stack in their original top-to-bottom order (Ingredients, then Recipes)
   at or below it. `align-items: start` keeps each column's own height
   independent — one column running long (e.g. a big ingredient list) never
   stretches or vertically centers the other. */
.fps-recipe-ingredients-columns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2em;
    align-items: start;
}
@media (max-width: 600px) {
    .fps-recipe-ingredients-columns {
        grid-template-columns: 1fr;
        gap: 0;
    }
}

/* ============================================================
   LIST TOOLBAR + PAGER (v1.6.0, pagination/search pilot)

   One reusable pattern meant to sit directly above a .fps-table:
   a row of filter controls, then (below the table) a summary +
   Prev/Next pair. Piloted on Incoming Goods only for now — see
   templates/portal-dashboard.php and the "Incoming goods — list
   toolbar" block in portal-script.js — but deliberately named and
   styled generically (fps-list-toolbar / fps-list-pager, not
   fps-incoming-goods-toolbar) so rolling it out to the app's other
   list views later is a markup+wiring change, not a new stylesheet.
   ============================================================ */

.fps-list-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .6em;
    margin-top: .75em;
    padding: .75em .9em;
    background: var(--fps-grey-50);
    border: 1px solid var(--fps-border);
    border-radius: 6px;
}

/* These controls sit in a plain <div>, not a <form>, so the app-wide
   `.fps-portal form input[type=…]` block/width-100%/max-width-320px
   rule (see the FORMS section) never reaches the date/search inputs
   here — only `.fps-portal select` applies unconditionally regardless
   of a <form> ancestor. Before the rule just below, that meant the
   select got a real border/padding while the search box and date
   inputs fell back to bare browser defaults — visibly different
   heights, and the date inputs' displayed value inherited the smaller
   `.fps-list-toolbar-date` caption's .8rem font-size via this file's
   own `font: inherit !important` rule (top of file). User-reported:
   "The type size in these input boxes is too small and the input box
   height varies. Keep all input boxes the same height as the date
   range boxes." Fixed by giving every control in this toolbar the same
   explicit box-sizing/height/padding/border/font-size, with enough
   specificity + !important to beat that inherited `font: inherit
   !important` on the date inputs specifically. */
.fps-portal .fps-list-toolbar select,
.fps-portal .fps-list-toolbar input[type="search"],
.fps-portal .fps-list-toolbar input[type="date"] {
    box-sizing: border-box;
    height: 2.4em;
    padding: 0 .65em;
    margin: 0;
    /* v1.13.22: was .95rem — matched to the v1.13.22 body-text baseline
       (.9rem, same as the Ingredients table). */
    font-size: .9rem !important;
    border: 1px solid var(--fps-grey-200);
    border-radius: 4px;
    background: #fff;
    color: var(--fps-grey-900);
}

/* v1.10.2: the "Clear filters" button also lives in this row — already
   spaced by the toolbar's own `gap`, so zero the base button rule's
   margin here too (same reasoning as the select/search/date inputs
   above, which already do this). */
.fps-portal .fps-list-toolbar button {
    margin: 0;
}

.fps-list-toolbar select {
    display: inline-block;
    width: auto;
    max-width: none;
}

.fps-list-toolbar input[type="search"] {
    flex: 1 1 200px;
    min-width: 160px;
}

.fps-list-toolbar-date {
    display: inline-flex !important;
    align-items: center;
    gap: .4em;
    /* v1.13.22: was .8rem — matched to the v1.13.22 body-text baseline. */
    font-size: .9rem;
    color: var(--fps-grey-500);
    white-space: nowrap;
}

.fps-list-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .6em;
    margin-top: .6em;
}

/* v1.10.2: Prev/Next are already spaced by the row's own `gap` — zero
   the base button rule's margin rather than double up. */
.fps-portal .fps-list-pager button {
    margin: 0;
}

.fps-list-pager-summary {
    /* v1.13.22: was .8rem — matched to the v1.13.22 body-text baseline. */
    font-size: .9rem;
    color: var(--fps-grey-500);
}

.fps-list-pager button:disabled {
    opacity: .45;
    cursor: not-allowed;
}

/* Databases tab (v1.7.0 follow-up) — the per-table search checkboxes.
   Deliberately plain checkbox+label pairs, not styled as pill/chip
   buttons, so this reads as a filter list rather than competing with
   the page's actual action buttons. */
/* ============================================================
   CHECKLIST LIST / ITEMS
   ============================================================ */

.fps-checklist-list { list-style: none; padding: 0; }
.fps-checklist-list li { margin-bottom: .6em; }
/* v1.13.22: was .85em — matched to the v1.13.22 body-text baseline (fixed
   `.9rem`, not `em`, so it reads the same size regardless of what
   container it's nested in). */
.fps-frequency { color: var(--fps-grey-500); font-size: .9rem; }

/* v1.10.7: "Today's checklists" — user-reported, from a screenshot,
   that these full-width rows waste space: "Display 3 per line, all same
   size (text can wrap)". Scoped to `#fps-tab-checklists` rather than the
   bare `.fps-checklist-list` class, because that same class name is
   reused by an unrelated list — `renderComplianceItems()` in
   portal-script.js builds a `.fps-checklist-list` for the Reports tab's
   printable compliance self-check, where each `<li>` is a pass/warning/
   fail line with a detail sentence below it, not a button — that one
   must keep reading top-to-bottom as a report, not become a 3-column
   grid. Scoping by the checklists tab's own section id keeps this
   change from ever reaching that other list, wherever it's rendered.
   `display: grid` with three equal-width columns replaces the plain
   `<ul>` flow; each `<li>` becomes a grid item and — with the grid's
   default `align-items: stretch` — is automatically stretched to match
   the tallest item in its row, so a title that wraps onto two lines
   doesn't leave its row-mates looking short; `height: 100%` on the
   button itself (added to `.fps-portal .fps-checklist-list button`
   below) is what actually fills that stretched `<li>`. `gap` replaces
   the old per-`<li>` `margin-bottom` for both row and column spacing.
   Collapses back to a single column under the app's existing 600px
   mobile breakpoint, matching how every other multi-column layout in
   this file already behaves at that width — three columns of wrapped
   checklist titles would be too cramped on a phone. */
#fps-tab-checklists .fps-checklist-list {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: .6em;
}
#fps-tab-checklists .fps-checklist-list li {
    margin-bottom: 0;
}
@media (max-width: 600px) {
    #fps-tab-checklists .fps-checklist-list { grid-template-columns: 1fr; }
}

.fps-checklist-item {
    border-bottom: 1px solid var(--fps-border);
    padding: .7em 0;
}
.fps-checklist-item label {
    margin-right: 1em;
    display: inline-block;
    width: auto;
}

/* Manage checklists — v1.4.0 self-service checklist/item customization,
   gated to can_manage_checklists (see portal-dashboard.php). */
.fps-checklist-manage-row {
    border: 1px solid var(--fps-border);
    border-radius: 6px;
    padding: .8em 1em;
    margin-bottom: .8em;
}
.fps-checklist-edit-form,
.fps-checklist-items-panel {
    margin-top: .8em;
    padding-top: .8em;
    border-top: 1px dashed var(--fps-border);
}
.fps-checklist-items-panel li {
    padding: .4em 0;
    border-bottom: 1px solid var(--fps-border);
}
.fps-item-add-row {
    margin-top: .8em;
}

/* ============================================================
   NOTICES / HINTS
   ============================================================ */

.fps-coming-soon {
    color: var(--fps-grey-700);
    background: var(--fps-orange-50);
    border: 1px solid var(--fps-orange-200);
    border-left: 3px solid var(--fps-orange-700);
    padding: 1em 1.1em;
    border-radius: 6px;
}
.fps-coming-soon code {
    background: var(--fps-grey-100);
    padding: .1em .35em;
    border-radius: 3px;
}

/* v1.13.22: was .85em — matched to the v1.13.22 body-text baseline. Fixed
   as a `rem` value (not `em`) so this reads the same size everywhere it's
   used, including the two places it's used OUTSIDE `.fps-portal`
   entirely (the e-signature and tier-upgrade modals, appended straight to
   `<body>` — see their own comments further down this file), where an
   `em` value would instead be relative to whatever font-size that other
   context happens to have. */
.fps-hint {
    color: var(--fps-grey-500);
    font-size: .9rem;
    font-style: italic;
}

/* v1.14.0: CAPA root cause investigation — the 6 fixed photo-upload
   slots on the investigation form. Each slot is a file picker + a
   caption field side by side; a link to the existing photo (on an
   already-saved slot) sits above them, so a slot with a photo already
   shows what it holds before the user decides whether to replace it. */
.fps-capa-inv-image-slot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .6em;
    padding: .5em 0;
    border-bottom: 1px solid var(--fps-border);
}
.fps-capa-inv-image-slot .fps-capa-inv-image-existing {
    flex-basis: 100%;
    font-size: .85rem;
    color: var(--fps-grey-500);
}
.fps-capa-inv-subtable input,
.fps-capa-inv-subtable select {
    width: 100%;
    box-sizing: border-box;
}

/* v1.9.0: flags a recipe/BOM row saved before the ingredient master list
   existed, still showing its original free-text name via the "__legacy__"
   select option rather than a linked master-list id. */
.fps-legacy-hint {
    display: inline-block;
    margin-left: .4em;
    padding: .1em .4em;
    border-radius: 3px;
    background: #fff3cd;
    color: #7a5c00;
    font-size: .75em;
    font-style: italic;
    white-space: nowrap;
}

/* v1.13.23: the small "Home" link render_notice() now prints above its
   own card (see that function's docblock) — same max-width as the card
   below it so the two line up, left-aligned so it reads as a way out
   rather than another piece of the centered message. */
.fps-portal-topbar {
    max-width: 480px;
    margin: 2em auto 0;
    text-align: left;
    font-family: var(--fps-font);
}
.fps-portal-home-link,
.fps-portal-home-link:visited {
    display: inline-block;
    font-size: .9rem;
    color: var(--fps-orange-700);
    text-decoration: none;
}
.fps-portal-home-link:hover,
.fps-portal-home-link:focus {
    text-decoration: underline;
}

.fps-notice {
    max-width: 480px;
    margin: 1em auto 3em;
    text-align: center;
    background: #fff;
    border: 1px solid var(--fps-border);
    border-radius: 8px;
    padding: 2em;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
    font-family: var(--fps-font);
    color: var(--fps-grey-700);
}

/* v1.3.6: .fps-notice is what render_notice() wraps around the "please
   log in" / "no access" screens — including, for the logged-out case,
   WordPress's OWN wp_login_form() output. That form is core WordPress
   markup (#loginform, #user_login, #user_pass, #wp-submit), not
   anything this plugin's templates render, so it sits inside
   `.fps-notice` but is never touched by any `.fps-portal`/
   `.fps-signup-form` rule above — it was rendering in the active
   theme's own default (often small, serif-body-copy) form styling,
   which is what "input boxes too small, typography wrong" was actually
   showing.
   The one thing this file genuinely can't reach here: the big page
   title above this card (e.g. "Food Safety Portal") is the WordPress
   PAGE title, rendered by the active theme's own shared page template —
   not by this plugin at all — so per the "don't touch the shared
   template" constraint this stylesheet cannot restyle it; only the card
   below it is this plugin's own markup to style.
   `.fps-notice` and everything inside it gets its own font forced
   (`!important`) for the same reason this file already does that for
   its own buttons/inputs: a theme's own CSS reliably beats a plain,
   non-!important font-family declaration on generic elements like
   `label`/`input`/`p`, even when it's only inherited from a parent. */
.fps-notice,
.fps-notice * {
    font-family: var(--fps-font) !important;
}

.fps-notice p {
    color: var(--fps-grey-700);
    /* v1.13.22: was 1rem — matched to the v1.13.22 body-text baseline.
       `.fps-notice` is rendered standalone (see render_notice() in
       class-fps-instance-registry.php) whenever the visitor isn't logged
       in or has no access, entirely outside `.fps-portal`, so it needs
       its own explicit size rather than picking one up by inheritance. */
    font-size: .9rem;
    margin-bottom: 1.5em;
}

.fps-notice a {
    color: var(--fps-orange-700);
}

.fps-notice #loginform {
    text-align: left;
    max-width: 320px;
    margin: 0 auto;
}

.fps-notice #loginform p {
    margin-bottom: 1.1em;
}

.fps-notice #loginform label {
    display: block;
    margin-bottom: .4em;
    /* v1.13.22: was .8rem — matched to the v1.13.22 body-text baseline. */
    font-size: .9rem !important;
    letter-spacing: .3px;
    color: var(--fps-grey-700);
}

.fps-notice #loginform input[type=text],
.fps-notice #loginform input[type=password] {
    width: 100%;
    box-sizing: border-box;
    padding: .65em .8em;
    border: 1px solid var(--fps-grey-200);
    border-radius: 4px;
    /* v1.13.22: was 1rem — matched to the v1.13.22 body-text baseline. */
    font-size: .9rem !important;
    color: var(--fps-grey-900);
}

.fps-notice #loginform input[type=text]:focus,
.fps-notice #loginform input[type=password]:focus {
    outline: none;
    border-color: var(--fps-orange-700);
    box-shadow: 0 0 0 3px rgba(232, 96, 12, 0.15) /* v1.10.2: matches new --fps-orange-700 #E8600C */;
}

/* Scoped as `.fps-notice #loginform .login-remember label` (id +
   2 classes), not just `.fps-notice .login-remember label` — an id
   selector's specificity always outranks any number of classes, so the
   plain 2-class version above lost to `.fps-notice #loginform label`'s
   id-bearing selector regardless of which was declared later, leaving
   "Remember Me" bold/uppercase like a real field label instead of a
   plain checkbox caption. */
.fps-notice #loginform .login-remember label {
    display: flex;
    align-items: center;
    gap: .5em;
    font-weight: 400;
    text-transform: none;
    font-size: .9rem !important;
    color: var(--fps-grey-700);
}

.fps-notice .login-remember input[type=checkbox] {
    width: auto;
    margin: 0;
}

.fps-notice #loginform input[type=submit],
.fps-notice #wp-submit {
    width: 100%;
    padding: .8em;
    background: var(--fps-orange-700);
    color: #fff !important;
    border: none;
    border-radius: 6px;
    /* was 1rem, then .75rem (25% smaller, per the v1.4.1 user request);
       v1.5.2 rescaled to `.95rem` alongside the base button tier's
       `.675rem` -> `.85rem` change, preserving this tier's original
       ~111% ratio to the base tier (`1rem` vs `.9rem` before any
       reduction: `1/.9 = 1.111`; `.85 * 1.111 ≈ .944`, rounded to `.95`). */
    font-size: .95rem !important;
    font-weight: 700 !important; /* orange button — bold, per the file-level policy above */
    cursor: pointer;
    box-shadow: none;
    transition: background-color .15s ease;
}

.fps-notice #loginform input[type=submit]:hover,
.fps-notice #wp-submit:hover {
    background: var(--fps-orange-900);
}

.fps-site-chooser ul { list-style: none; padding: 0; }
.fps-site-chooser a {
    display: block;
    padding: .85em 1.1em;
    background: #fff;
    border: 1px solid var(--fps-border);
    border-radius: 6px;
    margin-bottom: .6em;
    text-decoration: none;
    color: var(--fps-grey-900);
    font-weight: 500;
    transition: all .15s ease;
}
.fps-site-chooser a:hover {
    border-color: var(--fps-orange-400);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
    transform: translateY(-1px);
}

/* ============================================================
   DASHBOARD STAT GRID (v1.0.0)
   ============================================================ */

.fps-stat-grid { display: flex; flex-wrap: wrap; gap: 1em; margin: 1em 0; }
.fps-stat-card {
    background: #fff;
    border: 1px solid var(--fps-border);
    border-top: 4px solid var(--fps-orange-500);
    border-radius: 8px;
    padding: 1.1em 1.4em;
    min-width: 180px;
    display: flex;
    flex-direction: column;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
    transition: transform .15s ease, box-shadow .15s ease;
}
.fps-stat-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 12px rgba(0, 0, 0, 0.08);
}
.fps-stat-card.fps-stat-alert {
    border-top-color: var(--fps-fail);
    background: linear-gradient(to bottom, #FBEAE8 0%, #fff 100%);
}
.fps-stat-number {
    font-size: 2em;
    line-height: 1.1;
    color: var(--fps-grey-900);
}
.fps-stat-card.fps-stat-alert .fps-stat-number { color: var(--fps-fail); }
.fps-stat-label {
    color: var(--fps-grey-500);
    font-size: .78em;
    letter-spacing: .3px;
    margin-top: .4em;
}
.fps-stat-sub { color: var(--fps-fail); font-size: .8em; margin-top: .3em; }

/* ============================================================
   PRODUCT / RECIPE / BOM (v0.5.0)
   ============================================================ */

#fps-product-detail { margin-top: 1em; padding-top: 1em; }
.fps-recipe-block {
    border: 1px solid var(--fps-border);
    border-radius: 6px;
    padding: 1.1em;
    margin: 1em 0;
    background: var(--fps-grey-50);
}

/* ============================================================
   TEMPLATE / REPORT CARDS + COMPLIANCE SELF-CHECK (v1.0.0)
   ============================================================ */

.fps-template-card {
    border: 1px solid var(--fps-border);
    border-radius: 6px;
    padding: 1.1em;
    margin-bottom: .85em;
    background: #fff;
}
.fps-template-card pre {
    white-space: pre-wrap;
    background: var(--fps-grey-50);
    padding: .85em;
    border-radius: 4px;
    /* v1.13.52: direct instruction — "easier to read on screen" for the
       SOP/policy drafts. This was previously unset, so it just inherited
       the browser's own default monospace size/line-height (noticeably
       tighter and smaller than the app's own body text) — bumped to match
       ordinary reading comfort rather than a code-listing's default. */
    font-size: 1rem;
    line-height: 1.6;
}
/* v1.12.2: user-reported, from a screenshot of the Library tab's SOP
   starter drafts — the title-toggle buttons ("Basic cleaning &
   sanitation SOP (starter draft)", etc.) read as noticeably too small.
   This isn't an action button like Save/Open/Delete (the app-wide
   unified .775rem tier established in v1.5.3) — it's a clickable
   document title that expands/collapses the draft below it, closer in
   role to a heading than a commit button, so this is a new, deliberate
   exception to that tier (same category of exception as the tab bar
   and checklist run-list already documented above the BUTTONS section)
   rather than a reopening of the app-wide button-size question. Scoped
   to just this one button (.fps-template-toggle) — the business-
   template "Load this template" buttons in the card above share the
   same .fps-template-card wrapper but are genuine commit actions, so
   they deliberately keep the app's unified button size. */
.fps-template-card .fps-template-toggle {
    font-size: 1rem !important;
}

/* Template Library (v1.10.0; moved to its own top-level tab in v1.12.0) —
   self-service starter-template cards; reuses .fps-template-card/
   .fps-stat-grid above. */
.fps-template-library {
    margin-bottom: 1.5em;
    padding-bottom: 1em;
    border-bottom: 1px solid var(--fps-border);
}
.fps-template-card button:disabled {
    opacity: .55;
    cursor: not-allowed;
}

.fps-compliance-item { padding: .55em 0; border-bottom: 1px solid var(--fps-border); }
.fps-compliance-pass { color: var(--fps-pass); }
.fps-compliance-warning { color: var(--fps-warn); }
.fps-compliance-fail { color: var(--fps-fail); }

/* ============================================================
   ELECTRONIC-SIGNATURE MODAL (v1.1.0)
   ============================================================
   Built entirely by portal-script.js's requestEsignature() via
   element.style / innerHTML, with several properties set inline
   (background, padding, border-radius, input padding). An inline style
   always wins over an external stylesheet rule of any specificity short
   of !important, so — per this file's own established convention above
   for exactly this situation — the handful of properties that need to
   look right (surface colour, radius, input styling) are !important
   here specifically to beat those inline declarations; nothing else is.
*/
.fps-esig-overlay > div {
    background: #fff !important;
    border-radius: 8px !important;
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.25);
    font-family: var(--fps-font);
}
.fps-esig-overlay h3 {
    color: var(--fps-grey-900);
}
.fps-esig-overlay #fps-esig-password {
    border: 1px solid var(--fps-grey-200) !important;
    border-radius: 4px !important;
    padding: .55em .7em !important;
}
.fps-esig-overlay #fps-esig-password:focus {
    outline: none;
    border-color: var(--fps-orange-700) !important;
    box-shadow: 0 0 0 3px rgba(232, 96, 12, 0.15) /* v1.10.2: matches new --fps-orange-700 #E8600C */;
}

/* v1.13.2: tier-upgrade modal (portal-script.js's showTierUpgradeModal(),
   triggered by clicking a locked nav tab). Same "appended to <body>,
   inline positioning + a few !important surface overrides" pattern as
   the esig overlay above — see that block's own comment for why the
   overrides are needed at all (the inline `style.cssText` set on the
   card div itself would otherwise always win over this external
   stylesheet, short of !important, regardless of specificity). */
.fps-tier-upgrade-overlay > div {
    background: #fff !important;
    border-radius: 8px !important;
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.25);
    font-family: var(--fps-font);
}
.fps-tier-upgrade-overlay h3 {
    color: var(--fps-grey-900);
}

/* v2.0.7: HACCP Plan Wizard overlay (portal-script.js's fpsWizardOpen()).
   Same "appended to <body>, inline positioning + a few !important surface
   overrides" pattern as the esig/tier-upgrade overlays above — see the
   esig overlay's own comment for why the overrides are needed at all. A
   lower z-index than the esig overlay (9999) is deliberate: the wizard
   never itself triggers an e-signature (it never calls
   fps_approve_haccp_plan), so the two are never expected to stack, but
   keeping the wizard strictly below it is a cheap, correct ordering if
   that ever changes. */
.fps-wizard-overlay .fps-wizard-card {
    background: #fff !important;
    border-radius: 8px !important;
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.25);
    font-family: var(--fps-font);
}
.fps-wizard-overlay h3,
.fps-wizard-overlay h4 {
    color: var(--fps-grey-900);
}
.fps-wizard-overlay label {
    display: block;
    margin-top: .7em;
}

/* ============================================================
   PRINTABLE REPORTS (v1.0.0) — layout unchanged, colours only
   v1.13.11 — fixed 2 reported printing bugs: trailing blank pages, and
   very large print type. See finpress-food-safety.php's changelog for
   the full writeup; short version below.
   ============================================================ */

/* v1.13.53: user-reported — "Print margins are too narrow." There was no
   @page rule anywhere in this file, so every printed report/document ran
   entirely on whatever page margin the browser/printer driver happened
   to default to (often quite tight, and inconsistent across browsers) —
   #fps-print-area's own print padding (below) only ever added space
   above the content, never on the sides or bottom. An explicit @page
   margin is the standard, cross-browser way to control this from CSS
   (respected by Chrome/Firefox/Edge's print and print-to-PDF pipelines
   alike) — set generously enough to read as a normal printed document's
   margins, not a report crammed edge-to-edge.
*/
@page {
    margin: 2cm;
}

@media print {
    body * { visibility: hidden; }
    #fps-print-area, #fps-print-area * { visibility: visible; }
    #fps-print-area { position: absolute; left: 0; top: 0; width: 100%; }
    /* v1.14.45: undo the on-screen preview overlay for the printed page. */
    #fps-print-area.fps-print-preview-open {
        position: absolute !important; right: auto; bottom: auto; height: auto;
        overflow: visible !important; z-index: auto; background: none;
    }
    #fps-print-area .fps-print-toolbar { display: none !important; }

    /* v1.13.11: #fps-print-area is position:absolute, so it's already out
       of normal flow and unaffected by this — but every OTHER element on
       the page (every other tab panel, the nav, any host WordPress
       theme's own header/footer/sidebar markup around the shortcode)
       keeps its full on-screen layout height even though visibility:
       hidden makes it invisible, because visibility (unlike display)
       deliberately preserves box size — that's what lets a nested
       descendant like #fps-print-area override it back to visible in the
       first place. Left alone, that invisible-but-still-sized content can
       still generate extra, entirely blank pages after the real report
       (reported: "2 blank pages at end of file"), and how much of that a
       given browser's print engine actually turns into blank pages
       varies — collapsing html/body's own box to nothing removes the
       risk everywhere rather than relying on one engine's behaviour.
    */
    html, body {
        height: 0 !important;
        overflow: hidden !important;
    }
}
#fps-print-area { padding: 2em; font-family: var(--fps-font); }

/* v2.8.2: branded report header/footer (printReport() in portal-script.js).
   On screen and on the printed/saved-as-PDF page: the business's logo and
   name across the top, a light divider, and a disclaimer footer. */
.fps-report-brand {
    display: flex;
    align-items: center;
    gap: 1em;
    padding-bottom: 0.75em;
    margin-bottom: 1em;
    border-bottom: 2px solid #1f7a3d;
}
.fps-report-brand-logo {
    max-height: 64px;
    max-width: 220px;
    width: auto;
    height: auto;
    object-fit: contain;
}
.fps-report-brand-meta { line-height: 1.35; }
.fps-report-brand-name { font-size: 1.15rem; font-weight: 700; }
.fps-report-brand-plan { font-size: 0.85rem; color: #425066; }
.fps-report-brand-when { font-size: 0.8rem; color: #6b7a90; }
.fps-report-foot {
    margin-top: 2em;
    padding-top: 0.75em;
    border-top: 1px solid #c9d2dd;
    font-size: 0.75rem;
    color: #6b7a90;
}
@media print {
    /* Keep the brand divider/footer colours when printing (some engines
       drop backgrounds/colours unless asked to preserve them). */
    .fps-report-brand, .fps-report-foot { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
    .fps-report-brand { break-inside: avoid; }
}
/* v1.14.45: on screen, a generated report opens as a full-page preview
   with Print / Close buttons (see printReport() in portal-script.js).
   The @media print rules below turn it back into the plain printed page
   and hide the buttons. */
#fps-print-area.fps-print-preview-open {
    position: fixed; top: 0; right: 0; bottom: 0; left: 0;
    z-index: 100000; overflow-y: auto; background: #fff;
    box-sizing: border-box; outline: none;
}
html.fps-print-preview-lock, html.fps-print-preview-lock body { overflow: hidden; }
#fps-print-area .fps-print-toolbar-top { text-align: right; margin-bottom: .5em; }
#fps-print-area .fps-print-toolbar-bottom {
    margin-top: 2em; padding-top: 1em; border-top: 1px solid var(--fps-border);
}
#fps-print-area .fps-print-toolbar button { margin-right: .5em; }
#fps-print-area .fps-print-go { font-weight: 600; }
#fps-print-area h1 { margin-top: 0; color: var(--fps-grey-900); }
/* v1.13.52: SOP/Document print view — a meta line (type · version ·
   status) under the title, a divider, then the document body. Only
   meaningful on the printed page itself (see the @media print sizing
   below); left unstyled here beyond a sensible on-screen fallback since
   #fps-print-area only ever becomes visible for the brief instant before
   window.print() takes over (see printReport()). */
#fps-print-area .fps-print-meta { color: var(--fps-grey-500); }
#fps-print-area hr { border: none; border-top: 1px solid var(--fps-border); }
#fps-print-area .fps-doc-body { white-space: pre-wrap; }

@media print {
    /* v1.13.11: explicit, absolute print sizing (pt, not em/rem) for the
       report itself, instead of silently inheriting whatever base
       font-size happens to be active on screen — a real WordPress theme
       is free to set its own (often much larger, branding-driven) base
       font-size on html/body, and #fps-print-area had nothing of its own
       to stop that cascading straight through onto the printed page
       (reported: "the type size is very big"). Sizes below are picked to
       read comfortably on a printed A4/Letter page, matching ordinary
       printed-document body/heading proportions rather than on-screen
       web type. `!important` because this needs to win over both the
       host theme's cascade and this stylesheet's own on-screen rules for
       the same elements.
    */
    /* v1.13.53: this used to be 1.5em 0 0 0 — a top-only buffer, with
       genuinely ZERO padding on the left/right/bottom, so printed content
       ran right up against whatever page margin the browser/printer
       happened to apply on those sides (often tight — reported: "Print
       margins are too narrow"). The @page rule above is the correct,
       standard way to control the actual page margin and is honoured by
       every major browser's print dialog on its "Default" margin preset
       — but that pathway can't be exercised by this app's own headless-
       Chromium regression tooling (Chrome DevTools' printToPDF, which
       Playwright's page.pdf() uses for test-sop-library-visual.js and
       friends, always sends explicit margin values that override @page
       CSS — a tooling limitation of that specific automation surface,
       not of real browsers' print dialogs). This padding is the
       independently-effective, directly testable half of the fix: real
       breathing room around the content itself, on every side, that
       holds even for a print-dialog margin preset ("Minimum", "None")
       that ignores @page altogether.

       box-sizing: border-box is required here and wasn't needed before —
       #fps-print-area is `width: 100%` with content-box sizing (this
       stylesheet has no universal border-box reset), so adding padding
       to every side without also switching box-sizing would make the
       rendered box 100% + 3em wider than the page itself (confirmed
       empirically: without this, the right-hand padding actually
       overflowed the page instead of appearing as a margin — a real
       regression this fix would otherwise have introduced). border-box
       keeps the box at 100% total width with the padding subtracted from
       its content area instead of added on top.
    */
    #fps-print-area {
        box-sizing: border-box !important;
        padding: 1.5em !important;
        font-size: 10.5pt !important;
        line-height: 1.45 !important;
    }
    /* v1.13.52: direct instruction — "larger headings etc" for printed/
       PDF'd documents. Bumped from 18pt; still comfortably within normal
       printed-document proportions for a 10.5pt body, just more clearly a
       document title at a glance (the original size read as barely bigger
       than a subheading once actually printed). */
    #fps-print-area h1 {
        font-size: 22pt !important;
        margin: 0 0 0.3em 0 !important;
    }
    /* New with the same instruction — a document's type/version/status
       meta line (see ajax_get_document_print_view()/printSopDraft() in
       portal-script.js) sits between the title and the body, smaller and
       muted so it reads as metadata rather than more heading text, with
       the <hr> beneath it as a clear visual break before the body starts. */
    #fps-print-area .fps-print-meta {
        font-size: 9.5pt !important;
        color: #555 !important;
        margin: 0 0 0.8em 0 !important;
    }
    #fps-print-area hr {
        margin: 0 0 1em 0 !important;
    }
    /* Each compliance item (and, by the same shared class, other
       reports' own row-like blocks) shouldn't be split across a page
       boundary — a heading stranded alone at the bottom of one page with
       its detail line starting the next is exactly the kind of thing
       that makes a printed report look broken, and avoiding it also
       means content that's already borderline-close to a page boundary
       is more likely to move as a whole rather than leave an awkward
       sliver behind.
    */
    #fps-print-area li,
    #fps-print-area p {
        page-break-inside: avoid;
    }
}

/* ============================================================
   PUBLIC SIGNUP FORM — matched to FinPress's own self-service
   signup card (finpress-self-service/assets/css/signup-style.css)
   ============================================================ */

.fps-signup-form {
    max-width: 480px;
    margin: 2.5em auto;
    padding: 2.5em;
    background: #fff;
    border: 1px solid var(--fps-border);
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.07);
    font-family: var(--fps-font);
    /* v1.13.22: same body-text baseline as `.fps-portal` above — see that
       rule's own comment for the full rationale. */
    font-size: .9rem;
}

.fps-signup-form h2 {
    margin: 0 0 1.4em;
    color: var(--fps-grey-900);
    font-size: 1.375rem;
    font-weight: 500; /* matches the base heading weight used throughout the rest of this file */
    text-align: center;
    padding-bottom: 1em;
    border-bottom: 1px solid var(--fps-border);
}

.fps-signup-form form p {
    margin-bottom: 1.1em;
}

.fps-signup-form label {
    /* v1.13.22: was .8rem — matched to the v1.13.22 body-text baseline
       (and to `form label`'s own .9rem, used everywhere else in the
       app). */
    font-size: .9rem;
    letter-spacing: .3px;
    color: var(--fps-grey-700);
}

.fps-signup-form form input[type=text],
.fps-signup-form form input[type=email],
.fps-signup-form form input[type=password],
.fps-signup-form select {
    max-width: 100%;
}

.fps-signup-form button[type=submit] {
    width: 100%;
    padding: .8em;
    /* !important needed now that the base button rule above also uses it
       (important beats specificity regardless of selector weight).
       v1.5.2: rescaled to `.95rem` — same reasoning as the matching
       login-submit-button rule earlier in this file (search for
       "~111% ratio"). */
    font-size: .95rem !important;
    /* This re-declares font-weight for the SAME selector as the shared
       orange-button rule above (same specificity, this one is later in
       the file, so it wins) — kept in sync at 700/bold so this button
       doesn't quietly fall back to a lighter weight. v1.5.0: also needs
       !important for the same shorthand reason as the rule above. */
    font-weight: 700 !important;
    /* v1.10.2: was `margin-top: .4em;` alone. Now a full shorthand that
       explicitly zeroes right/bottom/left — this button is `width: 100%`,
       and the base button rule now sets a nonzero right/left margin by
       default, which would push this button wider than its container
       (width: 100% sizes against the containing block; margin sits
       outside that regardless of box-sizing) and add unwanted space
       below it too. Only the existing top spacing is kept. */
    margin: .4em 0 0;
}

.fps-signup-message {
    padding: .75em 1em;
    border-radius: 6px;
    margin-top: 1em;
    /* v1.13.22: was .875rem — matched to the v1.13.22 body-text baseline. */
    font-size: .9rem;
    color: var(--fps-grey-700);
}

/* ============================================================
   RESPONSIVE
   ============================================================ */

@media (max-width: 600px) {
    .fps-portal { padding: 1em; margin: 0; border-radius: 0; }
    .fps-tab-panel { padding: 1.1em; }
    .fps-signup-form { padding: 1.5em; margin: 0; border-radius: 0; }
    .fps-portal-header h1 { font-size: 1.4rem; }
}

/* v1.14.40: limit boxes locked to a CCP's own critical limits on the
   Log a reading form (and any other read-only input) read as fixed. */
.fps-portal input[readonly],
.fps-portal select:disabled {
    background: #f1f1f1;
    color: #555;
    cursor: not-allowed;
}

/* v1.14.40 layout fix: the last (actions) cell of a wide table keeps its
   buttons stacked and never shrinks below them. */
#fps-tab-haccp .fps-table td:last-child > button { display: block; white-space: nowrap; margin: 0 0 .35em; }

/* v1.14.48: offline readiness line at the foot of the portal. */
.fps-offline-status { margin-top: 2em; padding: .5em .8em; border-left: 4px solid var(--fps-border); }
.fps-offline-status.is-ready { border-left-color: #2c7a3f; }
.fps-offline-status.is-not-ready { border-left-color: #d9541e; }
@media print { .fps-offline-status { display: none !important; } }

/* v1.14.50: late entries. */
.fps-late-entry-block { border: 2px solid #d9541e; border-radius: 6px; padding: .8em 1em; margin-bottom: 1em; background: var(--fps-orange-100, #fff4ec); }
.fps-late-entry-block label { display: block; margin: .5em 0; }
.fps-late-entry-block textarea { width: 100%; }
.fps-late-label { display: inline-block; font-size: .75em; font-weight: 700; color: #fff; background: #d9541e; border-radius: 3px; padding: 0 .4em; }

/* v1.14.52: OPRP badge, management system section spacing. */
.fps-oprp-badge { display: inline-block; font-size: .72em; font-weight: 700; color: #fff; background: #5b6b8c; border-radius: 3px; padding: 0 .4em; vertical-align: middle; }
#fps-tab-management-system h3 { margin-top: 1.8em; }
#fps-tab-management-system h5 { margin: .8em 0 .2em; }
.fps-ms-jump { margin: .5em 0 1em; }

/* v1.14.53: labels, fieldsets and hints inside forms that are toggled
   with the hidden attribute (e.g. the Log a reading form's CCP / general
   fields) — the portal's own display rules on these would otherwise
   beat the browser's [hidden] rule, the same issue as buttons above. */
.fps-portal label[hidden],
.fps-portal span[hidden],
.fps-portal div.fps-device-setup[hidden],
.fps-portal fieldset[hidden],
.fps-portal p[hidden] {
    display: none !important;
}
.fps-reading-kind { border: 1px solid var(--fps-border); border-radius: 6px; padding: .4em .8em .6em; margin-bottom: 1em; }
.fps-reading-kind label { display: block; margin: .3em 0; }

/* v1.14.54: portal address in the offline readiness line. */
.fps-offline-url { word-break: break-all; font-size: .9em; background: var(--fps-orange-100, #fff4ec); padding: 0 .3em; border-radius: 3px; }

/* v1.14.56: "Set up this device" card. */
.fps-device-setup { border: 2px solid #2c7a3f; border-radius: 6px; padding: .6em 1em .8em; margin: .8em 0 1.2em; background: #f3faf4; }
.fps-device-setup h3 { margin-top: .2em; }
.fps-device-setup ol { margin: .3em 0 .8em 1.2em; }

/* v1.05: read-only demo banner. Flashes when a save is refused. */
.fps-demo-banner { background: var(--fps-orange-100); border-left: 4px solid var(--fps-orange-700); color: var(--fps-grey-900); padding: .7em 1em; margin: 0 0 1em; border-radius: 4px; }
.fps-demo-banner.is-flashing { background: var(--fps-orange-200); box-shadow: 0 0 0 3px var(--fps-orange-400); }
@media print { .fps-demo-banner { display: none !important; } }

/* v1.11 premises */
.fps-premises-gaps { margin: 0 0 1em; padding-left: 1.2em; }
.fps-premises-gaps li { margin: .3em 0; border-left: 3px solid #e05a00; padding-left: .6em; list-style: none; margin-left: -1.2em; }
.fps-premises-q { border-bottom: 1px solid #eee; padding: .6em 0; }
.fps-premises-q p { margin: 0 0 .3em; }
.fps-premises-q label { display: inline-block; margin-right: 1.2em; }
.fps-premises-q .fps-premises-note { display: block; width: 100%; max-width: 40em; margin-top: .3em; }
.fps-premises-q.fps-saved { background: #f6fbf6; }

/* v1.11.1 offline banner on My Tasks */
.fps-offline-banner { margin: 0 0 1em; padding: .5em .8em; border-left: 4px solid var(--fps-border); background: #fafafa; }
.fps-offline-banner.is-active { border-left-color: #2c7a3f; }
.fps-offline-banner.is-inactive { border-left-color: #d9541e; }
.fps-offline-status { margin-top: .5em; }

/* v1.12 premises flows */
.fps-premises-finding { border-left: 3px solid #e05a00; padding: .4em .8em; margin: .6em 0; background: #fffaf5; }
.fps-premises-finding.is-decided { border-left-color: #2c7a3f; background: #f6fbf6; }
.fps-premises-finding p { margin: .2em 0; }
.fps-premises-finding .fps-finding-reason { width: 100%; max-width: 32em; }
.fps-premises-change { border-left: 4px solid #d9541e; background: #fff6ee; padding: .5em .9em; margin: .8em 0; }
.fps-premises-change ul { margin: .3em 0 .6em 1.2em; }
.fps-flow-steps { margin: .3em 0 .8em 1.2em; }

/* v1.12.1: clearer flow entry */
.fps-flow-suggest { border-left: 3px solid #e05a00; background: #fffaf5; padding: .7em 1em; margin: .8em 0; }
.fps-flow-suggest button { margin-left: .6em; }
.fps-flow-form { border: 1px solid #e3e3e3; border-radius: 6px; padding: .8em 1em; background: #fcfcfc; }
.fps-flow-form label { display: block; margin: .4em 0; }
.fps-flow-step { margin: .9em 0 .3em; }
.fps-flow-num { display: inline-block; width: 1.6em; height: 1.6em; line-height: 1.6em; text-align: center; border-radius: 50%; background: #e05a00; color: #fff; font-size: .85em; margin-right: .3em; }
.fps-flow-chips { display: flex; flex-wrap: wrap; gap: .4em; margin: .3em 0 .5em; }
.fps-flow-chip { border-radius: 999px; }
.fps-flow-chip[data-zone="raw"] { border-color: #b8860b; }
.fps-flow-chip[data-zone="high_care"] { border-color: #2c7a3f; }
.fps-flow-steps { list-style: none; margin: .3em 0 .8em; padding: 0; display: flex; flex-wrap: wrap; gap: .3em; align-items: center; }
.fps-flow-steps li { background: #fff3e8; border: 1px solid #f0c9a8; border-radius: 4px; padding: .2em .3em .2em .6em; }
.fps-flow-steps li + li::before { content: "\2192"; margin-right: .5em; color: #888; }
.fps-flow-steps li.fps-hint { background: none; border: 0; }
.fps-flow-steps li.fps-hint::before { content: none !important; }
.fps-flow-remove { border: 0; background: none; cursor: pointer; padding: 0 .3em; color: #a33; font-size: 1.1em; }

/* v1.12.2: premises pictures */
.fps-premises-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 1em; margin: .8em 0 1.2em; }
.fps-premises-picture { margin: 0; border: 1px solid #e3e3e3; border-radius: 6px; background: #fff; overflow: hidden; display: flex; flex-direction: column; }
.fps-premises-picture.fps-pic-layout { grid-column: 1 / -1; }
.fps-premises-picture img { display: block; width: 100%; height: 190px; object-fit: contain; background: #f6f6f6; }
.fps-premises-picture.fps-pic-layout img { height: auto; width: 65%; max-height: 820px; margin: 0 auto; }
@media (max-width: 600px) { .fps-premises-picture.fps-pic-layout img { width: 100%; } }
.fps-pic-pdf { display: flex; align-items: center; justify-content: center; height: 190px; background: #f6f6f6; color: #a33; font-weight: 700; font-size: 1.6em; }
.fps-premises-picture figcaption { padding: .6em .8em; display: flex; flex-direction: column; gap: .2em; }


/* v2.5.0: product label checks. */
.fps-label-check-box { border: 1px solid var(--fps-border); border-radius: 6px; padding: .6em .9em; margin: .6em 0; }
.fps-label-errors li { color: #b3261e; }
.fps-label-warnings li { color: #8a5a00; }
.fps-label-bad { color: #b3261e; font-weight: 700; }
.fps-label-good { color: #2c7a3f; font-weight: 700; }
#fps-label-form fieldset { border: 1px solid var(--fps-border); border-radius: 6px; margin: .8em 0; padding: .4em .9em .8em; }

/* v2.5.8: dismissible demo intro banner (see portal-dashboard.php + portal-script.js) */
.fps-demo-banner { position: relative; }
.fps-demo-banner--intro { padding-right: 2.8em; }
.fps-demo-banner-dismiss { position: absolute; top: .3em; right: .45em; background: none; border: 0; font-size: 1.5em; line-height: 1; color: var(--fps-grey-700); cursor: pointer; padding: .05em .3em; border-radius: 4px; }
.fps-demo-banner-dismiss:hover, .fps-demo-banner-dismiss:focus { color: var(--fps-grey-900); background: rgba(0,0,0,.07); }
