/* ==========================================================================
   VestaSys — application stylesheet (hand-written, no build step)
   ==========================================================================

   This file replaces the previous Tailwind CSS + DaisyUI setup that was
   compiled through npm/Vite. The application is deployed without Node, so the
   stylesheet is authored by hand and served straight from public/ via a plain
   <link> tag.

   The class NAMES are deliberately unchanged from the Tailwind/DaisyUI ones the
   367 Blade views already use (`btn`, `card`, `flex`, `text-sm`, `bg-base-100`,
   …). That keeps the markup untouched, so this migration cannot silently break
   a view: every class the views reference is implemented below.

   Layout of this file. ORDER IS LOAD-BEARING: components come before the
   utilities so a utility always wins against a component rule of the same
   specificity — `class="btn h-20"` must render a 5rem-tall button, and
   `sm:stats-horizontal` must beat `stats-vertical`. Adding a component rule
   below the utilities would silently break both. Sections are numbered by
   their original authoring order, which is why the numbers below are not
   sequential:
     1. Theme tokens (light / dark / SBM brand)
     2. Reset and base elements
    10. Components (btn, card, input, table, modal, …)
     3. Layout utilities (display, flex, grid, position)
     4. Spacing utilities (margin, padding, gap, space-y)
     5. Sizing utilities (width, height, min/max)
     6. Typography utilities
     7. Colour utilities (text, background, border)
     8. Border, radius, shadow and effect utilities
     9. Responsive and state variants (sm:, md:, lg:, hover:, focus:, print:)
    11. SBM brand theme
    12. Sign-in gradient background (<x-gradient-bg>)
    13. Printed quote document (.quote-doc)

   Colour tokens are CSS custom properties so a theme swap is a token swap.
   Fractional colours (`text-base-content/60`) use color-mix() against the token
   rather than a hard-coded rgba, so they follow the active theme automatically.
   ========================================================================== */

/* ==========================================================================
   1. Theme tokens
   ========================================================================== */

/* Light theme — the default for the authenticated back-office UI. */
/* Light theme — the default for the authenticated back-office UI.

   The palette is Sparebanken Møre's, lifted from the :root block their own site
   ships (www.sbm.no, /dist/client/assets/default-layout-*.css). Their names are
   kept in the comments so a token can be traced back to the brand:

     hav       #0066ff  "sea"       the brand blue, everything primary
     byge      #000e5a  "shower"    the deep blue, primary-dark
     marine    #00149e               between the two, used here as the accent
     midnatt   #01052f  "midnight"  the brand's black — all body text
     sky       #dcf3fd               pale blue, hover and highlight ground
     foss      #f2f6fc  "waterfall" the off-white every panel sits on
     tåkedis   #f2f2f2  "mist"      neutral grey
     vårgrønn  #a2e2b6  "spring green"  the secondary, a support colour only */
:root {
    --color-base-100: #ffffff;
    --color-base-200: #f2f6fc;   /* foss */
    --color-base-300: #e2e9f5;   /* between foss and byge-18 — visible borders */
    --color-base-content: #01052f;   /* midnatt */

    --color-primary: #0066ff;    /* hav — the brand blue */
    --color-primary-content: #ffffff;
    --color-secondary: #000e5a;  /* byge — the deep blue, for a second-rank action */
    --color-secondary-content: #ffffff;
    --color-accent: #00149e;     /* marine */
    --color-accent-content: #ffffff;
    --color-neutral: #01052f;    /* midnatt */
    --color-neutral-content: #f2f6fc;

    /* Status colours. The bank's own site declares #0f0/#f00/#ff0 as
       placeholders it never actually paints with; those are unusable at text
       sizes, so these are proper accessible tones tuned to sit beside the blue.
       Success is pulled toward vårgrønn's hue so it still reads as brand. */
    --color-info: #0066ff;
    --color-info-content: #ffffff;
    --color-success: #10743f;
    --color-success-content: #ffffff;
    --color-warning: #9a5b00;
    --color-warning-content: #ffffff;
    --color-error: #c1121f;
    --color-error-content: #ffffff;

    /* Radii, from the bank's --border-radius scale:
         --border-radius-xs  .25rem   → nothing in this UI is that square
         --border-radius-s   .625rem  → fields
         --border-radius     1.25rem  → boxes and cards
         --border-radius-xl  6.25rem  → their pill buttons and chips
       `selector` is what pills, badges and toggles use here, so it takes the
       pill value; that is the single most recognisable shape on sbm.no. */
    --radius-selector: 6.25rem;
    --radius-field: 0.625rem;
    --radius-box: 1.25rem;

    /* Height of a default button, input and select.

       3rem is a touch target: correct for the wall-mounted kiosk and the phone
       check-in flow, which is why it is the base value. The authenticated
       back-office shell overrides it to 2.5rem further down — that UI is driven
       with a mouse and keyboard, and a toolbar of 48px controls wastes a lot of
       vertical space on a laptop. The size modifiers (btn-sm, btn-lg, input-sm,
       …) set explicit heights and are unaffected by either value. */
    --control-height: 3rem;

    /* SBM Sans is the bank's corporate face and carries both body and headings
       on their site, so it does the same here. Instrument Sans backs it up —
       see the licensing note in fonts.css. */
    --font-sans: 'SBM Sans', 'Instrument Sans', ui-sans-serif, system-ui,
        -apple-system, 'Segoe UI', Roboto, Arial, sans-serif,
        'Apple Color Emoji', 'Segoe UI Emoji';
    --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
        'Liberation Mono', monospace;
}

/* Dark theme — selected by the employee's saved theme preference.

   Built on midnatt (#01052f) rather than a neutral grey, so dark mode is still
   recognisably the bank's. The blue is lifted off #0066ff, which is too dark to
   read as an accent against a near-black blue ground, and vårgrønn — a pastel
   that is invisible on white — finally earns its place as the success colour. */
[data-theme='dark'] {
    --color-base-100: #0a1035;
    --color-base-200: #050a26;
    --color-base-300: #01052f;   /* midnatt */
    --color-base-content: #e8eefc;

    --color-primary: #4d94ff;
    --color-primary-content: #01052f;
    --color-secondary: #a2e2b6;  /* vårgrønn */
    --color-secondary-content: #01052f;
    --color-accent: #dcf3fd;     /* sky */
    --color-accent-content: #01052f;
    --color-neutral: #01052f;
    --color-neutral-content: #e8eefc;

    --color-info: #4d94ff;
    --color-info-content: #01052f;
    --color-success: #a2e2b6;    /* vårgrønn */
    --color-success-content: #04361a;
    --color-warning: #f5c26b;
    --color-warning-content: #2e1c00;
    --color-error: #ff8a8a;
    --color-error-content: #3b0508;
}

/* Back-office density.

   Set on <body> by layouts/app.blade.php only. The authenticated shell is a
   mouse-and-keyboard UI where a row of 48px controls eats a lot of screen, so
   its default control height is 2.5rem. Every other layout — the kiosk, the
   mobile visitor flow, the public screens and the login page — keeps the 3rem
   touch height from :root, so nothing that is tapped with a finger shrinks. */
.app-shell { --control-height: 2.5rem; }

/* ==========================================================================
   2. Reset and base elements
   ========================================================================== */

*,
*::before,
*::after {
    box-sizing: border-box;
    border-width: 0;
    border-style: solid;
    border-color: var(--color-base-300);
}

* {
    margin: 0;
}

html {
    -webkit-text-size-adjust: 100%;
    tab-size: 4;
    line-height: 1.5;
}

body {
    min-height: 100vh;
    font-family: var(--font-sans);
    font-size: 1rem;
    line-height: 1.5;
    color: var(--color-base-content);
    background-color: var(--color-base-200);
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
    font-size: inherit;
    font-weight: inherit;
}

a {
    color: inherit;
    text-decoration: none;
}

img, svg, video, canvas, iframe {
    display: block;
    max-width: 100%;
}

img, svg, video {
    height: auto;
}

ul, ol {
    list-style: none;
    padding: 0;
}

button, input, select, textarea, optgroup {
    font: inherit;
    color: inherit;
    margin: 0;
    /* Browsers give <button> a default padding (~1px 6px). Left in place it
       offsets any bare button against neighbouring text — the sign-out button
       sat indented from the profile link beside it in the user menu. Component
       classes such as .btn set their own padding after this rule. */
    padding: 0;
}

button {
    background: none;
    cursor: pointer;
}

button:disabled {
    cursor: not-allowed;
}

table {
    border-collapse: collapse;
    width: 100%;
}

hr {
    border-top-width: 1px;
}

[hidden] {
    display: none !important;
}

/* Hide Alpine x-cloak elements until Alpine initialises. */
[x-cloak] {
    display: none !important;
}

/* ==========================================================================
   10. Components

   DaisyUI-equivalent component classes. These carry the visual identity of the
   back-office UI, so they are written against the theme tokens only.
   ========================================================================== */

/* ── Buttons ─────────────────────────────────────────────────────────────── */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    height: var(--control-height);
    min-height: var(--control-height);
    padding: 0 1rem;
    /* The neutral button carries its own surface and hairline.

       It used to be `background: var(--color-base-200)` with a transparent
       border — the exact colour of the page, since <body> is `bg-base-200`.
       Inside a card that read fine, but every neutral button placed on the page
       itself (the Edit action in a show view's header row, for one) rendered as
       bare text with no button shape until it was hovered. base-100 plus a
       base-300 hairline reads as a button on both surfaces and in both themes. */
    border: 1px solid var(--color-base-300);
    border-radius: var(--radius-field);
    background-color: var(--color-base-100);
    color: var(--color-base-content);
    font-size: 0.875rem;
    font-weight: 600;
    line-height: 1;
    text-align: center;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
    transition: background-color 150ms ease, border-color 150ms ease,
        color 150ms ease, box-shadow 150ms ease, transform 100ms ease;
}
.btn:hover { background-color: var(--color-base-200); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.btn:disabled,
.btn-disabled {
    opacity: 0.5;
    pointer-events: none;
}

/* Filled variants clear the neutral button's hairline — a coloured fill is its
   own edge, and a base-300 border around a red button reads as a stray outline. */
/* Follow the shared submit handler's disabled state, including its back-navigation reset. */
.evac-start-pending-label { display: none; }
button:disabled .evac-start-label { display: none; }
button:disabled .evac-start-pending-label { display: inline; }

.btn-primary { background-color: var(--color-primary); color: var(--color-primary-content); border-color: transparent; }
.btn-primary:hover { background-color: color-mix(in srgb, var(--color-primary) 85%, black); }
.btn-error { background-color: var(--color-error); color: var(--color-error-content); border-color: transparent; }
.btn-error:hover { background-color: color-mix(in srgb, var(--color-error) 85%, black); }
.btn-success { background-color: var(--color-success); color: var(--color-success-content); border-color: transparent; }
.btn-success:hover { background-color: color-mix(in srgb, var(--color-success) 85%, black); }
.btn-warning { background-color: var(--color-warning); color: var(--color-warning-content); border-color: transparent; }
.btn-warning:hover { background-color: color-mix(in srgb, var(--color-warning) 85%, black); }

.btn-ghost {
    background-color: transparent;
    border-color: transparent;
    color: inherit;
}
.btn-ghost:hover { background-color: color-mix(in srgb, var(--color-base-content) 10%, transparent); }

.btn-outline {
    background-color: transparent;
    border-color: currentColor;
}
.btn-outline:hover {
    background-color: var(--color-base-content);
    color: var(--color-base-100);
}
/* Outline pairings for every colour modifier.

   These are REQUIRED, not decorative. A colour modifier such as .btn-error sets
   a filled background plus its *content* colour for the label. .btn-outline then
   clears the background but cannot clear that label colour, so the text keeps a
   colour chosen to sit on the filled variant and becomes invisible against the
   page — `btn-outline btn-error` rendered white-on-white in the light theme, and
   `btn-outline btn-warning` near-black on the dark kiosk. Each pairing therefore
   has to restate the label colour as the variant's own colour, and restore the
   content colour only on hover, where the fill comes back. */
.btn-outline.btn-primary { color: var(--color-primary); border-color: var(--color-primary); }
.btn-outline.btn-primary:hover { background-color: var(--color-primary); color: var(--color-primary-content); }
.btn-outline.btn-error { color: var(--color-error); border-color: var(--color-error); }
.btn-outline.btn-error:hover { background-color: var(--color-error); color: var(--color-error-content); }
.btn-outline.btn-success { color: var(--color-success); border-color: var(--color-success); }
.btn-outline.btn-success:hover { background-color: var(--color-success); color: var(--color-success-content); }
.btn-outline.btn-warning { color: var(--color-warning); border-color: var(--color-warning); }
.btn-outline.btn-warning:hover { background-color: var(--color-warning); color: var(--color-warning-content); }

.btn-active { background-color: var(--color-base-300); }

.btn-xs { height: 1.5rem; min-height: 1.5rem; padding: 0 0.5rem; font-size: 0.6875rem; }
.btn-sm { height: 2rem; min-height: 2rem; padding: 0 0.75rem; font-size: 0.8125rem; }
.btn-lg { height: 4rem; min-height: 4rem; padding: 0 1.5rem; font-size: 1.125rem; }
.btn-block { display: flex; width: 100%; }
/* Square buttons follow the control height, or they stop being square as soon
   as the shell changes it. */
.btn-square { width: var(--control-height); padding: 0; }
.btn-square.btn-sm { width: 2rem; }
.btn-square.btn-xs { width: 1.5rem; }
.no-animation { transition: none; }

/* ── Cards ───────────────────────────────────────────────────────────────── */
.card {
    position: relative;
    display: flex;
    flex-direction: column;
    border-radius: var(--radius-box);
    background-color: var(--color-base-100);
}
.card-body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.5rem;
    padding: 1.5rem;
}
.card-title {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 1.125rem;
    font-weight: 600;
}
.card-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 0.5rem;
}

/* ── Form controls ───────────────────────────────────────────────────────── */
.form-control {
    display: flex;
    flex-direction: column;
}
.label {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.25rem 0.125rem;
    user-select: none;
}
.label-text {
    font-size: 0.875rem;
    color: var(--color-base-content);
}
.label-text-alt {
    font-size: 0.75rem;
    color: color-mix(in srgb, var(--color-base-content) 60%, transparent);
}

/* Form controls are NOT full-width by default.

   The views were authored against DaisyUI, where these size to their content
   and `w-full` is added wherever full width is wanted — and it is, on ~230 of
   them. Forcing width:100% here instead made every toolbar filter claim a whole
   row, which broke the data-table's single-row toolbar into a stack. The ~80
   controls with no width class are almost all `input-sm`/`select-sm` filters
   that are meant to sit inline.

   Fields wrapped in `.form-control` (the form layout wrapper) do fill their
   column, which is what a stacked form wants and keeps the handful of bare
   `input input-bordered` form fields looking right. */
.input,
.select,
.textarea,
.file-input {
    display: block;
    height: var(--control-height);
    padding: 0 0.75rem;
    border: 1px solid transparent;
    border-radius: var(--radius-field);
    background-color: var(--color-base-100);
    color: var(--color-base-content);
    font-size: 0.875rem;
    line-height: 1.5;
    transition: border-color 150ms ease, box-shadow 150ms ease;
}
.textarea {
    height: auto;
    min-height: 5rem;
    padding: 0.625rem 0.75rem;
    line-height: 1.5;
}
.select {
    padding-right: 2rem;
    /* Native arrow replaced with an inline SVG so the control matches the theme. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.5rem center;
    background-size: 1rem;
    appearance: none;
}
.file-input { padding: 0.5rem 0.75rem; }
.file-input::file-selector-button {
    height: 2rem;
    margin-right: 0.75rem;
    padding: 0 0.75rem;
    border: 0;
    border-radius: var(--radius-field);
    background-color: var(--color-base-300);
    color: var(--color-base-content);
    font-size: 0.8125rem;
    font-weight: 600;
    cursor: pointer;
}

/* Inside a form layout wrapper, controls fill the column (see the note above). */
.form-control > .input,
.form-control > .select,
.form-control > .textarea,
.form-control > .file-input {
    width: 100%;
}

.input-bordered,
.select-bordered,
.textarea-bordered,
.file-input-bordered {
    border-color: color-mix(in srgb, var(--color-base-content) 20%, transparent);
}

.input:focus,
.select:focus,
.textarea:focus,
.file-input:focus {
    outline: none;
    border-color: var(--color-primary);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-primary) 25%, transparent);
}

.input-xs { height: 1.5rem; font-size: 0.6875rem; padding: 0 0.5rem; }
.input-sm, .file-input-sm { height: 2rem; font-size: 0.8125rem; padding: 0 0.5rem; }
/* A select keeps extra room on the trailing edge for its arrow. This must set
   the sides individually — sharing the `padding` shorthand above would reset
   the base rule's padding-right and let long option text run under the arrow. */
.select-sm {
    height: 2rem;
    font-size: 0.8125rem;
    padding-left: 0.5rem;
    padding-right: 1.75rem;
}
.textarea-sm { min-height: 4rem; font-size: 0.8125rem; }
.input-lg { height: 4rem; font-size: 1.125rem; padding: 0 1rem; }

/* Checkbox / radio / toggle — restyled native controls, so they stay accessible. */
.checkbox {
    flex-shrink: 0;
    width: 1.5rem;
    height: 1.5rem;
    border: 1px solid color-mix(in srgb, var(--color-base-content) 35%, transparent);
    border-radius: 0.25rem;
    background-color: var(--color-base-100);
    appearance: none;
    cursor: pointer;
}
.checkbox:checked {
    background-color: var(--color-base-content);
    border-color: var(--color-base-content);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.5'%3E%3Cpath d='M3 8.5l3.5 3.5L13 5'/%3E%3C/svg%3E");
    background-size: contain;
}
.checkbox-primary:checked { background-color: var(--color-primary); border-color: var(--color-primary); }
.checkbox-warning:checked { background-color: var(--color-warning); border-color: var(--color-warning); }
.checkbox-xs { width: 1rem; height: 1rem; }
.checkbox-sm { width: 1.25rem; height: 1.25rem; }
.checkbox-lg { width: 2rem; height: 2rem; }

.radio {
    flex-shrink: 0;
    width: 1.5rem;
    height: 1.5rem;
    border: 1px solid color-mix(in srgb, var(--color-base-content) 35%, transparent);
    border-radius: 9999px;
    appearance: none;
    cursor: pointer;
}
.radio:checked {
    border-width: 6px;
    border-color: var(--color-base-content);
}
.radio-primary:checked { border-color: var(--color-primary); }
/* Matches .checkbox-sm, for a radio sitting in a dense form row. */
.radio-sm { width: 1.25rem; height: 1.25rem; }

.toggle {
    flex-shrink: 0;
    position: relative;
    width: 3rem;
    height: 1.5rem;
    border: 1px solid color-mix(in srgb, var(--color-base-content) 35%, transparent);
    border-radius: 9999px;
    background-color: color-mix(in srgb, var(--color-base-content) 20%, transparent);
    appearance: none;
    cursor: pointer;
    transition: background-color 150ms ease;
}
.toggle::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 0.125rem;
    width: 1.125rem;
    height: 1.125rem;
    border-radius: 9999px;
    background-color: #ffffff;
    transform: translateY(-50%);
    transition: left 150ms ease;
}
.toggle:checked::before { left: calc(100% - 1.25rem); }
.toggle-primary:checked { background-color: var(--color-primary); border-color: var(--color-primary); }
.toggle-warning:checked { background-color: var(--color-warning); border-color: var(--color-warning); }

/* ── Badges ──────────────────────────────────────────────────────────────── */
.badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.25rem;
    height: 1.5rem;
    padding: 0 0.625rem;
    border: 1px solid transparent;
    border-radius: 9999px;
    background-color: var(--color-base-200);
    color: var(--color-base-content);
    font-size: 0.75rem;
    font-weight: 500;
    white-space: nowrap;
}
.badge-xs { height: 0.875rem; padding: 0 0.375rem; font-size: 0.625rem; }
.badge-sm { height: 1.25rem; padding: 0 0.5rem; font-size: 0.6875rem; }
.badge-lg { height: 1.75rem; padding: 0 0.75rem; font-size: 0.875rem; }
.badge-ghost { background-color: color-mix(in srgb, var(--color-base-content) 8%, transparent); }
.badge-outline { background-color: transparent; border-color: currentColor; }
.badge-primary { background-color: var(--color-primary); color: var(--color-primary-content); }
.badge-accent { background-color: var(--color-accent); color: var(--color-accent-content); }
.badge-info { background-color: var(--color-info); color: var(--color-info-content); }
.badge-success { background-color: var(--color-success); color: var(--color-success-content); }
.badge-warning { background-color: var(--color-warning); color: var(--color-warning-content); }
.badge-error { background-color: var(--color-error); color: var(--color-error-content); }
.badge-neutral { background-color: var(--color-neutral); color: var(--color-neutral-content); }

/* ── Alerts ──────────────────────────────────────────────────────────────── */
.alert {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.875rem 1rem;
    border: 1px solid color-mix(in srgb, var(--color-base-content) 12%, transparent);
    border-radius: var(--radius-box);
    background-color: var(--color-base-200);
    font-size: 0.875rem;
}
.alert-info {
    background-color: color-mix(in srgb, var(--color-info) 12%, var(--color-base-100));
    border-color: color-mix(in srgb, var(--color-info) 35%, transparent);
}
.alert-success {
    background-color: color-mix(in srgb, var(--color-success) 12%, var(--color-base-100));
    border-color: color-mix(in srgb, var(--color-success) 35%, transparent);
}
.alert-warning {
    background-color: color-mix(in srgb, var(--color-warning) 14%, var(--color-base-100));
    border-color: color-mix(in srgb, var(--color-warning) 35%, transparent);
}
.alert-error {
    background-color: color-mix(in srgb, var(--color-error) 12%, var(--color-base-100));
    border-color: color-mix(in srgb, var(--color-error) 35%, transparent);
}

/* ── Tables ──────────────────────────────────────────────────────────────── */
.table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 0.875rem;
    text-align: left;
}
.table th,
.table td {
    padding: 0.75rem 1rem;
    vertical-align: middle;
    border-bottom: 1px solid var(--color-base-200);
}
.table th {
    font-size: 0.75rem;
    font-weight: 600;
    color: color-mix(in srgb, var(--color-base-content) 60%, transparent);
    white-space: nowrap;
}
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background-color: color-mix(in srgb, var(--color-base-content) 4%, transparent); }
.table-xs th, .table-xs td { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
.table-sm th, .table-sm td { padding: 0.5rem 0.75rem; }

/* ── Stacking table (.table-stack) ────────────────────────────────────────────
   A table that becomes one labelled card per row on a narrow screen, instead of
   scrolling sideways inside its container.

   Used where the reader is on a phone and cannot afford to hunt: the public
   evacuation roll, where every cell (a phone number, a supervisor) is something
   somebody is about to act on. Each `td` carries `data-label="…"`, which becomes
   the row label below the breakpoint; above it, the table is an ordinary table
   and the labels are not rendered at all. */
@media (max-width: 639px) {
    .table-stack thead { display: none; }

    .table-stack,
    .table-stack tbody,
    .table-stack tr,
    .table-stack td {
        display: block;
        width: 100%;
    }

    /* Each row reads as its own card. */
    .table-stack tr {
        margin-bottom: 0.75rem;
        border: 1px solid var(--color-base-200);
        border-radius: var(--radius-box);
        padding: 0.25rem 0;
    }
    .table-stack tr:last-child { margin-bottom: 0; }

    /* Label left, value right — the label column is fixed so values line up
       down the card. */
    .table-stack td {
        display: grid;
        grid-template-columns: 7.5rem 1fr;
        gap: 0.5rem;
        align-items: start;
        border-bottom: 0;
        padding: 0.375rem 0.75rem;
    }
    .table-stack td::before {
        content: attr(data-label);
        font-size: 0.75rem;
        font-weight: 600;
        color: color-mix(in srgb, var(--color-base-content) 60%, transparent);
    }
    /* A cell with no label (an actions column) spans the full width. */
    .table-stack td:not([data-label]) { display: block; }
    .table-stack td:not([data-label])::before { content: none; }
}

/* ── Breadcrumbs ─────────────────────────────────────────────────────────── */
.breadcrumbs {
    max-width: 100%;
    overflow-x: auto;
    padding: 0.5rem 0;
    font-size: 0.875rem;
}
.breadcrumbs > ul {
    display: flex;
    align-items: center;
    white-space: nowrap;
    min-height: min-content;
}
.breadcrumbs > ul > li { display: flex; align-items: center; }
.breadcrumbs > ul > li + li::before {
    content: '';
    display: block;
    width: 0.375rem;
    height: 0.375rem;
    margin: 0 0.5rem;
    border-top: 1px solid currentColor;
    border-right: 1px solid currentColor;
    opacity: 0.4;
    transform: rotate(45deg);
}

/* ── Menu (sidebar navigation) ───────────────────────────────────────────── */
.menu {
    display: flex;
    flex-direction: column;
    /* NOT flex-wrap: wrap. On a column flex container, wrapping spills overflow
       into additional COLUMNS rather than scrolling, which pushed the tail of
       the sidebar out to the right of the panel. The sidebar sets its own
       overflow-y, which is what should handle a long nav. */
    flex-wrap: nowrap;
    padding: 0.5rem;
    font-size: 0.875rem;
}

/* Nested submenus (the <ul> inside a collapsible group) stack and sit indented
   under their heading. */
.menu ul {
    display: flex;
    flex-direction: column;
    margin-left: 0.75rem;
    padding-left: 0.5rem;
    border-left: 1px solid color-mix(in srgb, var(--color-base-content) 12%, transparent);
}

/* Interactive rows. `<details>` is excluded because it is only a wrapper — its
   <summary> is the actual row and is matched separately, or the icon and label
   inside it would never be laid out and the group would render as a stack. */
.menu li > *:not(ul):not(details):not(.menu-title),
.menu li > details > summary {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-radius: var(--radius-field);
    text-align: left;
    cursor: pointer;
    transition: background-color 150ms ease;
}
.menu li > *:not(ul):not(details):not(.menu-title):hover,
.menu li > details > summary:hover {
    background-color: color-mix(in srgb, var(--color-base-content) 8%, transparent);
}
.menu li > *.active,
.menu li > *.menu-active {
    background-color: var(--color-primary);
    color: var(--color-primary-content);
}

/* Hovering the current page must not wash it out.

   The generic row-hover rule above is the more specific selector — its three
   :not() arguments outweigh `.menu li > *.menu-active` — so hovering the active
   row replaced the primary fill with a translucent grey while the label kept
   its primary-content colour. In the light theme that is white text on a
   near-white background, i.e. unreadable. These selectors repeat the hover
   rule's shape so they outrank it, and keep the brand fill (just darkened for
   feedback) rather than dropping to the plain row-hover look, which would also
   have thrown away the "you are here" cue for as long as the pointer rests on
   it. Darkening the fill keeps the contrast right in both themes. */
.menu li > *:not(ul):not(details):not(.menu-title).active:hover,
.menu li > *:not(ul):not(details):not(.menu-title).menu-active:hover {
    background-color: color-mix(in srgb, var(--color-primary) 85%, black);
    color: var(--color-primary-content);
}

/* Replace the native disclosure triangle with a chevron on the trailing edge,
   so a group heading reads like the rest of the nav rows. */
.menu li > details > summary {
    list-style: none;
}
.menu li > details > summary::-webkit-details-marker {
    display: none;
}
.menu li > details > summary::after {
    content: '';
    width: 0.4rem;
    height: 0.4rem;
    margin-left: auto;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    opacity: 0.5;
    transform: rotate(-45deg);
    transition: transform 150ms ease;
}
.menu li > details[open] > summary::after {
    transform: rotate(45deg);
}
.menu-sm li > *:not(ul):not(.menu-title) { padding: 0.375rem 0.625rem; font-size: 0.8125rem; }

/* ── Sidebar rail (the collapsed navigation) ─────────────────────────────────

   The desktop collapse used to hide the sidebar outright, which cost the user
   every cue about where they were in the app. Collapsed now means a rail: the
   icons stay put and keep their active highlight, while the labels, the group
   chevrons and the submenus step aside. app.js reveals the label as a floating
   tooltip on hover — a CSS ::after tooltip like `.tooltip` above cannot be used
   here, because the sidebar is its own vertical scroll container and would clip
   anything drawn outside its edge.

   The state lives on <html>, not on the sidebar, so the inline script in the
   layout's <head> can restore it before the first paint — putting it on the
   sidebar itself meant the full-width nav painted first and visibly snapped to
   the rail a moment later. The width selector carries the sidebar's id so it
   beats the `w-64` utility the markup carries, whatever the rule order. */
.sidebar-rail #app-sidebar { width: 4rem; }

.sidebar-rail .menu { padding: 0.5rem 0.375rem; }

/* Labels are hidden visually but stay in the accessibility tree — `display:none`
   would strip them, leaving each row as an icon with no accessible name. */
.sidebar-rail .menu li > a > span,
.sidebar-rail .menu li > details > summary > span {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* A submenu and the credit line have nowhere to go in a 4rem column. Opening a
   group is handled in app.js instead: it expands the sidebar back to full width. */
.sidebar-rail .menu li > details > ul,
.sidebar-rail .sidebar-credits { display: none; }
.sidebar-rail .menu li > details > summary::after { content: none; }

/* Centre what is left of each row. */
.sidebar-rail .menu li > *:not(ul):not(details):not(.menu-title),
.sidebar-rail .menu li > details > summary {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
}

/* When the current page sits inside a group, the highlighted child is hidden in
   the collapsed submenu — so the group's own icon takes the highlight. Scoped to
   the rail: an expanded sidebar shows the open group and its active child, and
   lighting up both would read as two selections. */
.sidebar-rail .menu li > details > summary.menu-group-active {
    background-color: var(--color-primary);
    color: var(--color-primary-content);
}
/* Same darken-on-hover as an active row, for the same reason. */
.sidebar-rail .menu li > details > summary.menu-group-active:hover {
    background-color: color-mix(in srgb, var(--color-primary) 85%, black);
    color: var(--color-primary-content);
}

/* The floating rail label. Fixed rather than absolute so the sidebar's own
   overflow cannot clip it; positioned by app.js against the hovered row. */
.sidebar-tooltip {
    position: fixed;
    z-index: 60;
    padding: 0.25rem 0.5rem;
    border-radius: 0.375rem;
    background-color: var(--color-neutral);
    color: var(--color-neutral-content);
    font-size: 0.75rem;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 120ms ease;
}
.sidebar-tooltip.is-visible { opacity: 1; }

/* ── Modal ───────────────────────────────────────────────────────────────── */
/* Modals are HIDDEN by default and revealed by `.modal-open`, which the views
   toggle from Alpine (`:class="{ 'modal-open': open }"`). Without this a modal
   would render as a permanent full-screen overlay covering the page. */
.modal {
    position: fixed;
    inset: 0;
    display: none;
    place-items: center;
    z-index: 200;
    padding: 1rem;
    background-color: rgba(0, 0, 0, 0.45);
    overflow-y: auto;
}
.modal-open,
.modal[open] {
    display: grid;
}
.modal-box {
    position: relative;
    width: 100%;
    max-width: 32rem;
    max-height: calc(100vh - 5rem);
    padding: 1.5rem;
    border-radius: var(--radius-box);
    background-color: var(--color-base-100);
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25);
    overflow-y: auto;
}
.modal-action {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 1.5rem;
}
.modal-backdrop { position: fixed; inset: 0; z-index: -1; }

/* ── Dropdown ────────────────────────────────────────────────────────────── */
/* Dropdowns are CLOSED by default and opened by one of two triggers, matching
   the two markup patterns in the app:

     1. `.dropdown-open` on the wrapper, toggled by Alpine (the navbar user menu).
     2. `<details class="dropdown">` with a `<summary>` trigger (the data-table
        export menu, payroll), opened by the native `[open]` attribute.

   Deliberately NOT `:focus-within`. Focus cannot express a toggle: clicking the
   trigger a second time leaves it focused, so a focus-driven menu can only ever
   open, never close. Any new dropdown should drive `.dropdown-open` from Alpine
   rather than relying on focus.

   Hiding uses visibility/opacity rather than `display`, deliberately: the
   content element is usually also a `.menu` (display: flex), and toggling
   `display` here would fight that rule. `visibility: hidden` still removes the
   menu from hit-testing and the accessibility tree, so a closed menu cannot be
   clicked or tabbed into. */
.dropdown { position: relative; display: inline-flex; }
.dropdown-content {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 50;
    border-radius: var(--radius-box);
    background-color: var(--color-base-100);
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
    visibility: hidden;
    opacity: 0;
    transform: translateY(-0.25rem);
    transition: opacity 150ms ease, transform 150ms ease, visibility 150ms;
}
.dropdown.dropdown-open > .dropdown-content,
.dropdown[open] > .dropdown-content {
    visibility: visible;
    opacity: 1;
    transform: translateY(0);
}
.dropdown-end .dropdown-content { left: auto; right: 0; }

/* ── Searchable select (<x-searchable-select>) ───────────────────────────────
   A type-to-filter single-select for lists too long to scan. Select2 without
   Select2: that library needs jQuery, which this project does not use, so the
   control is built from `.select` (the trigger) and `.menu` (the list) and
   therefore inherits every theme automatically.

   The panel is `display: none` when closed rather than the visibility/opacity
   trick used by `.dropdown` above: this one is toggled by Alpine's `x-show`,
   which sets `display` itself, and there is no open/close transition to
   preserve. */
.searchable-select { position: relative; }

/* The trigger is a <button> wearing `.select`, so the shared control rules give
   it the right height, border and themed arrow. These few lines re-add what a
   button does not inherit from an <input>. */
.searchable-select-trigger {
    display: flex;
    align-items: center;
    text-align: left;
    cursor: pointer;
}

.searchable-select-panel {
    position: absolute;
    top: calc(100% + 0.25rem);
    left: 0;
    right: 0;
    z-index: 50;
    padding: 0.5rem;
    border: 1px solid color-mix(in srgb, var(--color-base-content) 15%, transparent);
    border-radius: var(--radius-box);
    background-color: var(--color-base-100);
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}

/* Capped so a long list scrolls inside the panel instead of running off the
   page; the keyboard highlight scrolls itself into view. */
.searchable-select-list {
    max-height: 15rem;
    overflow-y: auto;
    margin-top: 0.375rem;
    padding: 0;
    flex-wrap: nowrap;
}

/* The options are <button>s, which `.menu` styles as rows already — this just
   makes them fill the width and keeps long labels on one line. */
.searchable-select-list li > button {
    width: 100%;
    text-align: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ── Searchable multiselect (<x-searchable-multiselect>) ─────────────────────
   The many-values variant, used for index filters. It reuses every rule above;
   only two things differ. */

/* The trigger sits in a toolbar row and is therefore narrow, but the option
   labels (locations, asset types) are not — so the panel is allowed to be wider
   than the control and to grow rightwards instead of being squeezed. */
.searchable-multiselect-panel {
    right: auto;
    min-width: max(100%, 16rem);
}

/* Checkbox + label on one row. `gap` rather than a margin so the checkbox keeps
   its spacing when the label is empty. */
.searchable-multiselect-option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

/* ── Tabs ────────────────────────────────────────────────────────────────── */
.tabs { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.25rem; }
.tab {
    display: inline-flex;
    align-items: center;
    height: 2.5rem;
    padding: 0 1rem;
    border-radius: var(--radius-field);
    font-size: 0.875rem;
    color: color-mix(in srgb, var(--color-base-content) 65%, transparent);
    cursor: pointer;
}
.tab-active { background-color: var(--color-base-100); color: var(--color-base-content); font-weight: 600; }
.tabs-boxed { padding: 0.25rem; border-radius: var(--radius-box); background-color: var(--color-base-200); }

/* ── Stats ───────────────────────────────────────────────────────────────── */
.stats {
    display: grid;
    grid-auto-flow: column;
    border-radius: var(--radius-box);
    background-color: var(--color-base-100);
    overflow-x: auto;
}
.stats-vertical { grid-auto-flow: row; }
.stat {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 0.25rem 1rem;
    padding: 1rem 1.5rem;
    border-top: 1px solid var(--color-base-200);
}
.stats-vertical > .stat:first-child,
.stats:not(.stats-vertical) > .stat { border-top: 0; }
.stat-title {
    grid-column: 1;
    font-size: 0.75rem;
    color: color-mix(in srgb, var(--color-base-content) 60%, transparent);
}
.stat-value {
    grid-column: 1;
    font-size: 1.875rem;
    font-weight: 700;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}
.stat-desc {
    grid-column: 1;
    font-size: 0.75rem;
    color: color-mix(in srgb, var(--color-base-content) 55%, transparent);
}
.stat-figure { grid-column: 2; grid-row: 1 / span 3; align-self: center; }

/* ── Progress ────────────────────────────────────────────────────────────── */
.progress {
    width: 100%;
    height: 0.5rem;
    border-radius: 9999px;
    background-color: color-mix(in srgb, var(--color-base-content) 15%, transparent);
    appearance: none;
    overflow: hidden;
}
.progress::-webkit-progress-bar { background-color: transparent; }
.progress::-webkit-progress-value { background-color: var(--color-base-content); border-radius: 9999px; }
.progress::-moz-progress-bar { background-color: var(--color-base-content); }
.progress-primary::-webkit-progress-value { background-color: var(--color-primary); }
.progress-primary::-moz-progress-bar { background-color: var(--color-primary); }
.progress-success::-webkit-progress-value { background-color: var(--color-success); }
.progress-success::-moz-progress-bar { background-color: var(--color-success); }
.progress-warning::-webkit-progress-value { background-color: var(--color-warning); }
.progress-warning::-moz-progress-bar { background-color: var(--color-warning); }
.progress-error::-webkit-progress-value { background-color: var(--color-error); }
.progress-error::-moz-progress-bar { background-color: var(--color-error); }

/* ── Join (segmented control) ────────────────────────────────────────────── */
.join { display: inline-flex; align-items: stretch; }
.join-item { border-radius: 0; }
.join > .join-item:first-child { border-top-left-radius: var(--radius-field); border-bottom-left-radius: var(--radius-field); }
.join > .join-item:last-child { border-top-right-radius: var(--radius-field); border-bottom-right-radius: var(--radius-field); }

/* ── Divider ─────────────────────────────────────────────────────────────── */
.divider {
    display: flex;
    align-items: center;
    margin: 0.75rem 0;
    font-size: 0.8125rem;
    color: color-mix(in srgb, var(--color-base-content) 55%, transparent);
}
.divider::before,
.divider::after {
    content: '';
    flex: 1 1 0;
    height: 1px;
    background-color: var(--color-base-300);
}
.divider::before { margin-right: 0.75rem; }
.divider::after { margin-left: 0.75rem; }

/* ── Avatar ──────────────────────────────────────────────────────────────── */
.avatar { display: inline-flex; }
.avatar > div { overflow: hidden; }
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.avatar-placeholder > div {
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--color-neutral);
    color: var(--color-neutral-content);
}

/* ── Timeline ────────────────────────────────────────────────────────────── */
.timeline { display: grid; }
.timeline-vertical { grid-auto-flow: row; }
.timeline-compact { gap: 0.25rem; }
.timeline-start { grid-column: 1; }
.timeline-middle { grid-column: 2; }
.timeline-end { grid-column: 3; }

/* ── Tooltip ─────────────────────────────────────────────────────────────── */
.tooltip { position: relative; }
.tooltip::after {
    content: attr(data-tip);
    position: absolute;
    z-index: 60;
    max-width: 16rem;
    padding: 0.25rem 0.5rem;
    border-radius: 0.25rem;
    background-color: var(--color-neutral);
    color: var(--color-neutral-content);
    font-size: 0.75rem;
    font-weight: 400;
    white-space: normal;
    width: max-content;
    opacity: 0;
    pointer-events: none;
    transition: opacity 150ms ease;
}
/* Reveal on hover AND focus — <x-help-tip> is focusable, so keyboard users must
   be able to read the tip too. */
.tooltip:hover::after,
.tooltip:focus::after,
.tooltip:focus-visible::after { opacity: 1; }

/* Direction variants, emitted by <x-help-tip> as tooltip-{top|bottom|left|right}. */
.tooltip::after,
.tooltip-top::after {
    bottom: 100%; left: 50%;
    transform: translate(-50%, -0.25rem);
}
.tooltip-bottom::after {
    top: 100%; bottom: auto; left: 50%;
    transform: translate(-50%, 0.25rem);
}
.tooltip-left::after {
    right: 100%; left: auto; top: 50%; bottom: auto;
    transform: translate(-0.25rem, -50%);
}
.tooltip-right::after {
    left: 100%; right: auto; top: 50%; bottom: auto;
    transform: translate(0.25rem, -50%);
}

/* ── Links ───────────────────────────────────────────────────────────────── */
.link { text-decoration-line: underline; cursor: pointer; }
.link-hover { text-decoration-line: none; }
.link-hover:hover { text-decoration-line: underline; }
.link-primary { color: var(--color-primary); }

/* ── Loading spinner ─────────────────────────────────────────────────────── */
.loading {
    display: inline-block;
    width: 1.5rem;
    height: 1.5rem;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 9999px;
    animation: spin 0.6s linear infinite;
}
/* `.loading` already renders as a spinner; `.loading-spinner` exists so the
   class the submit-loading script injects (public/js/app.js) is a real rule
   rather than a silent no-op. */
.loading-spinner { border-radius: 9999px; }
.loading-sm { width: 1rem; height: 1rem; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ==========================================================================
   3. Layout utilities
   ========================================================================== */

.block { display: block; }
.inline-block { display: inline-block; }
.inline { display: inline; }
.flex { display: flex; }
.inline-flex { display: inline-flex; }
.grid { display: grid; }
/* Lets a wrapper vanish from the layout while its children stay in the parent
   grid — needed where an <template x-for> loop must emit <dt>/<dd> pairs into a
   grid the loop itself is not a cell of. */
.contents { display: contents; }
.hidden { display: none; }

.flex-row { flex-direction: row; }
.flex-col { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.flex-1 { flex: 1 1 0%; }
.flex-none { flex: none; }
.grow { flex-grow: 1; }
.shrink-0 { flex-shrink: 0; }

.items-start { align-items: flex-start; }
.items-center { align-items: center; }
.items-end { align-items: flex-end; }
.items-stretch { align-items: stretch; }
.self-center { align-self: center; }

.justify-start { justify-content: flex-start; }
.justify-center { justify-content: center; }
.justify-end { justify-content: flex-end; }
.justify-between { justify-content: space-between; }

.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* Four across at every width, not only from sm: up — the kiosk welcome screen
   puts its four tiles in a single row, and a device narrower than 640px would
   otherwise fall back to the base single column and stack them. */
.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-cols-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
.grid-cols-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); }
.grid-cols-9 { grid-template-columns: repeat(9, minmax(0, 1fr)); }

/* Arbitrary-value grid templates used by the detail/definition layouts. */
.grid-cols-\[9rem_1fr\] { grid-template-columns: 9rem 1fr; }
.grid-cols-\[10rem_1fr\] { grid-template-columns: 10rem 1fr; }
.grid-cols-\[12rem_1fr\] { grid-template-columns: 12rem 1fr; }
.grid-cols-\[9rem_1fr_9rem_1fr\] { grid-template-columns: 9rem 1fr 9rem 1fr; }
.grid-cols-\[1fr_14rem_auto\] { grid-template-columns: 1fr 14rem auto; }
.grid-cols-\[1fr_auto\] { grid-template-columns: 1fr auto; }

.col-span-2 { grid-column: span 2 / span 2; }

.static { position: static; }
.relative { position: relative; }
.absolute { position: absolute; }
.fixed { position: fixed; }
.sticky { position: sticky; }

.inset-0 { inset: 0; }
.inset-x-0 { left: 0; right: 0; }
.inset-y-0 { top: 0; bottom: 0; }
.top-0 { top: 0; }
.top-16 { top: 4rem; }
.right-0 { right: 0; }
.bottom-0 { bottom: 0; }
.left-0 { left: 0; }
.left-\[-5\%\] { left: -5%; }

.z-0 { z-index: 0; }
.-z-10 { z-index: -10; }
.z-10 { z-index: 10; }
.z-30 { z-index: 30; }
.z-40 { z-index: 40; }
.z-\[200\] { z-index: 200; }

.overflow-hidden { overflow: hidden; }
.overflow-x-auto { overflow-x: auto; }
.overflow-y-auto { overflow-y: auto; }

.object-cover { object-fit: cover; }
.object-contain { object-fit: contain; }

.aspect-video { aspect-ratio: 16 / 9; }

.float-left { float: left; }

.pointer-events-none { pointer-events: none; }
.select-none { user-select: none; }
.cursor-pointer { cursor: pointer; }
.cursor-help { cursor: help; }

/* ==========================================================================
   4. Spacing utilities
   ========================================================================== */

.p-0 { padding: 0; }
.p-1 { padding: 0.25rem; }
.p-1\.5 { padding: 0.375rem; }
.p-2 { padding: 0.5rem; }
.p-3 { padding: 0.75rem; }
.p-4 { padding: 1rem; }
.p-6 { padding: 1.5rem; }
.p-7 { padding: 1.75rem; }
.p-8 { padding: 2rem; }
.p-10 { padding: 2.5rem; }

.px-0 { padding-left: 0; padding-right: 0; }
.px-1 { padding-left: 0.25rem; padding-right: 0.25rem; }
.px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
.px-4 { padding-left: 1rem; padding-right: 1rem; }
.px-5 { padding-left: 1.25rem; padding-right: 1.25rem; }
.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
.px-8 { padding-left: 2rem; padding-right: 2rem; }

.py-0 { padding-top: 0; padding-bottom: 0; }
.py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
.py-4 { padding-top: 1rem; padding-bottom: 1rem; }
.py-5 { padding-top: 1.25rem; padding-bottom: 1.25rem; }
.py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; }
.py-8 { padding-top: 2rem; padding-bottom: 2rem; }
.py-10 { padding-top: 2.5rem; padding-bottom: 2.5rem; }
.py-12 { padding-top: 3rem; padding-bottom: 3rem; }
.py-16 { padding-top: 4rem; padding-bottom: 4rem; }

.pt-1 { padding-top: 0.25rem; }
.pt-2 { padding-top: 0.5rem; }
.pt-2\.5 { padding-top: 0.625rem; }
.pt-3 { padding-top: 0.75rem; }
.pt-4 { padding-top: 1rem; }
.pt-5 { padding-top: 1.25rem; }
.pt-6 { padding-top: 1.5rem; }
.pb-1 { padding-bottom: 0.25rem; }
.pb-3 { padding-bottom: 0.75rem; }
.pb-4 { padding-bottom: 1rem; }
.pb-6 { padding-bottom: 1.5rem; }
.pl-3 { padding-left: 0.75rem; }
.pl-5 { padding-left: 1.25rem; }
.pr-3 { padding-right: 0.75rem; }

.m-0 { margin: 0; }
.mx-auto { margin-left: auto; margin-right: auto; }
.my-1 { margin-top: 0.25rem; margin-bottom: 0.25rem; }
.my-2 { margin-top: 0.5rem; margin-bottom: 0.5rem; }

.mt-0\.5 { margin-top: 0.125rem; }
.mt-1 { margin-top: 0.25rem; }
.mt-2 { margin-top: 0.5rem; }
.mt-3 { margin-top: 0.75rem; }
.mt-4 { margin-top: 1rem; }
.mt-5 { margin-top: 1.25rem; }
.mt-6 { margin-top: 1.5rem; }
.mt-8 { margin-top: 2rem; }
.mt-auto { margin-top: auto; }
.-mt-2 { margin-top: -0.5rem; }

.mb-1 { margin-bottom: 0.25rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-3 { margin-bottom: 0.75rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-5 { margin-bottom: 1.25rem; }
.mb-6 { margin-bottom: 1.5rem; }

.ml-1 { margin-left: 0.25rem; }
.ml-2 { margin-left: 0.5rem; }
.ml-3 { margin-left: 0.75rem; }
.ml-auto { margin-left: auto; }
.mr-3 { margin-right: 0.75rem; }

.gap-0\.5 { gap: 0.125rem; }
.gap-1 { gap: 0.25rem; }
.gap-1\.5 { gap: 0.375rem; }
.gap-2 { gap: 0.5rem; }
.gap-2\.5 { gap: 0.625rem; }
.gap-3 { gap: 0.75rem; }
.gap-4 { gap: 1rem; }
.gap-5 { gap: 1.25rem; }
.gap-6 { gap: 1.5rem; }
.gap-8 { gap: 2rem; }
.gap-x-4 { column-gap: 1rem; }
.gap-x-6 { column-gap: 1.5rem; }
.gap-y-1 { row-gap: 0.25rem; }
.gap-y-2 { row-gap: 0.5rem; }
.gap-y-3 { row-gap: 0.75rem; }

/* space-y-* puts the gap between siblings, matching Tailwind's behaviour. */
.space-y-1 > * + * { margin-top: 0.25rem; }
.space-y-1\.5 > * + * { margin-top: 0.375rem; }
.space-y-2 > * + * { margin-top: 0.5rem; }
.space-y-3 > * + * { margin-top: 0.75rem; }
.space-y-4 > * + * { margin-top: 1rem; }
.space-y-6 > * + * { margin-top: 1.5rem; }

.divide-y > * + * { border-top-width: 1px; }

/* ==========================================================================
   5. Sizing utilities
   ========================================================================== */

.w-0 { width: 0; }
.w-3 { width: 0.75rem; }
.w-3\.5 { width: 0.875rem; }
.w-4 { width: 1rem; }
.w-5 { width: 1.25rem; }
.w-6 { width: 1.5rem; }
.w-8 { width: 2rem; }
.w-9 { width: 2.25rem; }
.w-10 { width: 2.5rem; }
.w-12 { width: 3rem; }
.w-14 { width: 3.5rem; }
.w-16 { width: 4rem; }
.w-18 { width: 4.5rem; }
.w-20 { width: 5rem; }
.w-24 { width: 6rem; }
.w-28 { width: 7rem; }
.w-32 { width: 8rem; }
.w-36 { width: 9rem; }
.w-40 { width: 10rem; }
.w-44 { width: 11rem; }
.w-48 { width: 12rem; }
.w-64 { width: 16rem; }
.w-auto { width: auto; }
.w-full { width: 100%; }
.w-screen { width: 100vw; }
.w-fit { width: fit-content; }
.w-\[18px\] { width: 18px; }
.w-\[110\%\] { width: 110%; }
.w-\[276px\] { width: 276px; }

.h-1 { height: 0.25rem; }
.h-3 { height: 0.75rem; }
.h-3\.5 { height: 0.875rem; }
.h-4 { height: 1rem; }
.h-5 { height: 1.25rem; }
.h-6 { height: 1.5rem; }
.h-8 { height: 2rem; }
.h-10 { height: 2.5rem; }
.h-11 { height: 2.75rem; }
.h-12 { height: 3rem; }
.h-14 { height: 3.5rem; }
.h-16 { height: 4rem; }
.h-20 { height: 5rem; }
.h-24 { height: 6rem; }
.h-40 { height: 10rem; }
.h-auto { height: auto; }
.h-full { height: 100%; }
.h-screen { height: 100vh; }
.h-\[18px\] { height: 18px; }
.h-\[72px\] { height: 72px; }
.h-\[16vh\] { height: 16vh; }

.min-w-0 { min-width: 0; }
.min-w-40 { min-width: 10rem; }
.min-w-\[44rem\] { min-width: 44rem; }

.min-h-16 { min-height: 4rem; }
.min-h-screen { min-height: 100vh; }
.min-h-\[88px\] { min-height: 88px; }
.min-h-\[200px\] { min-height: 200px; }
.min-h-\[4\.75rem\] { min-height: 4.75rem; }

.max-w-full { max-width: 100%; }
.max-w-xs { max-width: 20rem; }
.max-w-sm { max-width: 24rem; }
.max-w-md { max-width: 28rem; }
.max-w-lg { max-width: 32rem; }
.max-w-xl { max-width: 36rem; }
.max-w-2xl { max-width: 42rem; }
.max-w-3xl { max-width: 48rem; }
.max-w-4xl { max-width: 56rem; }
.max-w-5xl { max-width: 64rem; }
.max-w-\[10rem\] { max-width: 10rem; }
.max-w-\[160px\] { max-width: 160px; }
.max-w-\[220px\] { max-width: 220px; }
.max-w-\[64\%\] { max-width: 64%; }
.max-w-\[92\%\] { max-width: 92%; }

.max-h-64 { max-height: 16rem; }
.max-h-72 { max-height: 18rem; }

/* ==========================================================================
   6. Typography utilities
   ========================================================================== */

.text-\[10px\] { font-size: 10px; line-height: 1.4; }
.text-\[11px\] { font-size: 11px; line-height: 1.45; }
.text-xs { font-size: 0.75rem; line-height: 1rem; }
.text-sm { font-size: 0.875rem; line-height: 1.25rem; }
.text-base { font-size: 1rem; line-height: 1.5rem; }
.text-lg { font-size: 1.125rem; line-height: 1.75rem; }
.text-xl { font-size: 1.25rem; line-height: 1.75rem; }
.text-2xl { font-size: 1.5rem; line-height: 2rem; }
.text-3xl { font-size: 1.875rem; line-height: 2.25rem; }
.text-4xl { font-size: 2.25rem; line-height: 2.5rem; }
.text-5xl { font-size: 3rem; line-height: 1.1; }
.text-6xl { font-size: 3.75rem; line-height: 1.05; }
.text-7xl { font-size: 4.5rem; line-height: 1.05; }
.text-8xl { font-size: 6rem; line-height: 1; }
.text-\[8vmin\] { font-size: 8vmin; line-height: 1.1; }

/* SBM Sans ships two cuts only — Regular (400) and SemiBold (600); see
   public/css/fonts.css, where the SemiBold face is declared over 500–700 so
   `font-semibold` and `font-bold` both land on a real outline rather than a
   synthesised one. The values below are unchanged because they still have to be
   right for the Instrument Sans fallback, which is a 400–700 variable face: what
   changes is only which of them the brand font can honour. 200/300 have no cut
   at all and resolve to Regular — `font-thin` on the kiosk clock is therefore a
   normal-weight clock now, which is correct for the brand rather than a
   regression. 800/900 likewise resolve down to SemiBold. */
.font-thin { font-weight: 200; }
.font-light { font-weight: 300; }
.font-normal { font-weight: 400; }
.font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }
.font-extrabold { font-weight: 800; }
.font-black { font-weight: 900; }

.font-mono { font-family: var(--font-mono); }
.tabular-nums { font-variant-numeric: tabular-nums; }

.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }

.uppercase { text-transform: uppercase; }
.italic { font-style: italic; }
.underline { text-decoration-line: underline; }

.leading-tight { line-height: 1.25; }
.leading-relaxed { line-height: 1.625; }

.tracking-tight { letter-spacing: -0.015em; }
.tracking-wide { letter-spacing: 0.025em; }
.tracking-wider { letter-spacing: 0.05em; }
.tracking-widest { letter-spacing: 0.1em; }
.tracking-\[0\.2em\] { letter-spacing: 0.2em; }
.tracking-\[0\.25em\] { letter-spacing: 0.25em; }
.tracking-\[0\.3em\] { letter-spacing: 0.3em; }
.tracking-\[0\.35em\] { letter-spacing: 0.35em; }
.tracking-\[0\.4em\] { letter-spacing: 0.4em; }
.tracking-\[0\.5em\] { letter-spacing: 0.5em; }

.truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.whitespace-nowrap { white-space: nowrap; }
.whitespace-pre-line { white-space: pre-line; }
.break-all { word-break: break-all; }

.list-disc { list-style-type: disc; }
.list-decimal { list-style-type: decimal; }
.list-inside { list-style-position: inside; }

.align-middle { vertical-align: middle; }
.align-top { vertical-align: top; }

/* ==========================================================================
   7. Colour utilities

   Fractional opacities use color-mix() so they track the active theme token
   instead of baking in a fixed rgba value.
   ========================================================================== */

.text-base-content { color: var(--color-base-content); }
.text-base-content\/80 { color: color-mix(in srgb, var(--color-base-content) 80%, transparent); }
.text-base-content\/70 { color: color-mix(in srgb, var(--color-base-content) 70%, transparent); }
.text-base-content\/60 { color: color-mix(in srgb, var(--color-base-content) 60%, transparent); }
.text-base-content\/50 { color: color-mix(in srgb, var(--color-base-content) 50%, transparent); }
.text-base-content\/40 { color: color-mix(in srgb, var(--color-base-content) 40%, transparent); }
.text-base-content\/30 { color: color-mix(in srgb, var(--color-base-content) 30%, transparent); }

.text-primary { color: var(--color-primary); }
.text-primary\/80 { color: color-mix(in srgb, var(--color-primary) 80%, transparent); }
.text-neutral-content { color: var(--color-neutral-content); }
.text-error { color: var(--color-error); }
.text-error-content { color: var(--color-error-content); }
.text-success { color: var(--color-success); }
.text-warning { color: var(--color-warning); }
.text-info { color: var(--color-info); }
.text-white { color: #ffffff; }
.text-white\/60 { color: rgba(255, 255, 255, 0.6); }
.text-white\/40 { color: rgba(255, 255, 255, 0.4); }

.bg-base-100 { background-color: var(--color-base-100); }
.bg-base-100\/50 { background-color: color-mix(in srgb, var(--color-base-100) 50%, transparent); }
.bg-base-100\/90 { background-color: color-mix(in srgb, var(--color-base-100) 90%, transparent); }
.bg-base-100\/80 { background-color: color-mix(in srgb, var(--color-base-100) 80%, transparent); }
.bg-base-100\/70 { background-color: color-mix(in srgb, var(--color-base-100) 70%, transparent); }
.bg-base-200 { background-color: var(--color-base-200); }
.bg-base-200\/60 { background-color: color-mix(in srgb, var(--color-base-200) 60%, transparent); }
.bg-base-200\/50 { background-color: color-mix(in srgb, var(--color-base-200) 50%, transparent); }
.bg-base-200\/40 { background-color: color-mix(in srgb, var(--color-base-200) 40%, transparent); }
.bg-base-300 { background-color: var(--color-base-300); }
.bg-base-300\/85 { background-color: color-mix(in srgb, var(--color-base-300) 85%, transparent); }
.bg-base-300\/50 { background-color: color-mix(in srgb, var(--color-base-300) 50%, transparent); }
.bg-base-300\/40 { background-color: color-mix(in srgb, var(--color-base-300) 40%, transparent); }

.bg-primary { background-color: var(--color-primary); }
.bg-primary\/10 { background-color: color-mix(in srgb, var(--color-primary) 10%, transparent); }
.bg-neutral { background-color: var(--color-neutral); }
.bg-error { background-color: var(--color-error); }
.bg-error\/15 { background-color: color-mix(in srgb, var(--color-error) 15%, transparent); }
.bg-error\/5 { background-color: color-mix(in srgb, var(--color-error) 5%, transparent); }
.bg-success\/30 { background-color: color-mix(in srgb, var(--color-success) 30%, transparent); }
.bg-success\/15 { background-color: color-mix(in srgb, var(--color-success) 15%, transparent); }
.bg-success\/10 { background-color: color-mix(in srgb, var(--color-success) 10%, transparent); }
.bg-warning\/40 { background-color: color-mix(in srgb, var(--color-warning) 40%, transparent); }
.bg-warning\/15 { background-color: color-mix(in srgb, var(--color-warning) 15%, transparent); }
.bg-white { background-color: #ffffff; }
.bg-white\/10 { background-color: rgba(255, 255, 255, 0.1); }
.bg-white\/5 { background-color: rgba(255, 255, 255, 0.05); }
.bg-black\/80 { background-color: rgba(0, 0, 0, 0.8); }
.bg-black\/40 { background-color: rgba(0, 0, 0, 0.4); }

.border-base-100 { border-color: var(--color-base-100); }
.border-base-100\/40 { border-color: color-mix(in srgb, var(--color-base-100) 40%, transparent); }
.border-base-200 { border-color: var(--color-base-200); }
.border-base-300 { border-color: var(--color-base-300); }
.border-primary { border-color: var(--color-primary); }
.border-primary\/20 { border-color: color-mix(in srgb, var(--color-primary) 20%, transparent); }
.border-error\/40 { border-color: color-mix(in srgb, var(--color-error) 40%, transparent); }
.border-error\/30 { border-color: color-mix(in srgb, var(--color-error) 30%, transparent); }
.border-success\/40 { border-color: color-mix(in srgb, var(--color-success) 40%, transparent); }
.border-white\/15 { border-color: rgba(255, 255, 255, 0.15); }
.border-white\/10 { border-color: rgba(255, 255, 255, 0.1); }
.border-transparent { border-color: transparent; }

.divide-base-200 > * + * { border-color: var(--color-base-200); }

/* ==========================================================================
   8. Border, radius, shadow and effect utilities
   ========================================================================== */

.border { border-width: 1px; }
.border-0 { border-width: 0; }
.border-t { border-top-width: 1px; }
.border-t-2 { border-top-width: 2px; }
.border-b { border-bottom-width: 1px; }
.border-l-2 { border-left-width: 2px; }
.border-r { border-right-width: 1px; }
.border-r-2 { border-right-width: 2px; }
.border-dashed { border-style: dashed; }
.last\:border-0:last-child { border-width: 0; }

.rounded { border-radius: 0.25rem; }
.rounded-sm { border-radius: 0.125rem; }
.rounded-lg { border-radius: 0.5rem; }
.rounded-xl { border-radius: 0.75rem; }
.rounded-2xl { border-radius: 1rem; }
.rounded-full { border-radius: 9999px; }
.rounded-box { border-radius: var(--radius-box); }

.shadow {
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.08);
}
.shadow-lg {
    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
.shadow-xl {
    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 20px 25px -5px rgba(0, 0, 0, 0.12);
}

/* `ring-inset` draws the ring inside the element's bounds, so a highlighted
   table cell doesn't overlap its neighbours. */
.ring-1 { box-shadow: 0 0 0 1px var(--ring-color, var(--color-primary)); }
.ring-2 { box-shadow: 0 0 0 2px var(--ring-color, var(--color-primary)); }
.ring-2.ring-inset { box-shadow: inset 0 0 0 2px var(--ring-color, var(--color-primary)); }
.ring-primary { --ring-color: var(--color-primary); }

.opacity-30 { opacity: 0.3; }
.opacity-40 { opacity: 0.4; }
.opacity-50 { opacity: 0.5; }
.opacity-60 { opacity: 0.6; }
.opacity-80 { opacity: 0.8; }
.opacity-100 { opacity: 1; }

.backdrop-blur { backdrop-filter: blur(8px); }
.backdrop-blur-sm { backdrop-filter: blur(4px); }
.backdrop-blur-md { backdrop-filter: blur(12px); }

.transition {
    transition-property: color, background-color, border-color, opacity, box-shadow, transform;
    transition-duration: 150ms;
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.transition-all {
    transition-property: all;
    transition-duration: 150ms;
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.transition-transform {
    transition-property: transform;
    transition-duration: 150ms;
    transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.duration-200 { transition-duration: 200ms; }

.-translate-x-full { transform: translateX(-100%); }
.rotate-180 { transform: rotate(180deg); }

/* ==========================================================================
   9. Responsive and state variants
   ========================================================================== */

.hover\:bg-base-200:hover { background-color: var(--color-base-200); }
.hover\:bg-primary\/10:hover { background-color: color-mix(in srgb, var(--color-primary) 10%, transparent); }
.hover\:border-primary:hover { border-color: var(--color-primary); }
.hover\:border-primary\/40:hover { border-color: color-mix(in srgb, var(--color-primary) 40%, transparent); }
.hover\:text-primary:hover { color: var(--color-primary); }
.hover\:text-base-content\/70:hover { color: color-mix(in srgb, var(--color-base-content) 70%, transparent); }
.hover\:underline:hover { text-decoration-line: underline; }
.hover\:opacity-90:hover { opacity: 0.9; }
.hover\:shadow-md:hover {
    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
}
.hover\:shadow-\[0_12px_40px_rgba\(244\,101\,29\,0\.2\)\]:hover {
    box-shadow: 0 12px 40px color-mix(in srgb, var(--color-primary) 20%, transparent);
}
.hover\:-translate-y-0\.5:hover { transform: translateY(-0.125rem); }
.hover\:-translate-y-1:hover { transform: translateY(-0.25rem); }

.focus\:border-primary:focus { border-color: var(--color-primary); }
.focus\:outline-none:focus { outline: 2px solid transparent; outline-offset: 2px; }
.focus-within\:border-primary:focus-within { border-color: var(--color-primary); }

.active\:scale-95:active { transform: scale(0.95); }

@media (min-width: 640px) {
    .sm\:block { display: block; }
    .sm\:flex-row { flex-direction: row; }
    .sm\:items-center { align-items: center; }
    .sm\:justify-between { justify-content: space-between; }
    .sm\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .sm\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .sm\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    .sm\:grid-cols-\[10rem_1fr\] { grid-template-columns: 10rem 1fr; }
    .sm\:grid-cols-\[12rem_1fr\] { grid-template-columns: 12rem 1fr; }
    .sm\:col-span-2 { grid-column: span 2 / span 2; }
    .sm\:w-56 { width: 14rem; }
    .sm\:max-w-3xl { max-width: 48rem; }
    .sm\:p-6 { padding: 1.5rem; }
    .sm\:p-10 { padding: 2.5rem; }
    .sm\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
    .sm\:px-10 { padding-left: 2.5rem; padding-right: 2.5rem; }
    .sm\:pt-8 { padding-top: 2rem; }
    .sm\:text-base { font-size: 1rem; line-height: 1.5rem; }
    .sm\:text-7xl { font-size: 4.5rem; line-height: 1.05; }
    .sm\:stats-horizontal { grid-auto-flow: column; }
}

@media (min-width: 768px) {
    .md\:col-span-2 { grid-column: span 2 / span 2; }
    .md\:col-span-3 { grid-column: span 3 / span 3; }
    .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .md\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .md\:grid-cols-\[9rem_1fr_9rem_1fr\] { grid-template-columns: 9rem 1fr 9rem 1fr; }
    .md\:grid-cols-\[1fr_14rem_auto\] { grid-template-columns: 1fr 14rem auto; }
}

@media (min-width: 1024px) {
    .lg\:hidden { display: none; }
    .lg\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .lg\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    .lg\:col-span-1 { grid-column: span 1 / span 1; }
    .lg\:col-span-2 { grid-column: span 2 / span 2; }
    .lg\:col-span-3 { grid-column: span 3 / span 3; }
    .lg\:sticky { position: sticky; }
    .lg\:top-16 { top: 4rem; }
    .lg\:top-20 { top: 5rem; }
    .lg\:z-auto { z-index: auto; }
    .lg\:translate-x-0 { transform: translateX(0); }
    .lg\:shadow-none { box-shadow: none; }
    .lg\:h-\[calc\(100vh-4rem\)\] { height: calc(100vh - 4rem); }
}

@media print {
    .print\:hidden { display: none !important; }
    /* The counterpart: content that exists only on paper. Paired with `hidden`
       in the markup, so the block is invisible on screen and shown when
       printing — the printed quote is a different document from the calculator
       that produced it, not a screenshot of it. */
    .print\:block { display: block !important; }

    /* Print on white with black text whatever theme the employee is using. A
       dark-theme quote would come out as a solid ink-soaked page, and the
       customer's copy must not depend on a preference of ours. */
    :root {
        --color-base-100: #ffffff;
        --color-base-200: #f5f5f5;
        --color-base-300: #e5e5e5;
        --color-base-content: #111111;
    }
    html, body {
        background: #ffffff !important;
        color: #111111 !important;
    }

    /* Let the document use the full sheet; the app shell's padding is for a
       viewport, not for A4. */
    .app-shell .px-4,
    .app-shell .sm\:px-6 { padding-left: 0 !important; padding-right: 0 !important; }

    @page { margin: 18mm 16mm; }
}

/* ==========================================================================
   13. Printed quote document (.quote-doc)

   The customer's copy of a price quote, rendered only when printing (see
   `.print:block` above). It deliberately does NOT reuse the card/table
   components: those are tuned for a screen at arm's length, and a document
   that will be read on paper, forwarded as a PDF and possibly filed against a
   purchase order needs heavier structure and quieter colour.
   ========================================================================== */

.quote-doc {
    color: #111111;
    font-size: 10.5pt;
    line-height: 1.45;
}

/* Masthead: who is quoting, what this is, and when — the three things a reader
   checks before anything else. */
.quote-doc-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 2rem;
    border-bottom: 2px solid #111111;
    padding-bottom: 0.75rem;
    margin-bottom: 1.25rem;
}
.quote-doc-brand {
    font-size: 17pt;
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.1;
}
.quote-doc-kicker {
    font-size: 8.5pt;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: #555555;
}
.quote-doc-meta {
    text-align: right;
    font-size: 9pt;
    color: #333333;
    white-space: nowrap;
}

.quote-doc-section {
    /* A section that lands half on one page and half on the next reads as two
       different quotes, so keep each one whole. */
    break-inside: avoid;
    margin-bottom: 1.1rem;
}
.quote-doc-section-title {
    font-size: 8.5pt;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: #555555;
    border-bottom: 1px solid #d4d4d4;
    padding-bottom: 0.2rem;
    margin-bottom: 0.5rem;
}

/* Two-column facts (Prepared for, employees, server, …). */
.quote-doc-facts {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.2rem 1.25rem;
    font-size: 9.5pt;
}
.quote-doc-facts dt { color: #555555; }
.quote-doc-facts dd { font-weight: 600; }

/* Line items. */
.quote-doc-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 9.5pt;
}
.quote-doc-table th {
    text-align: left;
    font-size: 8.5pt;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #555555;
    border-bottom: 1px solid #111111;
    padding: 0.3rem 0;
}
.quote-doc-table td {
    border-bottom: 1px solid #e5e5e5;
    padding: 0.38rem 0;
    vertical-align: top;
}
.quote-doc-table .quote-doc-amount {
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.quote-doc-table .quote-doc-detail {
    display: block;
    font-size: 8.5pt;
    color: #666666;
}

/* The three figures the reader is actually looking for. The per-employee cost
   is given the emphasis because it is the one that survives being repeated in
   a meeting. */
.quote-doc-totals {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.75rem;
    margin-top: 0.4rem;
    break-inside: avoid;
}
.quote-doc-total {
    border: 1px solid #d4d4d4;
    padding: 0.6rem 0.7rem;
}
.quote-doc-total-lead {
    border: 2px solid #111111;
}
.quote-doc-total-label {
    font-size: 8pt;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #555555;
}
.quote-doc-total-value {
    font-size: 15pt;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
}
.quote-doc-total-sub {
    font-size: 8.5pt;
    color: #555555;
    font-variant-numeric: tabular-nums;
}

.quote-doc-terms {
    margin-top: 1.25rem;
    padding-top: 0.6rem;
    border-top: 1px solid #d4d4d4;
    font-size: 8.5pt;
    color: #555555;
    break-inside: avoid;
}
.quote-doc-signature {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2.5rem;
    margin-top: 1.5rem;
    break-inside: avoid;
}
.quote-doc-signature div {
    border-top: 1px solid #111111;
    padding-top: 0.3rem;
    font-size: 8.5pt;
    color: #555555;
}

/* ==========================================================================
   11. SBM brand theme

   The kiosk / visitor screens run full-screen on wall-mounted displays and
   phones, and carry Sparebanken Møre's brand rather than the back-office
   chrome. Applied via data-theme="sbm" on the kiosk and public layouts — and on
   the sign-in layout, which shares the same face.

   It is a LIGHT theme, unlike the dark one it replaces. That is not a stylistic
   choice: sbm.no is white, its panels are foss (#f2f6fc), and the mark is blue
   on light. A visitor standing in a branch lobby should see the same surface on
   the check-in screen as on the poster behind it, so the kiosk follows the bank
   rather than inverting it. The background is the light wash in section 12.

   Consequence worth knowing: anything that assumed this theme was dark — a
   white logo variant, a heavy scrim, light-on-dark utility classes — had to
   flip with it. Those are the paired changes in section 12/12b below.
   ========================================================================== */

[data-theme='sbm'] {
    color-scheme: light;

    /* Foss over white, the bank's own panel/page relationship. base-300 is
       byge at 18% on foss — the same divider tone their site mixes. */
    --color-base-100: #ffffff;
    --color-base-200: #f2f6fc;   /* foss */
    --color-base-300: #d6def0;
    --color-base-content: #01052f;   /* midnatt */

    --color-primary: #0066ff;    /* hav — the brand blue */
    --color-primary-content: #ffffff;
    --color-secondary: #000e5a;  /* byge */
    --color-secondary-content: #ffffff;
    --color-accent: #dcf3fd;     /* sky — the pale highlight ground */
    --color-accent-content: #01052f;
    --color-neutral: #01052f;    /* midnatt */
    --color-neutral-content: #f2f6fc;

    --color-info: #0066ff;
    --color-info-content: #ffffff;
    --color-success: #10743f;
    --color-success-content: #ffffff;
    --color-warning: #9a5b00;
    --color-warning-content: #ffffff;
    /* Alarm red, deliberately outside the palette: an evacuation banner must
       not be mistakable for anything the bank uses decoratively. */
    --color-error: #c1121f;
    --color-error-content: #ffffff;

    /* The bank's radii, one step softer than the back-office defaults because
       these surfaces are large and seen from a distance. */
    --radius-selector: 6.25rem;
    --radius-field: 0.625rem;
    --radius-box: 1.25rem;
}

/* Give both evacuation languages equal prominence. Auto margins centre short
   notices without clipping their beginning when a small display must scroll. */
.kiosk-evacuation { margin-block: auto; }
.kiosk-evacuation-languages {
    display: grid;
    width: 100%;
    gap: 2rem;
}
.kiosk-evacuation-language {
    display: grid;
    align-content: start;
    gap: 1rem;
}
.kiosk-evacuation-heading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6em;
    font-size: clamp(2rem, 3.4vw, 3rem);
    line-height: 1.15;
    font-weight: 800;
    text-transform: uppercase;
    color: var(--color-error);
}
.kiosk-evacuation-icon { width: 1em; height: 1em; flex-shrink: 0; }
.kiosk-evacuation-message {
    font-size: clamp(1.25rem, 2.2vw, 1.5rem);
    line-height: 1.4;
    color: var(--color-base-content);
}
@media (min-width: 700px) {
    .kiosk-evacuation-languages { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Reserve equal space for host titles, including missing or very long titles.
   Keep department names in the filter buttons rather than repeating them here. */
.visitor-host-card {
    height: 5.5rem;
    min-height: 5.5rem;
}
.kiosk-touch .visitor-host-card {
    height: 7rem;
    min-height: 7rem;
}
.visitor-host-title {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    height: 2.5rem;
    line-height: 1.25rem;
    white-space: normal;
    overflow: hidden;
    overflow-wrap: anywhere;
}

/* Physical kiosks run on modest tablets. Keep the brand colors and immediate
   pressed-state feedback without filtering large layers or animating shadows.
   Scoped to the touch layout; mobile check-in and the app keep their styling. */
.kiosk-touch :is(.backdrop-blur, .backdrop-blur-sm, .backdrop-blur-md) {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
}
.kiosk-touch :is(a, button) {
    touch-action: manipulation;
    transition: none;
}
.kiosk-touch :is(a, button):is(:hover, :active) {
    transform: none;
    box-shadow: none;
}

/* Display the 141×40 SBM logo at twice its intrinsic size. Use a definite width:
   a percentage inside the inline home link otherwise keeps its intrinsic width.
   The link and image remain bounded by the brand band's available width. */
.kiosk-brand-logo {
    width: 17.625rem;
    max-width: 100%;
    height: auto;
    max-height: 11rem;
}

@media (min-width: 640px) {
    .kiosk-brand-logo {
        max-height: 14rem;
    }
}

/* The kiosk display face. SBM Sans already carries the whole interface (it is
   first in --font-sans), so unlike the dark theme it replaces — which had to swap in
   Montserrat here — this rule only tightens the tracking at display sizes,
   where the brand face sets a little loose. */
[data-theme='sbm'] :is(h1, h2, .kiosk-display) {
    letter-spacing: -0.02em;
}

/* Keep the scannable code square when a meeting tile has limited width. */
.kiosk-meeting-qr { width: 10rem; max-width: 100%; height: auto; aspect-ratio: 1; }

/* ==========================================================================
   12. Brand gradient background (<x-gradient-bg>)

   The wash behind the sign-in card, the touch kiosk and the visitor check-in,
   and the base layer under the brand video when there is one: large, heavily
   blurred colour pools drifting very slowly over a pale base, so nothing
   competes with the content in front of them. .auth-bg alone is what every
   screen falls back to when the video cannot play — or, as ships today, when
   there is no clip at all.

   Rebuilt light for Sparebanken Møre. The dark version it replaces was near-black
   and carried its own contrast: white text sat straight on it. This one is
   white → foss → sky, so everything above it is dark-on-light instead, and the
   pools are correspondingly gentle — at these blur radii a saturated #0066ff
   pool would tint the whole frame and fight the mark.

   Every pool moves on its own long duration (48–72s) and a negative delay, so
   they start out of phase and the composite never visibly loops.
   ========================================================================== */

.auth-bg {
    /* Base wash: foss and sky pooling into white, weighted low and left so the
       frame has a direction rather than reading as a flat vignette. */
    background:
        radial-gradient(ellipse at 50% 115%, #dcf3fd 0%, transparent 62%),
        radial-gradient(ellipse at 12% -10%, #e8effb 0%, transparent 58%),
        linear-gradient(160deg, #ffffff 0%, #f2f6fc 52%, #e6eef9 100%);
}

/* A single colour pool. Size, position and colour are set per-instance below;
   the shared rule owns the shape, the blur and the drift timing function. */
.auth-glow {
    position: absolute;
    border-radius: 50%;
    filter: blur(90px);
    will-change: transform, opacity;
    animation: auth-glow-drift ease-in-out infinite alternate;
}

/* Brand blue, top-left — the dominant pool. Kept to 14%: this is the only
   saturated colour in the wash, and anything stronger reads as a blue page
   rather than a white one with the brand in it. */
.auth-glow-1 {
    width: 70vw; height: 70vw; top: -25%; left: -18%;
    background: radial-gradient(circle, rgba(0, 102, 255, 0.14), transparent 70%);
    animation-duration: 58s;
    animation-delay: -6s;
}

/* Sky, bottom-right, counter-drifting against the first. Pale enough to carry a
   larger opacity without darkening the frame. */
.auth-glow-2 {
    width: 65vw; height: 65vw; bottom: -30%; right: -16%;
    background: radial-gradient(circle, rgba(220, 243, 253, 0.85), transparent 70%);
    animation-name: auth-glow-drift-reverse;
    animation-duration: 72s;
    animation-delay: -21s;
}

/* Vårgrønn, kept very faint — the bank's support colour, present so the frame
   is not purely blue, never strong enough to read as a second brand colour. */
.auth-glow-3 {
    width: 50vw; height: 50vw; top: 20%; right: 5%;
    background: radial-gradient(circle, rgba(162, 226, 182, 0.22), transparent 70%);
    animation-duration: 64s;
    animation-delay: -33s;
}

/* Deep marine low pool, at low opacity — it gives the bottom of the frame some
   weight so the wash does not float, without tipping it toward grey. */
.auth-glow-4 {
    width: 55vw; height: 55vw; bottom: -10%; left: 8%;
    background: radial-gradient(circle, rgba(0, 20, 158, 0.10), transparent 70%);
    animation-name: auth-glow-drift-reverse;
    animation-duration: 48s;
    animation-delay: -12s;
}

/* Edge shading, so a card or a block of copy keeps its contrast against the
   frame. Inverted from the dark version it replaces: darkening the edges of a light
   wash would ring the screen in grey, so this deepens toward foss/byge instead
   — the same move, one order of magnitude lighter. */
.auth-bg-vignette {
    background: radial-gradient(ellipse at center, transparent 42%, rgba(0, 14, 90, 0.10) 100%);
}

/* The drift itself: a long, gentle wander with a slight scale breath. Kept
   small in amplitude — at these blur radii a few percent is plenty of motion. */
@keyframes auth-glow-drift {
    0%   { transform: translate(-6%, -4%) scale(1);    opacity: 0.6; }
    50%  { transform: translate(4%, 6%) scale(1.12);   opacity: 0.9; }
    100% { transform: translate(7%, -5%) scale(1.04);  opacity: 0.7; }
}
@keyframes auth-glow-drift-reverse {
    0%   { transform: translate(6%, 5%) scale(1.08);   opacity: 0.75; }
    50%  { transform: translate(-5%, -6%) scale(1);    opacity: 0.55; }
    100% { transform: translate(-7%, 4%) scale(1.14);  opacity: 0.85; }
}

/* Respect reduced-motion: hold the pools still, keep the colour. */
@media (prefers-reduced-motion: reduce) {
    .auth-glow { animation: none !important; }
}

/* --------------------------------------------------------------------------
   12b. Brand video background (<x-video-bg>)

   The brand banner clip, filling the frame behind the sign-in card, the touch
   kiosk and the visitor self check-in. It sits on top of the .auth-bg gradient
   wash rather than replacing it, so the page is already on-brand before the
   first frame decodes — and stays correct if the video never plays at all.
   -------------------------------------------------------------------------- */

.auth-video {
    position: absolute;
    top: 50%;
    left: 50%;
    /* Cover the viewport at any aspect ratio without distorting the clip:
       min-width/height plus a centring translate is the one approach that works
       for a <video> in every browser, since object-fit alone leaves letterboxing
       on some mobile engines. */
    min-width: 100%;
    min-height: 100%;
    width: auto;
    height: auto;
    transform: translate(-50%, -50%);
    object-fit: cover;
}

/* Flat veil over the clip. The kiosk and the sign-in card are dark-on-light
   under the SBM theme, so this LIGHTENS rather than darkens — the opposite of
   the dark version it replaces, which held a white wordmark off a bright frame. It
   sized for the darkest frame of a clip, not the average one: a veil that only
   works over bright footage would let midnatt body copy disappear the moment
   the video cuts to a night shot.

   Tinted foss rather than plain white, so the veil belongs to the palette
   instead of reading as haze over the picture. */
.auth-video-scrim {
    background: rgba(242, 246, 252, 0.72);
}

/* Heavier veil for the kiosk and visitor screens (<x-video-bg dim>). Their copy
   sits straight on the background — the mark, the instructions — with no card
   underneath to hold it off the frame, so it needs the extra margin. */
.auth-video-scrim-dim {
    background: rgba(242, 246, 252, 0.86);
}

/* Reduced motion: drop the video entirely and let the gradient wash beneath it
   carry the page. A full-frame moving background is precisely what this setting
   is asking us not to show. */
@media (prefers-reduced-motion: reduce) {
    .auth-video { display: none; }
}

/* ==========================================================================
   Bus departures — the live board in the kiosk's Bus panel, and the stop
   picker on the screen form that chooses where it reads from.
   ========================================================================== */

/* The board: one row per departure — line badge, destination, time. Left
   aligned inside the otherwise centred modal, because a timetable is read down
   its columns. */
.kiosk-departures {
    list-style: none;
    margin-left: 0;
    margin-right: 0;
    padding: 0;
    text-align: left;
}
.kiosk-departure {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--color-base-300);
}
.kiosk-departure:last-child { border-bottom: 0; }

/* Line number in a hav-blue badge, like the line plates on the buses. */
.kiosk-departure-line {
    flex-shrink: 0;
    min-width: 3rem;
    padding: 0.25rem 0.625rem;
    border-radius: 0.5rem;
    background: var(--color-primary);
    color: var(--color-primary-content);
    font-size: 1.125rem;
    font-weight: 600;
    text-align: center;
    font-variant-numeric: tabular-nums;
}
.kiosk-departure-destination {
    flex: 1 1 auto;
    min-width: 0; /* lets .truncate shorten a long destination instead of widening the row */
}
.kiosk-departure-time {
    flex-shrink: 0;
    font-size: 1.25rem;
    font-weight: 600;
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* Stop picker results on the screen form: a bordered list of full-width rows. */
.bus-stop-results {
    list-style: none;
    margin-left: 0;
    margin-right: 0;
    padding: 0;
    max-height: 16rem;
    overflow-y: auto;
    border: 1px solid var(--color-base-300);
    border-radius: var(--radius-field);
}
.bus-stop-result {
    display: flex;
    width: 100%;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.125rem;
    padding: 0.5rem 0.75rem;
    border: 0;
    border-bottom: 1px solid var(--color-base-300);
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.bus-stop-results li:last-child .bus-stop-result { border-bottom: 0; }
.bus-stop-result:hover,
.bus-stop-result:focus-visible { background: var(--color-base-200); }
/* Shared validation feedback across native fields and custom pickers. */
.form-error-summary { padding: 1rem; border: 1px solid var(--color-error); border-radius: .5rem; color: var(--color-base-content); background: var(--color-base-100); }
.form-error-summary ul { margin: .5rem 0 0; padding-left: 1.5rem; list-style: disc; }
.form-error-summary a { color: var(--color-error); text-decoration: underline; }
.form-field-error { margin-top: .25rem; font-size: .875rem; color: var(--color-error); }
[aria-invalid="true"] { border-color: var(--color-error); outline: 1px solid var(--color-error); outline-offset: 1px; }
[data-field-required]::after { content: ' *'; color: var(--color-error); }
.form-required-note { font-size: .875rem; margin-bottom: 1rem; }
.form-error-summary[hidden], .form-required-note[hidden] { display: none; }

/* One static image layer; only the image is blurred, never kiosk text or controls. */
.kiosk-background { overflow: hidden; pointer-events: none; }
.kiosk-background-image {
    position: absolute;
    inset: calc(-3 * var(--kiosk-background-blur, 0px));
    width: calc(100% + 6 * var(--kiosk-background-blur, 0px));
    height: calc(100% + 6 * var(--kiosk-background-blur, 0px));
    max-width: none;
    object-fit: cover;
    filter: blur(var(--kiosk-background-blur, 0px));
}
.kiosk-background-wash { position: absolute; inset: 0; background: rgba(242, 246, 252, .72); }
/* Screen create/edit forms use the same section rhythm at every viewport size. */
.screen-form { display: flex; flex-direction: column; gap: 1.5rem; }
.screen-form-section { min-width: 0; padding: 1.25rem; border: 1px solid var(--color-base-300); border-radius: .75rem; }
.screen-form-section > legend { padding: 0 .375rem; font-size: 1rem; font-weight: 600; }
.screen-form-description { margin: 0 0 1rem; font-size: .8125rem; color: var(--color-base-content); opacity: .7; }
.screen-form-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid var(--color-base-300); }
@media (max-width: 640px) {
    .screen-form-section { padding: 1rem; }
    .screen-form-actions > .btn { flex: 1; }
}
.screen-background-settings { min-width: 0; }
.screen-background-range { accent-color: var(--color-primary); }
.screen-background-preview { position: relative; isolation: isolate; overflow: hidden; aspect-ratio: 4 / 3; border: 1px solid var(--color-base-300); border-radius: .75rem; }
.screen-background-preview-content { position: relative; display: flex; flex-direction: column; align-items: center; gap: 2rem; padding: 2rem 1rem; color: var(--color-base-content); }
.screen-background-preview-logo { width: 60%; max-height: 4.5rem; object-fit: contain; }
.screen-background-preview-card { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1rem; width: 60%; min-height: 8rem; padding: 1rem; text-align: center; border: 1px solid var(--color-base-300); border-radius: 1rem; background: color-mix(in srgb, var(--color-base-100) 80%, transparent); font-weight: 600; }
@media (max-width: 480px) {
    .screen-background-preview-content { gap: 1rem; padding: 1rem; }
    .screen-background-preview-card { width: 80%; min-height: 6rem; gap: .5rem; padding: .5rem; }
}
