/*
 * styles.css
 * ordessa-dashboard-web
 *
 * Application stylesheet, linked from index.html and installed again by
 * SwiftWebUIRuntime at mount.
 *
 * THIS FILE IS NOT A PLACE TO STYLE THINGS.
 *
 * Ordessa Dashboard for the web is a SwiftWebUI application, built the way
 * ordessa-web and ordessa-manager-web are: how an element looks is stated on the
 * view as a modifier. Only the three kinds of thing an inline style cannot
 * express belong here:
 *
 *   1. selectors         — :hover, :focus-visible, :disabled, +, ::backdrop
 *   2. at-rules          — @media, @keyframes, @starting-style
 *   3. custom properties — the --dashboard-* palette
 *
 * See CLAUDE.md, "The browser is built in Swift, not in CSS".
 *
 * One thing is load bearing here that is only decoration in the manager: the
 * board's shape. The native application reads the window with `GeometryReader`
 * and chooses between four lanes, a filtered grid and one stacked column. A
 * browser already answers that question without being asked — `flex-wrap`,
 * `minmax()` and a 900px `@media` block do it at layout time, with no size to
 * measure and no rebuild — so the reflow rules live in this file, and they are
 * at-rules, which is exactly what belongs in it.
 */

:root {
    color-scheme: light dark;

    /* A board propped up beside a fryer. The surface behind the tickets is
       recessed and a notch darker than the manager's window, because here the
       raised thing is the ticket: the native application inverts the manager's
       card-on-window for the same reason. */
    --dashboard-background: #ececed;

    /* The bar across the top, `.background(.bar)`. */
    --dashboard-bar: #f7f7f9;

    /* A ticket, and a sheet. */
    --dashboard-surface: #ffffff;

    /* A control's resting fill. */
    --dashboard-surface-elevated: #f1f1f3;

    /* The lane box the tickets sit in — `Color.primary.opacity(0.04)`. */
    --dashboard-lane: rgba(0, 0, 0, 0.045);

    /* The card a line of an order being assembled is drawn on. */
    --dashboard-line: rgba(0, 0, 0, 0.045);

    --dashboard-border: #d7d7dc;
    --dashboard-separator: #e2e2e7;

    --dashboard-text: #1d1d1f;
    --dashboard-text-secondary: #6e6e73;
    --dashboard-text-tertiary: #a1a1a6;

    /* The Ordessa orange, taken from the application icon's own gradient —
       srgb:0.94902,0.43922,0.00000. Chrome only: what a lane's colour means is
       the status palette below. */
    --dashboard-accent: #f27000;
    --dashboard-accent-text: #ffffff;
    --dashboard-accent-soft: rgba(242, 112, 0, 0.14);

    --dashboard-destructive: #d70015;
    --dashboard-destructive-soft: rgba(215, 0, 21, 0.1);
    --dashboard-success: #1f8a3b;
    --dashboard-success-soft: rgba(52, 199, 89, 0.16);

    /* The colour a status wears, here and in Ordessa Manager and the native
       dashboard alike. A shop owner with two of the three open should not have
       to learn two meanings for green. */
    --dashboard-placed: #f27000;
    --dashboard-placed-soft: rgba(242, 112, 0, 0.16);
    --dashboard-accepted: #0a64d8;
    --dashboard-accepted-soft: rgba(10, 100, 216, 0.14);
    --dashboard-preparing: #8544c9;
    --dashboard-preparing-soft: rgba(133, 68, 201, 0.14);
    --dashboard-ready: #1f8a3b;
    --dashboard-ready-soft: rgba(31, 138, 59, 0.14);
    --dashboard-collected: #6e6e73;
    --dashboard-collected-soft: rgba(110, 110, 115, 0.14);
    --dashboard-cancelled: #d70015;
    --dashboard-cancelled-soft: rgba(215, 0, 21, 0.12);

    /* A ticket the counter is behind on. Deliberately a wash over the whole
       ticket rather than a border: it is the one thing on this screen worth
       shouting, and it has to carry across a room. */
    --dashboard-late-surface: rgba(215, 0, 21, 0.16);
    --dashboard-late-border: rgba(215, 0, 21, 0.45);

    --dashboard-radius-ticket: 18px;
    --dashboard-radius-lane: 22px;
    --dashboard-radius-control: 10px;
    --dashboard-radius-capsule: 999px;

    --dashboard-shadow-ticket: 0 2px 10px rgba(0, 0, 0, 0.08);
    --dashboard-shadow-sheet: 0 30px 80px rgba(0, 0, 0, 0.3);

    /* The scrim behind a sheet, read by swift-web-ui's runtime rule for
       `::backdrop`. */
    --swiftwebui-dialog-backdrop: rgba(0, 0, 0, 0.35);
}

@media (prefers-color-scheme: dark) {
    :root {
        --dashboard-background: #111113;
        --dashboard-bar: #1c1c1f;
        --dashboard-surface: #242427;
        --dashboard-surface-elevated: #2d2d31;
        --dashboard-lane: rgba(255, 255, 255, 0.05);
        --dashboard-line: rgba(255, 255, 255, 0.06);

        --dashboard-border: #3a3a3f;
        --dashboard-separator: #303035;

        --dashboard-text: #f5f5f7;
        --dashboard-text-secondary: #98989d;
        --dashboard-text-tertiary: #636366;

        --dashboard-accent: #ff8a1f;
        --dashboard-accent-text: #1a0d00;
        --dashboard-accent-soft: rgba(255, 138, 31, 0.2);

        --dashboard-destructive: #ff453a;
        --dashboard-destructive-soft: rgba(255, 69, 58, 0.18);
        --dashboard-success: #32d74b;
        --dashboard-success-soft: rgba(50, 215, 75, 0.18);

        --dashboard-placed: #ff8a1f;
        --dashboard-placed-soft: rgba(255, 138, 31, 0.2);
        --dashboard-accepted: #409cff;
        --dashboard-accepted-soft: rgba(64, 156, 255, 0.2);
        --dashboard-preparing: #bf5af2;
        --dashboard-preparing-soft: rgba(191, 90, 242, 0.2);
        --dashboard-ready: #32d74b;
        --dashboard-ready-soft: rgba(50, 215, 75, 0.2);
        --dashboard-collected: #98989d;
        --dashboard-collected-soft: rgba(152, 152, 157, 0.2);
        --dashboard-cancelled: #ff453a;
        --dashboard-cancelled-soft: rgba(255, 69, 58, 0.2);

        --dashboard-late-surface: rgba(255, 69, 58, 0.22);
        --dashboard-late-border: rgba(255, 69, 58, 0.55);

        --dashboard-shadow-ticket: 0 2px 10px rgba(0, 0, 0, 0.4);
        --swiftwebui-dialog-backdrop: rgba(0, 0, 0, 0.6);
    }
}

/* Element resets. UA defaults rather than styling: a form control does not
   inherit the page's face on its own, and every box is measured the same way. */
* {
    box-sizing: border-box;
}

body {
    margin: 0;
    min-height: 100vh;
    background: var(--dashboard-background);
    color: var(--dashboard-text);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
}

button,
input,
select,
textarea {
    font: inherit;
}

/* Rounded is deliberate throughout, for the reason the native `Dashboard` style
   gives: it is the friendliest face the system has and it reads at distance,
   which is why road signs use it. `-apple-system-rounded` is the real thing
   where it exists; everywhere else the stack above stands. A declaration on a
   class rather than a modifier because the fallback chain is the point, and
   `Font.system(design:)` in swift-web-ui names one family. */
.dashboard-rounded {
    font-family: ui-rounded, "SF Pro Rounded", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/* Digits that line up down a column of tickets — the native `.monospacedDigit()`.
   Deliberately not a monospaced *face*: what has to line up is the figures, and
   the letters beside them should stay in the page's own face. No modifier names
   `font-variant-numeric`, so it rides in on a class the way the face above
   does. */
.dashboard-digits {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/* The board ------------------------------------------------------------- */

/*
 * The lanes, side by side, with the board scrolling sideways when they no
 * longer fit. This is the native `lanesSideBySide(in:)`, and the two rules
 * under it are `Dashboard.laneMinimumWidth` — 260 points, measured from an
 * 11-inch iPad in landscape, where four lanes plus the board's padding and
 * gutters have to fit inside 1180 without scrolling.
 */
.dashboard-lanes {
    display: flex;
    flex-direction: row;
    align-items: stretch;
    gap: 16px;
    padding: 20px;
    overflow-x: auto;
    overflow-y: hidden;
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
}

.dashboard-lane {
    flex: 1 1 0;
    min-width: 260px;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/*
 * A lane holds as many columns of tickets as its width allows — the native
 * `Dashboard.columns(across:ideal:spacing:)`, done by the browser.
 *
 * That is the whole of the reflow rule, and it is worth saying again here
 * because this is where it is actually enforced: **a lane that loses its
 * neighbours gains tickets, not ticket width.** `auto-fill` with a `minmax`
 * floor is exactly that — two lanes on an iPad are 560px each, which is two
 * tickets across at the width a ticket is meant to be read at rather than one
 * at twice it. A 560px lane holding one 540px ticket would put half a metre
 * between an order's number and its clock, which is the pair this screen exists
 * to be read as.
 */
.dashboard-tickets {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
    align-items: start;
    gap: 12px;
    width: 100%;
}

/* A board filtered to one state is a screen hung above one department, read
   from further back than a four-lane board is, and it has the room — so the
   extra space buys bigger tickets first and a further column second. */
.dashboard-tickets-focused {
    grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr));
    gap: 16px;
}

/* Tickets are the only thing on the board that scrolls, and each lane scrolls
   on its own — which is what keeps the lane's header and its count on screen
   while somebody works down a long queue. */
.dashboard-lane-scroll {
    overflow-y: auto;
    overflow-x: hidden;
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    scrollbar-width: thin;
}

/* Controls ---------------------------------------------------------------- */

.dashboard-button:hover:not(:disabled) {
    filter: brightness(0.96);
}

.dashboard-icon-button:hover:not(:disabled) {
    background: var(--dashboard-surface-elevated) !important;
}

.dashboard-button:disabled,
.dashboard-icon-button:disabled,
.dashboard-select:disabled,
.dashboard-input:disabled {
    opacity: 0.5;
    cursor: not-allowed !important;
}

.dashboard-input:focus-visible,
.dashboard-select:focus-visible {
    outline: 3px solid var(--dashboard-accent-soft);
    outline-offset: 0;
    border-color: var(--dashboard-accent) !important;
}

button:focus-visible,
a:focus-visible,
select:focus-visible {
    outline: 3px solid var(--dashboard-accent-soft);
    outline-offset: 2px;
}

/* A request in flight: a second press would sign in twice. */
[aria-busy="true"].dashboard-signin-button {
    opacity: 0.65;
    pointer-events: none;
}

/* The step a ticket offers is a thumb target for somebody holding a frying
   basket, and it is the only control on the board that is pressed all evening.
   It says so under the pointer. */
.dashboard-step:hover:not(:disabled) {
    filter: brightness(1.06);
}

.dashboard-step:active:not(:disabled) {
    transform: translateY(1px);
}

/* Sheets ------------------------------------------------------------------ */

/* The sheet's arrival and departure. The dialog stays mounted and only its
   `open` attribute changes, so the states are `[open]` and not-open — a
   selector — and the from-state needs `@starting-style`, an at-rule. */
.swiftwebui-dialog {
    opacity: 0;
    transform: translateY(10px) scale(0.985);
    transition:
        opacity 0.18s ease,
        transform 0.18s ease,
        overlay 0.18s ease allow-discrete,
        display 0.18s ease allow-discrete;
}

.swiftwebui-dialog[open] {
    opacity: 1;
    transform: none;
}

@starting-style {
    .swiftwebui-dialog[open] {
        opacity: 0;
        transform: translateY(10px) scale(0.985);
    }
}

.swiftwebui-dialog::backdrop {
    backdrop-filter: blur(2px);
    transition: opacity 0.18s ease, display 0.18s ease allow-discrete;
}

/* Loading ----------------------------------------------------------------- */

.dashboard-spinner {
    animation: dashboard-spin 0.8s linear infinite;
}

@keyframes dashboard-spin {
    to { transform: rotate(360deg); }
}

/* The refresh glyph turns while a fetch the operator asked for is in flight —
   the native `.rotationEffect` with a repeating animation. A poll never sets
   it: a spinner that blinks every twenty seconds is noise, not information. */
.dashboard-refreshing {
    animation: dashboard-spin 1s linear infinite;
}

/* A ticket that has just arrived. The native board animates a ticket in with
   opacity and a slight scale; here the node is new to the document, which is
   the one moment CSS can key off without Swift saying anything. */
@keyframes dashboard-ticket-in {
    from { opacity: 0; transform: scale(0.97); }
    to { opacity: 1; transform: none; }
}

.dashboard-ticket {
    animation: dashboard-ticket-in 0.18s ease-out;
}

/* Narrow screens ---------------------------------------------------------- */

/*
 * Below 900px the lanes become one scrolling column with their headers pinned,
 * which is the native `lanesStacked`. A phone cannot show four lanes side by
 * side, and a phone is a real place for this screen to be — it is what the
 * owner carries to the back.
 *
 * `!important` throughout because a modifier lowers to an inline style, which
 * no stylesheet rule outranks otherwise — the reason ordessa-web gives for its
 * own 900px block.
 */
@media (max-width: 900px) {
    .dashboard-lanes {
        flex-direction: column;
        overflow-x: hidden;
        overflow-y: auto;
        padding: 12px;
        gap: 12px;
    }

    .dashboard-lane {
        min-width: 0;
        flex: 0 0 auto;
    }

    /* The lane's own scroller goes: one column of lanes scrolls as one page, so
       a lane that kept its own would be a scroller inside a scroller and the
       pinned header would have nothing to pin against. */
    .dashboard-lane-scroll {
        overflow: visible;
        flex: 0 0 auto;
    }

    .dashboard-lane-header {
        position: sticky;
        top: 0;
        z-index: 3;
        background: var(--dashboard-background) !important;
    }

    .dashboard-header {
        flex-wrap: wrap;
        padding-left: 14px !important;
        padding-right: 14px !important;
    }

    /* The shop's name is the one thing on the header that says which counter
       this is. Everything beside it gives way first, and on a phone the row of
       controls takes a line of its own rather than squeezing it. */
    .dashboard-header-controls {
        width: 100%;
        justify-content: flex-start !important;
    }

    /* The word goes and the dot stays. A header that keeps "Live" ends up
       truncating the shop's name to make room for it, which is the wrong thing
       to lose — and red-versus-green already carries the whole message. */
    .dashboard-live-label {
        display: none;
    }

    /* A lane with nothing in it is a placeholder on a board with room for one
       and a wasted screen on a phone, where four of them would push the lane
       with work in it below the fold. This is the native `lanesStacked`
       skipping them, said in the one place that knows the screen is narrow. */
    .dashboard-lane-empty {
        display: none !important;
    }
}

@media (prefers-reduced-motion: reduce) {
    .dashboard-spinner {
        animation-duration: 2.4s;
    }

    .dashboard-refreshing,
    .dashboard-ticket {
        animation: none;
    }

    .swiftwebui-dialog,
    .swiftwebui-dialog::backdrop {
        transition: none !important;
    }

    .swiftwebui-dialog {
        transform: none;
    }

    .dashboard-step:active:not(:disabled) {
        transform: none;
    }
}
