/* ==========================================================================
   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 / vestavik kiosk)
     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. Vestavik kiosk theme and the data-flow animation

   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. */
:root {
    --color-base-100: #ffffff;
    --color-base-200: #f3f4f6;
    --color-base-300: #e4e7ec;
    --color-base-content: #1b1f27;

    /* Vestavik IT brand teal, taken from vestavik.no's brand scale:
       brand-600 #0b7a93 is the button/link colour, brand-800 #0a4d5f the deep
       wordmark tone, brand-400 #3aacc9 the light "IT AS" blue. */
    --color-primary: #0b7a93;
    --color-primary-content: #ffffff;
    --color-secondary: #0a4d5f;
    --color-secondary-content: #ffffff;
    --color-accent: #3aacc9;
    --color-accent-content: #04222c;
    --color-neutral: #0a2d3e;    /* the logo's ink */
    --color-neutral-content: #f2f8fa;

    --color-info: #0284c7;
    --color-info-content: #ffffff;
    --color-success: #15803d;
    --color-success-content: #ffffff;
    --color-warning: #b45309;
    --color-warning-content: #ffffff;
    /* A plain red: with a teal brand, nothing else on screen competes with it,
       so an error can use the colour everyone already reads as "error". */
    --color-error: #dc2626;
    --color-error-content: #ffffff;

    --radius-selector: 1rem;
    --radius-field: 0.5rem;
    --radius-box: 0.75rem;

    /* 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;

    --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, -apple-system,
        'Segoe UI', Roboto, 'Helvetica Neue', 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. */
[data-theme='dark'] {
    --color-base-100: #1d222b;
    --color-base-200: #171b22;
    --color-base-300: #11141a;
    --color-base-content: #e6e9ee;

    /* Lifted a step up the brand scale (brand-500/400) so the teal keeps its
       contrast against the dark surfaces; the deep tones go muddy there. */
    --color-primary: #1f96b0;
    --color-primary-content: #ffffff;
    --color-secondary: #0b7a93;
    --color-secondary-content: #ffffff;
    --color-accent: #7fc4dc;
    --color-accent-content: #06232a;
    --color-neutral: #11141a;
    --color-neutral-content: #e6e9ee;

    --color-info: #38bdf8;
    --color-info-content: #06212e;
    --color-success: #4ade80;
    --color-success-content: #05210f;
    --color-warning: #fbbf24;
    --color-warning-content: #241a02;
    --color-error: #f87171;
    --color-error-content: #2a0708;
}

/* 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. */
.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); }

.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; }

/* ── 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; }

/* ── 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; }
.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)); }
.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; }

.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\.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-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; }

/* Montserrat (the kiosk display face) is self-hosted at 200/300/400/600 only —
   see public/css/fonts.css. Prefer those weights on kiosk headings so the
   browser renders a real cut instead of synthesising a faux-bold. */
.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); }
/* Used by the kiosk wordmark, which sets "IT" in the accent blue the way the
   Vestavik logo does. */
.text-accent { color: var(--color-accent); }
.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-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-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; }
}

/* ==========================================================================
   11. Vestavik kiosk theme and the data-flow animation

   The kiosk / visitor screens run full-screen on wall-mounted displays and
   phones, and carry Vestavik IT's brand rather than the back-office chrome.
   Applied via data-theme="vestavik" on the kiosk and public layouts.

   Vestavik IT sells digitalisation ("enkel digitalisering"), so the ambient
   background is a data-flow scene rather than a physical one: packets stream
   down into the network, nodes light up as they land, and rings echo the
   circular arrow mark in the logo. The stream is the focal point — the rings
   and nodes exist to receive it, not merely to drift past.
   ========================================================================== */

[data-theme='vestavik'] {
    color-scheme: dark;

    /* Deep teal-navy, mixed down from the logo's ink (#0a2d3e), so the brand
       teal stays the brightest thing on screen. */
    --color-base-100: #0d3346;
    --color-base-200: #082433;
    --color-base-300: #041720;
    --color-base-content: #e9f5f9;

    --color-primary: #1f96b0;   /* brand-500 — reads bright against the navy */
    --color-primary-content: #ffffff;
    --color-secondary: #0b7a93; /* brand-600 — the website's button teal */
    --color-secondary-content: #ffffff;
    --color-accent: #7fc4dc;    /* brand-300 — the light "IT AS" blue */
    --color-accent-content: #04222c;
    --color-neutral: #061f2b;
    --color-neutral-content: #e9f5f9;

    --color-info: #7fd3e6;
    --color-info-content: #05121a;
    --color-success: #4ade80;
    --color-success-content: #05210f;
    --color-warning: #fbbf24;
    --color-warning-content: #241a02;
    /* Red is unused by the brand here, so an error can simply be red. */
    --color-error: #ff5c5c;
    --color-error-content: #2a0708;

    --radius-selector: 1rem;
    --radius-field: 0.75rem;
    --radius-box: 1rem;
}

/* The kiosk display face. Loaded as a self-hosted webfont (see fonts.css); the
   markup's own weight/size utilities still win because only the family is set. */
[data-theme='vestavik'] :is(h1, h2, .kiosk-display) {
    font-family: 'Montserrat', var(--font-sans);
    letter-spacing: -0.01em;
}

/* The brand mark is a full-colour PNG whose wordmark is near-black teal, so it
   disappears on the dark kiosk/guest themes. brightness(0) crushes every
   opaque pixel to black and invert(1) lifts it to white, leaving the alpha
   channel untouched — a white mark from the single asset, no second file. */
.logo-white { filter: brightness(0) invert(1); }

/* ── Ambient depth ───────────────────────────────────────────────────────── */

/* Slow-drifting colour pools that keep the flat navy from reading as a dead
   background. Two tones: the brand teal and the lighter "IT AS" blue. */
.flow-glow {
    position: absolute;
    border-radius: 50%;
    filter: blur(80px);
    animation: flow-glow-move 26s ease-in-out infinite;
}
.flow-glow-1 {
    width: 60vw; height: 60vw; top: -22%; left: -12%;
    background: radial-gradient(circle, rgba(11, 122, 147, 0.45), transparent 70%);
}
.flow-glow-2 {
    width: 55vw; height: 55vw; bottom: -28%; right: -14%;
    background: radial-gradient(circle, rgba(127, 196, 220, 0.14), transparent 70%);
    animation-duration: 32s;
    animation-delay: -9s;
}

/* Faint motes lifting off the network — the ambient counterpart to the packets
   coming down, so the scene reads as a two-way exchange rather than a drain. */
.flow-mote {
    position: absolute;
    bottom: -5vh;
    border-radius: 50%;
    background: radial-gradient(circle at 30% 30%, rgba(127, 196, 220, 0.55), rgba(127, 196, 220, 0.04));
    animation: flow-lift linear infinite;
}

/* ── Data packets ────────────────────────────────────────────────────────── */

/* A packet travels from above the viewport down into the network. The wrapper
   owns the fall; the inner square owns the lane-change drift and rotation, so
   the two never fight over the transform property. */
.flow-packet-track {
    position: absolute;
    top: 0;
    animation: flow-send linear infinite;
}
.flow-packet {
    display: block;
    border-radius: 2px;
    background: linear-gradient(140deg, #a5dcec, var(--color-primary) 55%, #0a4d5f);
    box-shadow: 0 0 10px rgba(31, 150, 176, 0.5);
    animation: flow-drift ease-in-out infinite alternate;
}

/* The moment a packet lands on a node: a short teal flash where it is absorbed. */
.flow-spark {
    position: absolute;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(127, 196, 220, 0.55), transparent 65%);
    opacity: 0;
    animation: flow-spark-flash ease-out infinite;
}

/* ── Orbit rings ─────────────────────────────────────────────────────────── */

/* The large drifting shapes, echoing the circular arrow mark in the logo. Each
   is a plain bordered circle with an inner arc, so no image asset is needed and
   any brand colour themes it. The nested tracks compose three motions without
   conflict: travel (across the screen), bob (vertical sway) and spin. */
.flow-ring {
    display: block;
    aspect-ratio: 1;
    border: 2px solid currentColor;
    border-radius: 50%;
    position: relative;
}
/* The inner arc: a second circle with three sides transparent, which draws a
   quarter turn inside the ring the way the logo's arrows sweep through it. */
.flow-ring::after {
    content: '';
    position: absolute;
    inset: 18%;
    border: 2px solid transparent;
    border-top-color: currentColor;
    border-right-color: currentColor;
    border-radius: 50%;
}
.flow-ring-travel {
    position: absolute;
    left: 0;
    animation: flow-travel linear infinite;
}
.flow-ring-travel-reverse { animation-name: flow-travel-reverse; }
.flow-ring-bob  { animation: flow-bob ease-in-out infinite alternate; }
.flow-ring-spin { animation: flow-spin linear infinite; }

.flow-ring-brand { color: var(--color-primary); opacity: 0.5; filter: drop-shadow(0 0 26px rgba(31, 150, 176, 0.35)); }
.flow-ring-deep  { color: var(--color-secondary); opacity: 0.38; }
.flow-ring-ghost { color: #ffffff; opacity: 0.06; }

/* ── Network floor ───────────────────────────────────────────────────────── */

/* The receiving end of the stream: a band of links and nodes along the bottom,
   replacing what would otherwise be an empty edge. */
.flow-link { stroke: rgba(31, 150, 176, 0.5); stroke-width: 1.5; fill: none; }
.flow-link-back { stroke: rgba(127, 196, 220, 0.22); animation: flow-link-sway 18s ease-in-out infinite; }
.flow-link-front { animation: flow-link-sway 12s ease-in-out infinite reverse; }
.flow-node {
    position: absolute;
    border-radius: 50%;
    background: var(--color-primary);
    box-shadow: 0 0 12px rgba(31, 150, 176, 0.7);
    animation: flow-node-pulse ease-in-out infinite;
}

/* ── Keyframes ───────────────────────────────────────────────────────────── */

@keyframes flow-lift {
    0%   { transform: translate(0, 0); opacity: 0; }
    10%  { opacity: 0.5; }
    90%  { opacity: 0.3; }
    100% { transform: translate(24px, -105vh); opacity: 0; }
}
@keyframes flow-glow-move {
    0%, 100% { transform: translate(-5%, -5%) scale(1); opacity: 0.5; }
    50%      { transform: translate(5%, 5%) scale(1.15); opacity: 0.85; }
}

/* Packet falls the full height and fades as it is absorbed near the bottom. */
@keyframes flow-send {
    0%   { transform: translateY(-12vh); opacity: 0; }
    8%   { opacity: 1; }
    82%  { opacity: 1; }
    100% { transform: translateY(104vh); opacity: 0; }
}
@keyframes flow-drift {
    from { transform: translateX(-14px) rotate(-25deg); }
    to   { transform: translateX(14px) rotate(25deg); }
}
@keyframes flow-spark-flash {
    0%, 70%  { opacity: 0; transform: scale(0.4); }
    78%      { opacity: 1; transform: scale(1); }
    100%     { opacity: 0; transform: scale(1.6); }
}

/* Ring crosses the screen, slowing mid-way where the packet stream is densest —
   the dwell in the middle is what reads as "receiving" rather than "passing". */
@keyframes flow-travel {
    0%       { transform: translateX(-24vw); }
    38%      { transform: translateX(38vw); }
    52%      { transform: translateX(46vw); }
    100%     { transform: translateX(124vw); }
}
@keyframes flow-travel-reverse {
    0%       { transform: translateX(124vw); }
    38%      { transform: translateX(56vw); }
    52%      { transform: translateX(48vw); }
    100%     { transform: translateX(-24vw); }
}
@keyframes flow-bob {
    from { transform: translateY(0); }
    to   { transform: translateY(-34px); }
}
@keyframes flow-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}
/* Nodes breathe out of phase so the floor never blinks in unison. */
@keyframes flow-node-pulse {
    0%, 100% { opacity: 0.35; transform: scale(1); }
    50%      { opacity: 1;    transform: scale(1.5); }
}
@keyframes flow-link-sway {
    0%, 100% { transform: translateX(0); }
    50%      { transform: translateX(-3%); }
}

/* Respect reduced-motion: freeze the ambient background entirely. */
@media (prefers-reduced-motion: reduce) {
    .flow-glow, .flow-mote, .flow-packet-track, .flow-packet, .flow-spark,
    .flow-ring-travel, .flow-ring-bob, .flow-ring-spin, .flow-node,
    .flow-link-back, .flow-link-front {
        animation: none !important;
    }
}
