/* Splash Screen Styles */
#app-splash-screen {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    background-color: #f4f6f8; /* A neutral color, or your app's primary brand color */
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    z-index: 99999;
    transition: opacity 0.5s ease-out;
}

    #app-splash-screen.fade-out {
        opacity: 0;
        pointer-events: none;
    }

    #app-splash-screen > h2 {
        margin-top: 20px;
        font-family: 'Roboto', sans-serif;
        color: #333;
    }

    #app-splash-screen .app-splash-spinner {
        width: 300px;
        height: 300px;
    }

.app-spinner-screen {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    transition: opacity 0.5s ease-out;
}

    .app-spinner-screen.fade-out {
        opacity: 0;
        pointer-events: none;
    }

    .app-spinner-screen > .app-spinner {
        width: 150px;
        height: 150px;
    }

.grecaptcha-badge {
    visibility: hidden;
}

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

/* ---------------------------------------------------------------------------
   Opaque backing for Text-variant MudBlazor alerts.

   MudBlazor's base .mud-alert is fully transparent and the Text variant (the
   default) layers on only a ~6% tint - e.g. .mud-alert-text-error resolves to
   rgba(244,67,54,0.06). That is designed for a solid page; over the animated
   BubbleBackground the bubbles show straight through the alert, which reads as
   them floating on top of it.

   Rather than flatten the tint away, an opaque surface is painted underneath it:
   background-color is painted BELOW background-image, so the surface sits at the
   bottom and each variant's tint is re-applied on top via a flat gradient. The
   alert therefore looks exactly as MudBlazor intends, just no longer see-through.

   The .mud-alert[class*=...] selector is (0,2,0), which outranks MudBlazor's own
   (0,1,0) .mud-alert-text-* background-color, and app.css loads after
   MudBlazor.min.css.
   --------------------------------------------------------------------------- */
.mud-alert[class*="mud-alert-text-"] {
    background-color: var(--mud-palette-surface);
    background-image: linear-gradient(var(--app-alert-tint, transparent), var(--app-alert-tint, transparent));
}

.mud-alert-text-normal    { --app-alert-tint: var(--mud-palette-dark-hover); }
.mud-alert-text-primary   { --app-alert-tint: var(--mud-palette-primary-hover); }
.mud-alert-text-secondary { --app-alert-tint: var(--mud-palette-secondary-hover); }
.mud-alert-text-tertiary  { --app-alert-tint: var(--mud-palette-tertiary-hover); }
.mud-alert-text-info      { --app-alert-tint: var(--mud-palette-info-hover); }
.mud-alert-text-success   { --app-alert-tint: var(--mud-palette-success-hover); }
.mud-alert-text-warning   { --app-alert-tint: var(--mud-palette-warning-hover); }
.mud-alert-text-error     { --app-alert-tint: var(--mud-palette-error-hover); }
.mud-alert-text-dark      { --app-alert-tint: var(--mud-palette-dark-hover); }

/* ---------------------------------------------------------------------------
   Vertically centre a MudAlert's icon against a multi-line message.

   MudAlert nests its icon and message inside .mud-alert-position, which MudBlazor
   sets to align-items:start - fine for a one-line alert, but on a validation
   summary listing several errors the icon sticks to the top instead of sitting
   beside the block of text.

   This has to target that inner wrapper: MudBlazor's own `align-center` utility,
   passed via Class, lands on the outer .mud-alert element, whose align-items has
   no bearing on the icon's position.

   Opt-in rather than global, so ordinary single-line alerts elsewhere keep
   MudBlazor's stock behaviour.
   --------------------------------------------------------------------------- */
.app-alert-center-icon .mud-alert-position {
    align-items: center;
}

/* ---------------------------------------------------------------------------
   Ordering page.

   The rail sticks under the app bar so the customer always knows which section
   they are in while scrolling one long menu. `position: sticky` rather than
   `fixed`: it should scroll away with the page above it and only pin once it
   reaches the top, which is what makes the page feel like one list instead of a
   list under a permanent toolbar.
   --------------------------------------------------------------------------- */
.frosty-menu-rail {
    position: sticky;

    /* Pinned a little higher than the app bar's nominal height, with the difference added back as
       padding. --mud-appbar-height is 64px, but the bar itself is 56px on a phone, and pinning at the
       nominal value left a transparent strip between the two where product names slid past. The bar
       paints above the rail, so overlapping it is invisible; the gap was not. */
    top: calc(var(--mud-appbar-height, 64px) - 12px);
    z-index: 10;
    padding: 20px 0 8px;
    margin-bottom: 4px;
    background: var(--mud-palette-background);

    /* One row, always. It scrolls sideways instead of wrapping: a rail that grows
       to three rows on a phone and then sticks there eats the screen the menu is
       supposed to fill. The scrollbar is hidden so it reads as a row of chips
       rather than a scrollable panel; it stays swipeable and keyboard reachable,
       and frosty.menuRail slides the active chip into view as the page scrolls. */
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    scroll-behavior: smooth;
}

.frosty-menu-rail::-webkit-scrollbar {
    display: none;
}

/* width: max-content is what actually defeats wrapping: the track is as wide as
   its chips need, and the rail scrolls over it. */
.frosty-menu-rail__track {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 8px;
    width: max-content;
    min-width: 100%;
    padding: 0 2px;
}

/* A chip. Plain button, styled to match the filled chips it replaces, so the rail
   owes nothing to MudChipSet's selection model. */
.frosty-menu-chip {
    flex: 0 0 auto;
    min-height: 36px;
    padding: 0 14px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 999px;
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-primary);
    font-family: inherit;
    font-size: 0.875rem;
    line-height: 1.2;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}

.frosty-menu-chip:hover {
    background: var(--mud-palette-action-default-hover);
}

.frosty-menu-chip:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

/* The section the customer is looking at. */
.frosty-menu-chip.is-active {
    background: var(--mud-palette-primary);
    border-color: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
    font-weight: 600;
}

/* Anchor targets. scroll-margin-top is a second line of defence behind the JS
   offset, so a browser that jumps to the anchor natively - a shared deep link,
   a back navigation - still clears the rail. */
.frosty-menu-section {
    scroll-margin-top: calc(var(--mud-appbar-height, 64px) + 64px);
}

.frosty-product-card {
    cursor: pointer;
    height: 100%;
    transition: box-shadow 120ms ease, transform 120ms ease;
}

.frosty-product-card:hover {
    box-shadow: var(--mud-elevation-4);
}

/* Sold out: dimmed and inert, but still on the menu. Removing it would read to
   the customer as "we stopped selling this" rather than "not today". */
.frosty-product-card--unavailable {
    cursor: default;
    opacity: 0.55;
}

.frosty-product-card--unavailable:hover {
    box-shadow: none;
}

/* Two lines of description, so cards in a row stay the same height whatever the
   copy length. */
.frosty-clamp-2 {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* The persistent basket bar. Fixed to the bottom so the order total is always
   visible without navigating away, and padded for the iOS home indicator. */
.frosty-cart-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
    background: var(--mud-palette-surface);
    box-shadow: var(--mud-elevation-8);
}

/* ---------------------------------------------------------------------------
   Option thumbnails in the customisation sheet.

   One rule for both the image and the empty slot that stands in for it, so the
   two can never drift apart and knock a group's labels out of line. `flex` is
   spelled out because these sit in a flex row: without a fixed basis and
   flex-shrink 0, a long option name squeezes the picture narrower on some rows
   and not others - which is the very misalignment the slot exists to prevent.
   --------------------------------------------------------------------------- */
.frosty-option-thumb {
    width: 44px;
    height: 44px;
    flex: 0 0 44px;
}

/* ---------------------------------------------------------------------------
   Single-select option groups.

   MudRadioGroup renders two nested elements: the outer .mud-input-control that
   a Class parameter lands on, and an inner .mud-radio-group that is the actual
   flex container - and MudBlazor styles that inner one `row` + `wrap`, so the
   options flow across the line and wrap. On a menu that put "Grande" on its own
   second line and, worse, pushed a leftover sibling up beside the heading of
   whatever group came next.

   It has to be targeted from CSS because the Class parameter cannot reach it.

   The selector repeats MudBlazor's own two classes on purpose. Theirs is
   `.mud-input-control.mud-input-control-boolean-input .mud-radio-group`, which
   is specificity (0,3,0) - a plain `.frosty-option-group .mud-radio-group` is
   (0,2,0) and silently loses, applying the gap but not the direction. Adding
   the marker to their pair makes this (0,4,0) and decisive, rather than relying
   on this file happening to load after MudBlazor's.
   --------------------------------------------------------------------------- */
.mud-input-control.mud-input-control-boolean-input.frosty-option-group .mud-radio-group {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    width: 100%;
}

/* ---------------------------------------------------------------------------
   Keeping the fixed cart bar off Google's reCAPTCHA notice.

   Google's terms require that notice to stay visible wherever the badge is
   hidden, so this is a compliance fix rather than a cosmetic one.

   The notice is not simply the last thing on a long page: the layout renders it
   inside .mud-main-content, a flex column, in a wrapper with `mt-auto` - so it
   is pinned to the bottom of that column. A spacer element placed before it is
   therefore useless; `mt-auto` just absorbs the extra room and parks the notice
   at the bottom regardless.

   Padding on the column itself is what moves it, and it is also the one answer
   that works in both states: on a short page it lifts the pinned notice above
   the bar, and on a page long enough to scroll it lets the content end above
   the bar instead of behind it.

   Applied only while the bar exists, via :has(), so pages without a cart bar
   keep their full height and gain no stray gap.
   --------------------------------------------------------------------------- */
:root {
    --frosty-cart-bar-height: 76px;
}

.frosty-cart-bar {
    min-height: var(--frosty-cart-bar-height);
    box-sizing: border-box;
}

body:has(.frosty-cart-bar) .mud-main-content {
    padding-bottom: calc(var(--frosty-cart-bar-height) + env(safe-area-inset-bottom));
}

/* ---------------------------------------------------------------------------
   The option row.

   The label and its description take the line; the price, control and chevron
   sit at the end at their natural widths. Before this the label shared one flex
   row with all of them and lost - measured at 49px on a 375px phone, wrapping
   and doubling the row height.

   min-width: 0 is what actually allows the shrink. A flex item's default
   min-width is auto, meaning "never smaller than my content", so without it the
   long name would push the price and control off the row instead of ellipsing.
   --------------------------------------------------------------------------- */
.frosty-option-body {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 1 1 auto;
    min-width: 0;
}

/* The whole line is the tap target, not just the 20px control. Not applied when
   the option is blocked - a pointer cursor on something that cannot be chosen
   promises an action that will not happen. */
.frosty-option-body--tappable {
    cursor: pointer;
}

/* ---------------------------------------------------------------------------
   Full-screen customisation sheets on phones.

   A centred dialog spends its margins on chrome: at 375px the sheet measured
   311px and its rows 263px, so a sixth of the screen went to framing a form
   that needed every pixel. Below MudBlazor's sm breakpoint it takes the screen,
   which is what every large food-ordering app does with this step.

   The selector doubles up .mud-dialog so it outweighs MudBlazor's own sizing
   rather than depending on this file loading afterwards.
   --------------------------------------------------------------------------- */
@media (max-width: 599.98px) {
    .mud-dialog.frosty-customize-dialog,
    .mud-dialog.frosty-nested-dialog {
        margin: 0;
        width: 100%;
        max-width: 100%;
        height: 100%;
        max-height: 100%;
        border-radius: 0;
        display: flex;
        flex-direction: column;
    }

    /* The content is the only part that scrolls, so the title stays put and the
       action bar stays reachable without scrolling to the end of the options. */
    .mud-dialog.frosty-customize-dialog .mud-dialog-content,
    .mud-dialog.frosty-nested-dialog .mud-dialog-content {
        flex: 1 1 auto;
        max-height: none;
        overflow-y: auto;
    }
}

/* ---------------------------------------------------------------------------
   Order queue rows.

   An overdue order - past its pickup time and still not made - is the one thing
   on that screen that should interrupt someone. A left border rather than a
   tinted background: the row stays readable at a glance from across a counter,
   and the marker survives both light and dark themes without a second rule.

   border-radius is cleared because a single-sided border on a rounded box
   leaves the accent floating away from the corner.
   --------------------------------------------------------------------------- */
.frosty-order-row {
    transition: border-color 120ms ease;
}

.frosty-order-row--overdue {
    border-left: 4px solid var(--mud-palette-error);
    border-radius: 0;
}

/* ---------------------------------------------------------------------------
   Option rows that open a further choice.

   A row whose selected option leads to a nested dialog has to look like a
   door, not a label: without a hover state nothing distinguished "tap here to
   choose your pearl type" from a plain row, and people did not know where to
   click. The tint on hover plus the chevron sliding right reads as "this goes
   somewhere" - the same affordance a settings list uses.
   --------------------------------------------------------------------------- */
.frosty-option-body--tappable:hover {
    background: var(--mud-palette-action-default-hover);
    border-radius: var(--mud-default-borderradius);
}

.frosty-option-body--opens-nested {
    cursor: pointer;
}

.frosty-option-body--opens-nested:hover {
    background: var(--mud-palette-primary-hover);
}

.frosty-option-row:hover .frosty-option-chevron {
    transform: translateX(3px);
    color: var(--mud-palette-primary);
}

.frosty-option-chevron {
    transition: transform 120ms ease, color 120ms ease;
}

/* The order number on the confirmation page. h3 is 48px, and on a phone the card is about 230px
   wide, so a nine-character code wrapped at its hyphen onto two lines - the one thing the customer
   has to read out at the counter, cut in half. The size follows the viewport instead: full h3 on a
   desktop, down to 1.5rem on the narrowest phones, and the hyphen is never a break opportunity. */
.mud-typography.frosty-order-number {
    font-size: clamp(1.5rem, 8vw, 3rem);
    font-weight: 700;
    letter-spacing: 0.02em;
    line-height: 1.2;
    white-space: nowrap;
}

/* Printing the confirmation: the card and nothing else. The app bar, the animated background, the
   footer notice and every button are chrome, not the receipt. */
@media print {
    .mud-appbar,
    .app-background,
    .frosty-footer,
    .frosty-no-print,
    .mud-snackbar-provider,
    .mud-popover-provider,
    .grecaptcha-badge {
        display: none !important;
    }

    .mud-main-content {
        padding-top: 0 !important;
    }

    .frosty-print-card {
        box-shadow: none !important;
        border: 1px solid #999 !important;
    }

    /* Belt and braces: no control on the printed card, whatever class it happens to carry. */
    .frosty-print-card button {
        display: none !important;
    }
}

/* ---------------------------------------------------------------------------
   The kitchen ticket, printed from the order board. The block does not show on screen at all; when it
   is on the page, print shows it and nothing else - the board, the drawer and the app bar all stay
   behind. Sizes are in points because this is paper. */
.frosty-ticket-print {
    display: none;
}

@media print {
    .mud-drawer {
        display: none !important;
    }

    body:has(.frosty-ticket-print) .frosty-screen {
        display: none !important;
    }

    body:has(.frosty-ticket-print) .mud-main-content {
        padding: 0 !important;
    }

    .frosty-ticket-print {
        display: block !important;
    }

    .frosty-ticket {
        color: #000;
        font-size: 12pt;
        line-height: 1.35;
    }

    .frosty-ticket__header {
        border-bottom: 2px solid #000;
        padding-bottom: 6pt;
        margin-bottom: 8pt;
    }

    .frosty-ticket__store {
        font-size: 14pt;
    }

    .frosty-ticket__number {
        font-size: 28pt;
        font-weight: 700;
        letter-spacing: 0.04em;
    }

    /* The word the counter calls out, under the number and nearly as large. Boxed because on a
       thermal print there is no colour to separate it from the number above it, and someone
       glancing at the paper has to see two distinct things rather than one long line. */
    .frosty-ticket__alias {
        font-size: 20pt;
        font-weight: 700;
        line-height: 1.2;
        margin-top: 2pt;
        padding: 2pt 4pt;
        border: 1.5pt solid #000;
        border-radius: 2pt;

        /* A customer-chosen word can be longer than the paper is wide. */
        overflow-wrap: anywhere;
    }

    .frosty-ticket__status {
        text-transform: uppercase;
    }

    .frosty-ticket__meta th {
        text-align: left;
        padding: 1pt 10pt 1pt 0;
        font-weight: 600;
        white-space: nowrap;
        vertical-align: top;
    }

    .frosty-ticket__due {
        font-size: 14pt;
        font-weight: 700;
    }

    .frosty-ticket__alert {
        border: 1px solid #000;
        padding: 4pt 6pt;
        margin: 8pt 0;
    }

    .frosty-ticket__lines {
        list-style: none;
        padding: 0;
        margin: 10pt 0 0;
    }

    .frosty-ticket__line {
        border-top: 1px solid #000;
        padding: 6pt 0;
        break-inside: avoid;
    }

    .frosty-ticket__line-head {
        display: flex;
        gap: 8pt;
        font-size: 14pt;
        font-weight: 700;
    }

    .frosty-ticket__qty {
        min-width: 30pt;
    }

    .frosty-ticket__name {
        flex: 1;
    }

    .frosty-ticket__mods {
        list-style: none;
        margin: 2pt 0 0;
        padding: 0 0 0 38pt;
        font-size: 11pt;
    }

    .frosty-ticket__note {
        margin: 3pt 0 0 38pt;
        font-style: italic;
        font-size: 11pt;
    }

    .frosty-ticket__totals {
        width: 100%;
        margin-top: 8pt;
        border-top: 2px solid #000;
        border-collapse: collapse;
    }

    .frosty-ticket__totals td {
        padding: 2pt 0;
    }

    .frosty-ticket__totals td:last-child {
        text-align: right;
    }

    .frosty-ticket__total td {
        font-size: 16pt;
        font-weight: 700;
        padding-top: 4pt;
    }

    .frosty-ticket__footer {
        margin-top: 10pt;
        font-size: 10pt;
        text-align: center;
    }

    /* The desglose, under the total it explains. Black on white and no dashed rule: a thermal printer
       renders a solid line reliably and a dashed one as a smudge. */
    .frosty-ticket .frosty-tax {
        margin-top: 6pt;
        padding-top: 4pt;
        border-top: 1px solid #000;
        color: #000;
        font-size: 10pt;
    }

    .frosty-ticket .frosty-tax__table th {
        width: 5em;
    }

    /* Says what the paper is, so nobody files it as a receipt. Centred and small - a disclaimer that
       shouts competes with the total. */
    .frosty-ticket__disclaimer {
        margin-top: 6pt;
        font-size: 9pt;
        font-style: italic;
        text-align: center;
    }

    /* COMANDA, or the customer's summary: read across the room to tell two printouts apart. */
    .frosty-ticket__kind {
        font-size: 11pt;
        font-weight: 700;
        letter-spacing: 0.08em;
        text-transform: uppercase;
    }
}

/* 80 mm receipt paper: the same ticket, laid out for a thermal roll. A named page keeps these rules
   from touching anything else that prints; a zero page margin also stops the browser adding its own
   header and footer lines, so the ticket carries its own padding. The paper size itself is left to the
   printer driver (the roll width), which is what a receipt printer expects. */
@page ticket {
    margin: 0;
}

@media print {
    .frosty-ticket--receipt {
        page: ticket;
        width: 72mm;
        padding: 3mm 4mm 8mm;
        font-size: 10pt;
        line-height: 1.3;
    }

    .frosty-ticket--receipt .frosty-ticket__header {
        padding-bottom: 4pt;
        margin-bottom: 6pt;
    }

    .frosty-ticket--receipt .frosty-ticket__store {
        font-size: 12pt;
    }

    .frosty-ticket--receipt .frosty-ticket__number {
        font-size: 22pt;
    }

    .frosty-ticket--receipt .frosty-ticket__alias {
        font-size: 16pt;
    }

    .frosty-ticket--receipt .frosty-ticket__meta th {
        padding-right: 6pt;
    }

    .frosty-ticket--receipt .frosty-ticket__due {
        font-size: 12pt;
    }

    .frosty-ticket--receipt .frosty-ticket__line {
        padding: 4pt 0;
    }

    .frosty-ticket--receipt .frosty-ticket__line-head {
        font-size: 12pt;
        gap: 5pt;
    }

    .frosty-ticket--receipt .frosty-ticket__qty {
        min-width: 22pt;
    }

    .frosty-ticket--receipt .frosty-ticket__mods {
        padding-left: 22pt;
        font-size: 9.5pt;
    }

    .frosty-ticket--receipt .frosty-ticket__total td {
        font-size: 13pt;
    }

    .frosty-ticket--receipt .frosty-ticket__footer {
        margin-top: 6pt;
        font-size: 9pt;
    }
}

/* ---------------------------------------------------------------------------
   Category tiles at the top of the menu: picture, name, and a jump to the section. */
.frosty-category-tile {
    cursor: pointer;
    overflow: hidden;
    transition: transform 120ms ease, box-shadow 120ms ease;
}

.frosty-category-tile:hover,
.frosty-category-tile:focus-visible {
    transform: translateY(-2px);
    box-shadow: var(--mud-elevation-4);
}

/* Every tile the same height whatever its name does, so the row of them lines up. */
.frosty-category-tile {
    height: 100%;
}

/* One picture box for every tile: same width, same height, square.

   Square because that is the shape the picture arrives in. MenuService asks the
   image service for 400x400 and the service crops to exactly the size in the
   path, so what comes back is always a square - and a square in a square box
   fills it edge to edge with nothing letterboxed and nothing cropped. Any other
   box ratio has to give one of those two up: a 4:3 box either floats the picture
   in grey bars or takes a second bite out of it, which on a round logo means the
   top and bottom of the circle.

   `cover` rather than `contain` so a file that is somehow not square still fills
   the box instead of sitting in a frame, and `center` so what it loses is the
   edges rather than one side. */
.frosty-category-tile__image,
.frosty-category-tile__placeholder {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    background: var(--mud-palette-background-gray);
}

.frosty-category-tile__image {
    object-fit: cover;
    object-position: center;
}

.frosty-category-tile__placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--mud-palette-text-secondary);
}


/* ---------------------------------------------------------------------------
   The order board's call-out alias: the line a colleague reads across the shop,
   so it outranks the item count next to it without becoming a heading. */
.frosty-queue-alias {
    font-weight: 700;
    line-height: 1.3;

    /* Customer-chosen text in a flexible column: it wraps rather than pushing
       the due time off a phone. */
    overflow-wrap: anywhere;
}


/* ---------------------------------------------------------------------------
   The privacy and legal notices. Long prose rather than an interface, so the
   line length is capped for reading and the identity block is laid out as
   label-and-value pairs that collapse to two lines on a phone. */
.frosty-legal p {
    max-width: 68ch;
}

.frosty-legal__updated {
    color: var(--mud-palette-text-secondary);
}

.frosty-legal-row {
    display: flex;
    gap: 12px;
    align-items: baseline;
    flex-wrap: wrap;
}

.frosty-legal-row__label {
    flex: 0 0 140px;
    color: var(--mud-palette-text-secondary);
    font-size: 0.85rem;
}

@media (max-width: 599.98px) {
    .frosty-legal-row__label {
        flex-basis: 100%;
    }
}

/*
 * Motion, for a visitor who has asked their system not to have much of it.
 *
 * The background component answers this on its own (BubbleBackground.razor.css); this is everything
 * else - animate.css, which is loaded from a CDN and drives the Animate component, and MudBlazor's own
 * transitions. Near-zero rather than zero: a duration of exactly 0 makes some browsers skip the
 * transitionend event a few components wait for, and a component waiting for an event that never comes
 * is a worse accessibility outcome than a 10ms fade.
 *
 * Scrolling is included because smooth scrolling is motion the visitor did not ask for either; the
 * jump-to-category rail uses it.
 */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/*
 * A visible focus ring on everything that takes focus, not only the two places that had one.
 *
 * MudBlazor styles focus on its own controls, but plain anchors, the skip target and anything this
 * application draws itself were relying on the browser default - which several browsers suppress once
 * a stylesheet touches outline anywhere. :focus-visible rather than :focus so a mouse click does not
 * leave a ring behind, which is the thing that makes people remove focus styles in the first place.
 */
:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
    border-radius: 2px;
}

/*
 * One contrast correction that does not belong in the theme.
 *
 * `Color.Secondary` on a MudText does not mean "secondary text" - it means the theme's secondary
 * colour, which is MudBlazor's pink #FF4081. Every product description, hint and caption in this
 * application asks for it, so they were all rendering in pink at 3.33:1 against white where 4.5:1 is
 * the floor for text that size. That is a misuse of an accent rather than a bad accent, so it is
 * corrected here and not in the palette: the pink stays exactly what it is for anything that genuinely
 * wants the secondary colour, and text that plainly meant "muted" gets the palette's own
 * text-secondary, in whichever theme is showing.
 *
 * Marked important because MudBlazor marks its own that way - `.mud-secondary-text` carries
 * `!important` in their stylesheet, so no amount of specificity here would reach it. Same reasoning as
 * the promo row in doc 07 entry 40: the override names only classes MudBlazor is unlikely to rename,
 * and the failure mode if they do is a return to pink, which is visible rather than silent.
 *
 * The other contrast failures - success, error and warning, all of which are Material fill swatches
 * being used as text - are fixed in the palette instead; see FrostyTheme.
 */
.mud-typography.mud-secondary-text {
    color: var(--mud-palette-text-secondary) !important;
}

/*
 * The IVA breakdown, wherever it appears: the cart, the customer's own order page, the counter's
 * dialog and the printed ticket all render the same component.
 *
 * Quiet on purpose. It is a statement about money already shown above it, not a number anybody has to
 * act on - the total is the figure that matters and this explains what it was made of.
 */
.frosty-tax {
    margin-top: 12px;
    padding-top: 8px;
    border-top: 1px dashed var(--mud-palette-lines-default);
    color: var(--mud-palette-text-secondary);
    font-size: 0.8125rem;
}

.frosty-tax__title {
    font-weight: 600;
    margin-bottom: 2px;
}

.frosty-tax__table {
    width: 100%;
    border-collapse: collapse;
}

/* Rates line up on the left, money on the right, whatever the rate's width. */
.frosty-tax__table th {
    width: 4.5em;
    text-align: left;
    font-weight: 600;
    padding: 1px 0;
}

.frosty-tax__table td {
    text-align: right;
    padding: 1px 0;
    font-variant-numeric: tabular-nums;
}

.frosty-tax__label {
    margin-right: 4px;
}

/*
 * The promo code row: a text field and the button that applies it, sharing one top and one bottom edge.
 *
 * A dense MudBlazor input control carries its own vertical margins (8px above, 4px below) to leave room
 * for the floating label. A button beside it has neither, so the two boxes start and end in different
 * places - and nudging the button down by hand only lines the tops up, at one font size, until
 * something changes. Taking the field's margins off instead makes the row's height the field's height,
 * and AlignItems.Stretch on the row then gives the button exactly that height, whatever it turns out to
 * be. The spacing removed here comes back from the row's own mt-3, which also matches the caption below.
 *
 * Marked important, on a class of this application's own, because MudBlazor sets those margins from
 * `.mud-input-control.mud-input-control-margin-dense.mud-input-outlined-with-label` - three classes, so
 * any sane selector here loses on specificity. Naming that chain to outrank it would be the more
 * fragile choice: it is exactly the kind of override doc 07 entry 40 warns about, and an upgrade that
 * renames one of those classes would undo the override silently. This way nothing depends on their
 * internals, only on the cascade.
 */
.frosty-promo-row__field {
    margin-top: 0 !important;
    margin-bottom: 0 !important;
}


/* ---------------------------------------------------------------------------
   Phone layout.

   Everything below exists because a 390px screen in Spanish is the tightest
   thing this application has to fit into: Spanish labels run roughly a third
   longer than the English ones the desktop layout was measured against, and a
   row built for a laptop clips rather than wraps. 600px is MudBlazor's own
   sm breakpoint, so these rules start exactly where its grid switches to one
   column.
   --------------------------------------------------------------------------- */

/* The customisation sheet's footer. Two blocks: what it costs, and what to do
   about it. Side by side when there is room, stacked when there is not. */
.frosty-sheet-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 16px;
    width: 100%;
}

.frosty-sheet-actions__status {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 1 1 auto;
    min-width: 0;
}

.frosty-sheet-actions__total {
    white-space: nowrap;
    font-weight: 600;
}

.frosty-sheet-actions__buttons {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
    margin-left: auto;
}

/* Rows in the admin forms - attached modifier groups, a group's options, a
   category's products, the preselection tree. Built as one wide line for a
   desktop; on a phone the tail of every row (status chip, actions, the locked
   and refundable switches) was clipped with no way to scroll to it. Giving the
   name the whole first line pushes the rest onto a second one. */
.frosty-tree-row {
    margin-left: calc(var(--frosty-depth, 0) * 28px);
}

@media (max-width: 599.98px) {
    .frosty-row {
        flex-wrap: wrap;
        row-gap: 8px;
    }

    .frosty-row > .flex-grow-1 {
        flex-basis: 100%;
        min-width: 0;
    }

    /* Nested options start 28px further right per level, which on a phone is the
       difference between a readable row and a clipped one. */
    .frosty-tree-row {
        margin-left: calc(var(--frosty-depth, 0) * 12px);
    }

    /* The sheet footer, stacked: quantity and total on one line, buttons on the
       next, with the primary action taking the room it needs. */
    .frosty-sheet-actions__status,
    .frosty-sheet-actions__buttons {
        flex: 1 1 100%;
    }

    .frosty-sheet-actions__status {
        justify-content: space-between;
    }

    .frosty-sheet-actions__buttons {
        margin-left: 0;
    }

    .frosty-sheet-actions__buttons > .mud-button-filled {
        flex: 1 1 auto;
    }

    /* Ordering page: the summary above the form, so the button that places the
       order is the last thing on the page rather than the middle of it. */
    .frosty-order-summary {
        order: 1;
    }

    .frosty-order-form {
        order: 2;
    }

    /* Plus and minus are 26px at Size.Small, which is fine with a mouse and too
       small for a thumb. The icon stays small; the target grows. */
    .frosty-qty .mud-icon-button {
        min-width: 44px;
        min-height: 44px;
    }

    /* Four Spanish tab labels do not fit across a phone. MudBlazor's answer is a
       scrolling bar with two arrows and a clipped word, which hides half the form
       behind a control nobody notices. On a phone the tabs wrap onto a second row
       instead, so every section of the form is one tap away.

       Three MudBlazor defaults have to go for that: the bar's max-content width,
       which makes flex-wrap a no-op; the nowrap and clipping on its content; and
       the sliding transform the arrows drive. The slider that marks the active tab
       goes with them - it can only underline one row - so the active tab carries
       its own underline instead. */
    .mud-tabs-tabbar-content {
        overflow: visible;
        white-space: normal;
    }

    /* Two class names, because MudBlazor's own max-content width is written that
       way and a single class would lose to it. */
    .mud-tabs-tabbar-content .mud-tabs-tabbar-wrapper {
        width: 100%;
        flex-wrap: wrap;
        transform: none !important;
        transition: none;
    }

    .mud-tabs-scroll-button,
    .mud-tab-slider {
        display: none;
    }

    .mud-tabs .mud-tab {
        min-width: 0;
        padding-left: 10px;
        padding-right: 10px;
        font-size: 0.74rem;
        letter-spacing: 0;
    }

    .mud-tabs .mud-tab.mud-tab-active {
        box-shadow: inset 0 -2px 0 0 var(--mud-palette-primary);
    }
}
