/* 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;
    top: var(--mud-appbar-height, 64px);
    z-index: 10;
    padding: 8px 0;
    margin-bottom: 4px;
    background: var(--mud-palette-background);

    /* Horizontal scroll on a phone, where the categories will not fit across.
       The hidden scrollbar keeps it reading as a row of chips rather than a
       scrollable panel; it is still swipeable and keyboard reachable. */
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}

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

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

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

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

.frosty-category-tile__image,
.frosty-category-tile__placeholder {
    display: block;
    width: 100%;
    height: 110px;
}

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

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