@import '_content/Blazored.Toast/Blazored.Toast.bundle.scp.css';
@import '_content/Microsoft.AspNetCore.Components.QuickGrid/Microsoft.AspNetCore.Components.QuickGrid.boiwgh0w5b.bundle.scp.css';

/* /Components/Controls/AiChatWidget.razor.rz.scp.css */
/* ── Widget container ─────────────────────────────────────────── */

.chat-widget[b-otmf18nv24] {
    position: fixed;
    bottom: 1.5rem;
    right: 1.5rem; /* body-container's scrollbar now sits at the true window edge, same as a native scrollbar would */
    z-index: 200;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.75rem;
    pointer-events: none;
}

.chat-widget > *[b-otmf18nv24] {
    pointer-events: auto;
}

/* ── Floating action button ───────────────────────────────────── */

.chat-fab[b-otmf18nv24] {
    /* AB#1033. A custom property rather than a rule on .nibble, because custom properties inherit into
       the child component's own scope and a scoped class selector cannot reach across into it. */
    --nibble-size: 40px;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    border: none;
    /* AB#1033. A dark centre inside the brand gradient, rather than the flat gradient this used to be.
       Nibble is white with a cyan and magenta edge, and on the gradient both halves of that edge landed
       on a colour close enough to cancel them, leaving a white blob. On the dark centre the edge reads
       and the gradient survives as a ring, which is the part of it anyone was looking at anyway. */
    background:
        radial-gradient(circle at 50% 50%, var(--color-bg-base) 0 66%, transparent 67%),
        var(--gradient-brand);
    color: #fff;
    font-size: 1.4rem;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    flex-shrink: 0;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4), 0 0 16px rgba(255, 0, 255, 0.2);
    transition: transform 0.15s, box-shadow 0.15s;
}

.chat-fab:hover[b-otmf18nv24] {
    transform: scale(1.08);
    box-shadow: 0 6px 28px rgba(0, 0, 0, 0.5), 0 0 24px rgba(255, 0, 255, 0.35);
}

.chat-fab:active[b-otmf18nv24] {
    transform: scale(0.94);
}

/* ── Floating panel ───────────────────────────────────────────── */

.chat-widget-panel[b-otmf18nv24] {
    width: 380px;
    height: 520px;
    display: flex;
    flex-direction: column;
    border-radius: 1rem;
    overflow: hidden;
    box-shadow: 0 8px 40px rgba(0, 0, 0, 0.55);
}

/* ── Docked at desktop widths (AB#1013) ───────────────────────── */

/* MainLayout renders this widget as a child of .page-body's grid. Closed, everything above still
   applies: position: fixed takes it out of flow, so the grid never sees it and nothing about the
   page changes. Open, .chat-docked adds a column and these rules drop the widget into it, which is
   what makes the page reflow beside the panel rather than sit underneath it.

   The last-track lines (-2 / -1) rather than a column number: the nav gives up its column between
   992px and 1280px, so the dock is the third track on a wide window and the second on a narrow one. */
@media (min-width: 992px) {
    /* No padding anywhere: the dock is the panel and nothing else, flush to the window edge and running
       the full height of the grid. Anything the widget left around the panel showed the page backdrop
       through a gap that read as a rendering artifact rather than as a margin. */
    .page-body.chat-docked .chat-widget[b-otmf18nv24] {
        position: relative;
        /* Load-bearing. The floating state sets bottom/right to hold the widget off the window edge,
           and under position: fixed those are insets from the viewport. Under relative the same two
           properties are *offsets* instead, so they drag the whole dock 1.5rem up and 1.5rem left of
           its own column. relative is only here so the resize grip has something to anchor to. */
        bottom: auto;
        right: auto;
        grid-column: -2 / -1;
        grid-row: 1 / -1;
        min-height: 0;
        padding: 0;
        gap: 0;
    }

    /* The panel's own header carries a close button, so a second one floating over the conversation is
       just a control competing with itself - and it was the only reason the column needed room around
       the panel at all. The button comes back the moment the panel closes and the widget floats again. */
    .page-body.chat-docked .chat-fab[b-otmf18nv24] {
        display: none;
    }

    .page-body.chat-docked .chat-widget-panel[b-otmf18nv24] {
        width: 100%;
        height: auto;
        flex: 1 1 auto;
        min-height: 0;
        /* Square and unshadowed: it is an edge of the window now, not a card floating above one. */
        border-radius: 0;
        box-shadow: none;
    }
}

/* ── Resize grip (AB#1013) ────────────────────────────────────── */

/* Nothing to resize while the panel floats, so the grip only exists in the docked state below. */
.chat-dock-grip[b-otmf18nv24] {
    display: none;
}

@media (min-width: 992px) {
    /* The whole left edge of the dock is the drag target, with the visible grip in the middle of it -
       a 6px bar is honest about how thin the seam is, and a 16px column with the panel's own gutter
       beside it is what you can actually hit. */
    .page-body.chat-docked .chat-dock-grip[b-otmf18nv24] {
        display: flex;
        align-items: center;
        justify-content: center;
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        width: 16px;
        /* Above the panel, which is now flush against this edge rather than inset from it. .chat-panel
           is position: relative and comes later in the DOM, so without this the grip paints underneath
           it and there is nothing to grab. */
        z-index: 3;
        cursor: col-resize;
        /* Without this a touch drag scrolls the page instead of moving the edge. */
        touch-action: none;
    }

    .page-body.chat-docked .chat-dock-grip-bar[b-otmf18nv24] {
        width: 6px;
        height: 48px;
        border-radius: 3px;
        background: var(--color-border);
        transition: background 0.15s, height 0.15s;
    }

    .page-body.chat-docked .chat-dock-grip:hover .chat-dock-grip-bar[b-otmf18nv24],
    .page-body.chat-docked .chat-dock-grip:focus-visible .chat-dock-grip-bar[b-otmf18nv24] {
        background: var(--color-cyan);
        height: 72px;
    }

    .page-body.chat-docked .chat-dock-grip:focus-visible[b-otmf18nv24] {
        outline: 2px solid var(--color-cyan);
        outline-offset: -2px;
        border-radius: 4px;
    }
}

/* ── Responsive: fridge / small screens ──────────────────────── */

@media (max-width: 575.98px) {
    .chat-widget[b-otmf18nv24] {
        bottom: 1rem;
        right: 1rem;
    }

    .chat-widget-panel[b-otmf18nv24] {
        width: calc(100vw - 2rem);
        height: 70vh;
    }
}
/* /Components/Controls/BusyButton.razor.rz.scp.css */
/* inline-flex to get the icon and the label onto one baseline without the caller adding me-1 to every
   icon, and to make the button a positioning context for the overlay spinner. justify-content matters
   for the icon-only case, where btn-icon's 48px floor leaves the glyph room to drift. */
.busy-btn[b-62uo88ingy] {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.375rem;
}

/* The text-only case, driven off the aria-busy the button already sets rather than a second class
   saying the same thing. The label stays laid out and only stops being drawn, which is what keeps the
   button the same width busy as idle, and opacity keeps it in the accessibility tree where
   visibility: hidden would not. Attribute selectors are as old as CSS, so the fridge's M94 is fine. */
.busy-btn[aria-busy="true"] .busy-btn-label[b-62uo88ingy] {
    opacity: 0;
}

/* Centred by negative margins rather than a transform: spinner-border is already animating a
   transform, and stacking a second one on the same element is a fight not worth having.
   spinner-border-sm is 1rem square, so half of it is 0.5rem. */
.busy-btn-overlay[b-62uo88ingy] {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -0.5rem;
    margin-left: -0.5rem;
}
/* /Components/Controls/ConsentBanner.razor.rz.scp.css */
/* Analytics consent banner (AB#776).

   Bottom-anchored and self-sizing so it never covers the primary actions of the page behind it: the
   app's own controls sit in the scrolling content area above, and on the fridge (720x1080 portrait)
   the banner takes a strip at the bottom that one tap clears. Fridge-safe CSS only — Samsung Internet
   10 / Chromium M94, so no :has(), @container, nesting, @layer or subgrid. */

.consent-banner[b-v1n6jkgbj9] {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1080; /* above Bootstrap modals' backdrop tier so it is never buried */
    padding: 0.75rem;
    background: rgba(26, 31, 46, 0.98);
    border-top: 1px solid var(--color-cyan, #00D4FF);
    box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.45);
}

.consent-banner[hidden][b-v1n6jkgbj9] {
    display: none;
}

.consent-banner-inner[b-v1n6jkgbj9] {
    max-width: 720px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.consent-banner-text[b-v1n6jkgbj9] {
    margin: 0;
    color: var(--color-text-secondary, #A0AABB);
    font-size: 0.875rem;
    line-height: 1.45;
}

.consent-banner-text a[b-v1n6jkgbj9] {
    color: var(--color-cyan, #00D4FF);
}

/* Two equal columns rather than content-sized flex items, so the buttons are identical widths however
   long the copy in each language turns out to be. "Reject must be as easy as accept" then holds by
   construction instead of by whichever label happens to be shorter. */
.consent-banner-actions[b-v1n6jkgbj9] {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
}

/* Both answers share every dimension. Only the colour differs, and the reject button is deliberately
   not the muted/ghost one — "reject must be as easy as accept" is a layout property, not a wording
   one. 48px minimum height is the fridge touch-target floor. */
.consent-btn[b-v1n6jkgbj9] {
    min-height: 48px;
    min-width: 8rem;
    padding: 0.625rem 1.25rem;
    border-radius: 0.5rem;
    font-size: 0.9375rem;
    font-weight: 600;
    cursor: pointer;
}

.consent-btn-accept[b-v1n6jkgbj9] {
    background: var(--color-cyan, #00D4FF);
    border: 1px solid var(--color-cyan, #00D4FF);
    color: #0A0E1A;
}

.consent-btn-reject[b-v1n6jkgbj9] {
    background: transparent;
    border: 1px solid var(--color-text-secondary, #A0AABB);
    color: #FFFFFF;
}

.consent-btn:focus-visible[b-v1n6jkgbj9] {
    outline: 2px solid var(--color-cyan, #00D4FF);
    outline-offset: 2px;
}

/* Side by side once there is room; stacked full-width below that so each stays a comfortable target. */
@media (min-width: 576px) {
    .consent-banner-inner[b-v1n6jkgbj9] {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
    }

    .consent-banner-actions[b-v1n6jkgbj9] {
        flex: 0 0 auto;
    }
}
/* /Components/Controls/DraftMealChip.razor.rz.scp.css */
/* Styles for a proposed meal chip (AB#478).

   Self-contained rather than shared with MealChip.razor.css, because Blazor's scoped CSS keys on the
   component that *renders* an element: this component's markup carries its own scope attribute, so none
   of the chip rules over there match it. Only the shape is copied, and only the parts a ghost actually
   needs — there is no grip, no kebab, no drag state and no menu here, because a suggestion cannot be
   dragged, opened or removed.

   Inside the fridge's ~M94 budget: no :has(), container queries, CSS nesting, @layer or view transitions.

   The distinction from a committed chip is carried three ways on purpose, because any one of them alone
   fails somebody: a dashed border (visible without colour), a washed-out fill (visible without shape),
   and the word "Suggested" in the label (the only one that survives a screen reader). Rule 17's backdrop
   treatment leaves the meal-time tints as the only meaningful hue on this page, so the ghost stays
   deliberately colourless rather than claiming one of its own.

   AB#1039 splits that: the three ways above hold for a dish nobody has written down, and a suggestion
   that resolved to something already in the library is drawn as the real thing instead. It keeps the
   label, which was always the only one of the three that reached everybody, and what remains unsaved
   about it is said by the bar over the week rather than by the chip. */

.mp-chip[b-1qx6kga15z] {
    display: flex;
    align-items: stretch;
    gap: 4px;
    padding: 2px;
    font-size: 0.78rem;
    line-height: 1.3;
    /* Not draggable and not selectable-into: it is a picture of a plan, not part of one. */
    user-select: none;
    -webkit-user-select: none;
}

.mp-chip-draft[b-1qx6kga15z] {
    /* Dashed rather than solid, and inside the same 4px radius the real chips use, so a ghosted slot
       lines up with a committed one instead of sitting a pixel out. */
    border: 1px dashed var(--bs-secondary-color, #6c757d);
    border-radius: 4px;
    background: var(--bs-secondary-bg, #e9ecef);
    color: var(--bs-secondary-color, #6c757d);
    font-style: italic;
    /* Deliberately no opacity on the chip as a whole. Fading it would take the name down with it, and the
       names have to stay readable at arm's length on a 720x1080 door — the fade is on the thumbnail only. */

    /* The height a chip with a picture comes out at: a 48px thumbnail with the padding and border around
       it. Stated rather than derived, because a dish nobody has written down has no picture to derive it
       from and collapsed to the height of its own text — so a week of new dishes drew a row of stubs
       beside the full-size chips of the meals they already had, and the grid stepped up and down across
       the days for a reason that had nothing to do with the food.

       One pixel less padding than a committed chip, because the border takes that pixel back: box-sizing
       is border-box, so the two boxes then measure the same and a cell holding one of each does not step
       either. */
    padding: 1px;
    min-height: 52px;
}

/* A suggestion that resolved to something already in the library (AB#1039). It is drawn as the chip it
   stands for rather than as a ghost, because the ghost treatment is a statement about the dish not
   existing and this one does: the recipe is written down, it has the household's own photo on it, and
   approving costs nothing. Ghosting it hid the most useful thing about a proposed week, which is how
   much of it they already have.

   The two hues are MealChip's own, restated rather than shared for the reason at the top of this file:
   scoped CSS keys on the component that renders the element, so a rule over there matches nothing here.
   Both classes are written with .mp-chip-draft in front so they win on specificity rather than on order,
   and cannot be undone by a later edit moving the rules around. */
.mp-chip-draft.mp-chip-draft-recipe[b-1qx6kga15z],
.mp-chip-draft.mp-chip-draft-pantry[b-1qx6kga15z],
.mp-chip-draft.mp-chip-draft-product[b-1qx6kga15z],
.mp-chip-draft.mp-chip-draft-notcooked[b-1qx6kga15z] {
    border-style: solid;
    border-color: transparent;
    font-style: normal;
}

.mp-chip-draft.mp-chip-draft-recipe[b-1qx6kga15z] {
    background: #d1ecf1;
    color: #0c5460;
}

.mp-chip-draft.mp-chip-draft-pantry[b-1qx6kga15z] {
    background: #d4edda;
    color: #155724;
}

/* The last two are solid for a different reason from the first two, and it is worth saying which. Those
   are solid because the recipe exists; these are solid because there is nothing left to make (AB#1078,
   AB#1083). A dashed ghost promises that approving will go and write something, and for a night out or a
   frozen lasagna that promise would be false. Both hues are MealChip's own, restated here because scoped
   CSS keys on the component that renders the element. */
.mp-chip-draft.mp-chip-draft-product[b-1qx6kga15z] {
    background: #fff3cd;
    color: #664d03;
}

/* Darker than the grey this started as, for MealChip's reason and for one this file has on its own: a
   ghost's background is var(--bs-secondary-bg), which Bootstrap defines as #e9ecef and nothing here
   overrides. So a night out and a dish nobody has written down were painted the same colour, and the only
   thing left telling them apart was a dashed border and an italic. Two states that mean opposite things
   about what approving will do should not have to be told apart by a border. */
.mp-chip-draft.mp-chip-draft-notcooked[b-1qx6kga15z] {
    background: #ced4da;
    color: #343a40;
}

/* Same square box as a committed chip's thumbnail (AB#886), so a row of half-real, half-suggested cells
   does not read as ragged. Faded because the picture is a stock search result standing in for a dish
   nobody has cooked yet, not a photo of the household's own food. */
.mp-chip-thumb[b-1qx6kga15z] {
    align-self: center;
    width: 48px;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: 2px;
    flex-shrink: 0;
    opacity: 0.65;
}

/* The fade above is an argument about stock photography, and it does not apply to a resolved suggestion:
   that picture is the household's own recipe photo, the same blob the committed chip would show. */
.mp-chip-draft-recipe .mp-chip-thumb[b-1qx6kga15z],
.mp-chip-draft-pantry .mp-chip-thumb[b-1qx6kga15z] {
    opacity: 1;
}

/* Matching MealChip's link: the chip's own colour, underlined only on hover, so the name reads as a name
   rather than as a row of blue link text in a grid made mostly of names. */
.mp-chip-link[b-1qx6kga15z] {
    color: inherit;
    text-decoration: none;
}

.mp-chip-link:hover[b-1qx6kga15z] {
    text-decoration: underline;
}

.mp-chip-body[b-1qx6kga15z] {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1px;
}

.mp-chip-name[b-1qx6kga15z] {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The one-line aside the proposal came with ("uses up the chicken"). Smaller and dimmer than the name,
   and clipped to one line: a cell is a few characters wide on the fridge and this must never be the
   reason a week stops fitting. */
.mp-chip-note[b-1qx6kga15z] {
    font-size: 0.7rem;
    font-style: normal;
    opacity: 0.8;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* What approving has made of this suggestion (AB#1012): a tick once it points at a real recipe, a warning
   when it could not be turned into one. Pinned to the top-right corner rather than added to the body,
   because the body is already two clipped lines wide on a 720x1080 door and a third element in the flow
   would push the name out of a cell that barely fits it.

   align-self keeps it at the top of a chip whose thumbnail makes the row 48px tall; the shrink guard stops
   flex collapsing an icon that has no text to hold it open. */
.mp-chip-state[b-1qx6kga15z] {
    align-self: flex-start;
    flex-shrink: 0;
    font-size: 0.8rem;
    line-height: 1;
    padding: 1px;
}

/* Green and amber are the two hues this page otherwise reserves for meal-time tints, which is a real cost
   (see the backdrop note in MealPlans.razor.css). Spent deliberately: these appear only while a week is
   being approved and for the moment after, they sit inside the chip rather than on the cell, and status
   that cannot be told apart at a glance is not status. Both are backed by an icon shape and a
   visually-hidden label, so neither depends on colour alone. */
.mp-chip-state-ready[b-1qx6kga15z] {
    color: var(--bs-success, #198754);
}

.mp-chip-state-failed[b-1qx6kga15z] {
    color: var(--bs-warning-text-emphasis, #997404);
}

/* The meal being worked on, sized to the icons it stands in for rather than to Bootstrap's own small
   spinner: .spinner-border-sm is 1rem square against a 0.8rem glyph, and the corner visibly grew and shrank
   as each meal was picked up and ticked off.

   Set through Bootstrap's own --bs-spinner-* variables rather than by overriding the width, height and
   border it resolves from them. That is the hook .spinner-border-sm itself uses, so this reads as another
   size of the same control instead of a rule fighting it, and 1em tracks the font-size .mp-chip-state
   above already decided rather than repeating 0.8rem in a second place.

   The +2px is the padding. Everything here is border-box, so a bare 1em would put .mp-chip-state's 1px
   inside the ring, while the <i> siblings are auto-width and add theirs outside a 1em glyph: measured, that
   was a 13px spinner against 15px icons, and the corner still stepped 2px each time a meal ticked over —
   the exact thing sizing it by hand was meant to stop.

   0.2em is .spinner-border-sm's own border, kept in proportion rather than thinned. Bootstrap's default
   0.25em is heavy enough at this diameter to close the hole up into a smudge, but 0.15em resolves to
   1.92px, which Chromium floors to a single pixel — a hairline arc on a 15px circle, read from arm's length
   on a fridge door.

   Muted rather than tinted, and that is the point of it: green and amber are the two outcomes, and this is
   neither yet. It carries meaning by moving, which is also why it is the one state with no colour to fall
   back on and why the label beside it is not optional. Bootstrap slows the animation to a crawl under
   prefers-reduced-motion rather than stopping it, so it still reads as in-flight for anyone who asked for
   less movement. */
.mp-chip-state-working[b-1qx6kga15z] {
    --bs-spinner-width: calc(1em + 2px);
    --bs-spinner-height: calc(1em + 2px);
    --bs-spinner-border-width: 0.2em;
    color: var(--bs-secondary-color, #6c757d);
}
/* /Components/Controls/FoodPreferencesEditor.razor.rz.scp.css */
/* --touch-min is opted into per surface, not inherited, so a new one that does not restate it renders
   its controls below the floor. This panel is typed into on the fridge, where the add box and its
   button are the two things a finger has to land on. */
.food-preferences .form-control[b-r3v3p123dg],
.food-preferences .btn[b-r3v3p123dg] {
    min-height: var(--touch-min);
}
/* /Components/Controls/Loader.razor.rz.scp.css */
/* /Components/Controls/MealChip.razor.rz.scp.css */
/* Styles for the meal plan chip (AB#879).

   These moved here from MealPlans.razor.css when the chip became a component. Blazor's scoped CSS keys
   on the component that *renders* an element, so once MealChip owned this markup, the page's stylesheet
   stopped matching any of it. Cell-level drag states (.mp-cell-valid, .mp-cell-armed) stay with the page,
   which still renders the cells.

   Everything here stays inside the fridge's ~M94 budget: no :has(), container queries, CSS nesting,
   @layer, or view transitions. The fridge is a target for this feature, so a modern selector would break
   the one device the drag was proven on. */

.mp-chip[b-v69c3eyjv4] {
    display: flex;
    align-items: stretch;
    gap: 4px;
    padding: 2px;
    border-radius: 4px;
    font-size: 0.78rem;
    line-height: 1.3;
    cursor: grab;
    /* touch-action is what makes the drag possible at all on touch: without it the browser claims the
       press for a scroll and no pointermove ever reaches us. The selection properties stop a long press
       raising Samsung Internet's selection magnifier, which otherwise makes press-and-hold unusable. */
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
    /* Alongside draggable="false" on the photo and the link. The attribute covers Chromium; Safari also
       honours this WebKit property, and between them no descendant can start a native drag that would
       hijack the pointer gesture. */
    -webkit-user-drag: none;
}

.mp-chip:active[b-v69c3eyjv4] {
    cursor: grabbing;
}

.mp-chip-recipe[b-v69c3eyjv4] {
    background: #d1ecf1;
    color: #0c5460;
}

.mp-chip-pantry[b-v69c3eyjv4] {
    background: #d4edda;
    color: #155724;
}

/* A product the household still has to buy (AB#1083). Amber rather than the pantry's green, because the
   green says "you have this" and the whole point of a product is that nobody has bought it yet. It is the
   one kind of meal on the week that is certain to be on the shopping list. */
.mp-chip-product[b-v69c3eyjv4] {
    background: #fff3cd;
    color: #664d03;
}

/* A meal nobody cooks (AB#1078). Neutral on purpose: the other three are food the app is doing something
   about, and this is the one that asks nothing of it. Grey is what says so without inventing a fifth
   meaning for a colour.

   Two steps darker than the grey this started as (#e9ecef), because that grey was chosen against a white
   page and these chips sit on a dark slot. At 91% lightness it came out the brightest thing in the cell,
   so the one chip meaning "nothing to do here" was drawing more attention than the three that mean there
   is. Neutral has to be read against what is behind it, not off a swatch. */
.mp-chip-notcooked[b-v69c3eyjv4] {
    background: #ced4da;
    color: #343a40;
}

/* The affordance that makes the drag discoverable without hovering. Decorative only: the whole chip is
   the drag surface, and the keyboard path is the kebab, so a focusable handle here would be a tab stop
   that does nothing. */
.mp-chip-grip[b-v69c3eyjv4] {
    display: flex;
    align-items: center;
    color: inherit;
    opacity: 0.45;
    font-size: 1rem;
    line-height: 1;
    flex-shrink: 0;
}

/* A square box, stated here rather than inherited (AB#886). This used to be align-self: stretch with
   height: auto, which handed the thumbnail's height to the flex line, i.e. to whatever else the chip
   happened to contain. Every chip then cropped its photo to a slightly different shape and a row read as
   ragged. align-self: center opts the image out of .mp-chip's stretch so aspect-ratio decides the height;
   leave the stretch in and it would win and we would be back to a derived box. aspect-ratio is M88, well
   inside the fridge's ~M94 budget. */
.mp-chip-thumb[b-v69c3eyjv4] {
    align-self: center;
    width: 48px;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: 2px;
    flex-shrink: 0;
}

/* Holds everything beside the thumbnail. Since the three action buttons became one kebab, the name and
   the actions share a single row and the wrapping two-line layout this used to need is gone. */
.mp-chip-body[b-v69c3eyjv4] {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 2px;
}

/* One kebab instead of three buttons, so the name and the actions finally fit on a single line even in
   a narrow portrait column. This is what retires the wrap described in AB#884: that issue was three
   48px targets not fitting beside a meal name, and there is only one target now. */
.mp-chip-name[b-v69c3eyjv4] {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mp-chip-link[b-v69c3eyjv4] {
    color: inherit;
    text-decoration: none;
}

.mp-chip-link:hover[b-v69c3eyjv4] {
    text-decoration: underline;
}

.mp-chip-menu-wrap[b-v69c3eyjv4] {
    position: relative;
    flex-shrink: 0;
}

.mp-chip-btn[b-v69c3eyjv4] {
    background: none;
    border: none;
    padding: 0;
    line-height: 1;
    cursor: pointer;
    opacity: 0.6;
    color: inherit;
    min-width: 48px;
    min-height: 48px;
    font-size: 1.15rem;
    flex-shrink: 0;
}

.mp-chip-btn:hover[b-v69c3eyjv4] {
    opacity: 1;
}

/* ── Kebab menu ──
   Fixed, not absolute. The menu sits inside .mp-grid, which sets overflow-x: auto so the week can scroll
   sideways; CSS computes the other axis to auto too, making the grid a scroll container that clipped an
   absolutely-positioned menu at its bottom edge. Fixed escapes every clipping ancestor.

   Coordinates come from anchoredMenu.js, which measures the kebab and the menu and flips or clamps as
   needed. It starts hidden at 0,0 so it cannot flash in the corner before it is placed. */
.mp-chip-menu[b-v69c3eyjv4] {
    position: fixed;
    top: 0;
    left: 0;
    visibility: hidden;
    z-index: 1090;
    min-width: 160px;
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
    border: 1px solid var(--bs-border-color);
    border-radius: 6px;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
    padding: 4px;
    display: flex;
    flex-direction: column;
}

.mp-chip-menu-item[b-v69c3eyjv4] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    min-height: 48px;
    padding: 0 0.75rem;
    background: none;
    border: none;
    border-radius: 4px;
    color: inherit;
    font-size: 0.85rem;
    text-align: left;
    cursor: pointer;
}

.mp-chip-menu-item:hover[b-v69c3eyjv4],
.mp-chip-menu-item:focus-visible[b-v69c3eyjv4] {
    background: var(--bs-secondary-bg);
}

.mp-chip-menu-item-danger[b-v69c3eyjv4] {
    color: var(--bs-danger-text-emphasis);
}

/* Catches the tap that dismisses the menu. Transparent rather than dimmed: this is a small chip menu,
   not a modal, and darkening the whole week to close it would be heavy-handed. */
.mp-chip-menu-backdrop[b-v69c3eyjv4] {
    position: fixed;
    inset: 0;
    z-index: 10;
    background: transparent;
    border: none;
    padding: 0;
    cursor: default;
}

/* ── Drag states ── */

/* The gap left behind, so it is clear where the chip returns to if the drag is abandoned. */
.mp-chip-dragging[b-v69c3eyjv4] {
    opacity: 0.35;
    filter: grayscale(1);
}

/* The lifted chip: elevation, scale, and reduced opacity together, so it reads as picked up rather than
   merely highlighted. Applied to a clone that mealPlanDrag.js appends to <body>; because the clone comes
   from this component's markup it carries this stylesheet's scope attribute, which is what lets a scoped
   rule style an element sitting outside the component's own tree.

   Its wrapper does the follow-the-pointer translate with no transition. The transition here is only for
   the lift itself, or the chip would ease along behind the finger on every move. */
.mp-chip-preview[b-v69c3eyjv4] {
    pointer-events: none;
    opacity: 0.9;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
    cursor: grabbing;
    transition: transform 140ms ease-out;
}

.mp-chip-settle[b-v69c3eyjv4] {
    animation: mp-settle-b-v69c3eyjv4 200ms ease-out;
}

@keyframes mp-settle-b-v69c3eyjv4 {
    from { transform: scale(1.08); opacity: 0.7; }
    to   { transform: scale(1);    opacity: 1; }
}

/* State still changes, it just stops moving. */
@media (prefers-reduced-motion: reduce) {
    .mp-chip-preview[b-v69c3eyjv4] {
        transition: none;
    }

    .mp-chip-settle[b-v69c3eyjv4] {
        animation: none;
    }
}
/* /Components/Controls/NavGroup.razor.rz.scp.css */
/* Group-header + submenu styling for NavGroup (AB#1179). The group header markup lives in this
   component, so the scoped rules in NavMenu.razor.css don't reach it — these mirror the matching
   .nav-item / .nav-link / .nav-menu-icon / .nav-submenu rules there so a group renders identically
   to the flat nav items around it. Child links inside the submenu keep NavMenu's scope and styling. */

.nav-item[b-ss8ia6f2p0] {
    font-size: 0.9rem;
    padding-bottom: 0.5rem;
}

.nav-item .nav-link[b-ss8ia6f2p0] {
    color: var(--color-text-secondary);
    background: none;
    border: none;
    border-radius: 4px;
    height: 3rem;
    display: flex;
    align-items: center;
    line-height: 3rem;
    width: 100%;
    white-space: nowrap;
}

.nav-item .nav-link:hover[b-ss8ia6f2p0] {
    background-color: rgba(0, 212, 255, 0.06);
    color: var(--color-text-primary);
}

.nav-menu-icon[b-ss8ia6f2p0] {
    color: white;
    font-size: 1.5rem;
    margin-right: 10px;
}

.nav-submenu[b-ss8ia6f2p0] {
    border-left: 2px solid var(--color-border);
    margin-left: 1.75rem;
    overflow: hidden;
    animation: submenu-open-b-ss8ia6f2p0 0.2s ease-out;
}

@keyframes submenu-open-b-ss8ia6f2p0 {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* Pinned desktop sidebar (AB#928): the group header is 44px like the flat rows beside it. Mirrors the
   matching block in NavMenu.razor.css for the same reason the rules above are duplicated - this markup
   carries NavGroup's scope, so NavMenu's sheet cannot reach it. */
@media (min-width: 992px) {
    /* !important for the same reason as in NavMenu.razor.css: the markup's px-3 is a Bootstrap
       utility, and those are !important. */
    .nav-item[b-ss8ia6f2p0] {
        padding-left: 0 !important;
        padding-right: 0 !important;
        padding-bottom: 0.15rem;
    }

    .nav-item .nav-link[b-ss8ia6f2p0] {
        height: 44px;
        line-height: 44px;
        padding: 0 0.6rem;
        font-size: 0.9375rem;
    }

    .nav-menu-icon[b-ss8ia6f2p0] {
        font-size: 1.05rem;
        width: 1.25rem;
        text-align: center;
        flex-shrink: 0;
        margin-right: 0.6rem;
    }

    .nav-submenu[b-ss8ia6f2p0] {
        margin-left: 1.05rem;
    }
}
/* /Components/Controls/NibbleAvatar.razor.rz.scp.css */
/* AB#1033. Nibble's motion. Blinks and eye movement only, deliberately: anything that moves the whole
   character (a bob, a bounce, a spin) turns him into a busy indicator, and he sits on screen all the
   time rather than only while something is happening.

   Everything animated here is a transform on a group inside the SVG, so it composites and never asks
   the page for a layout pass. The eyes are two nested groups on purpose: the outer one carries the
   glance and the inner one the blink, because both are transforms and one element cannot run two
   animations on the same property. */

.nibble[b-vxrimd7osy] {
    display: inline-block;
    inline-size: var(--nibble-size, 1.5em);
    block-size: var(--nibble-size, 1.5em);
    flex: 0 0 auto;
    line-height: 0;
}

.nibble svg[b-vxrimd7osy] {
    display: block;
    inline-size: 100%;
    block-size: 100%;
    /* The bite and the coloured edge sit right at the viewBox border; without this the fringe is
       clipped on whichever side the container rounds down. */
    overflow: visible;
}

/* fill-box rather than the default view-box: the origin has to be the middle of each eye, not the
   middle of the 2048-unit canvas, or a blink squashes the eyes toward the character's chin. */
.nibble-eye[b-vxrimd7osy] {
    transform-box: fill-box;
    transform-origin: center;
    animation: nibble-glance-b-vxrimd7osy 7.4s ease-in-out infinite;
}

.nibble-lid[b-vxrimd7osy] {
    transform-box: fill-box;
    transform-origin: center;
    animation: nibble-blink-b-vxrimd7osy 5.6s infinite;
}

/* The hold at each stop is what makes this read as looking at something rather than sliding. Timings
   are deliberately coprime with the blink so the two never settle into a visible pattern. */
@keyframes nibble-glance-b-vxrimd7osy {
    0%, 18% { transform: translate(0, 0); }
    24%, 38% { transform: translate(-46px, 6px); }
    45%, 63% { transform: translate(0, 0); }
    69%, 82% { transform: translate(42px, 4px); }
    89%, 100% { transform: translate(0, 0); }
}

/* Two blinks close together, not one. A single evenly spaced blink is a metronome, and the eye reads
   a metronome as a loading indicator. */
@keyframes nibble-blink-b-vxrimd7osy {
    0%, 90.5% { transform: scaleY(1); }
    92% { transform: scaleY(0.06); }
    93.5%, 95.5% { transform: scaleY(1); }
    96.8% { transform: scaleY(0.06); }
    98.2%, 100% { transform: scaleY(1); }
}

/* Thinking: looking away and up, which is where people look when they are working something out,
   and blinking faster because that is what concentration does. */
.nibble.is-thinking .nibble-eye[b-vxrimd7osy] {
    animation-name: nibble-think-b-vxrimd7osy;
    animation-duration: 2.9s;
}

.nibble.is-thinking .nibble-lid[b-vxrimd7osy] {
    animation-duration: 3.1s;
}

@keyframes nibble-think-b-vxrimd7osy {
    0%, 12% { transform: translate(-38px, -30px); }
    30%, 46% { transform: translate(30px, -34px); }
    64%, 78% { transform: translate(-20px, -18px); }
    92%, 100% { transform: translate(-38px, -30px); }
}

/* Required by the accessibility standard, and the right default regardless: a character that blinks
   forever in the corner of the screen is exactly the kind of motion this setting exists to stop.

   !important is load-bearing here, not laziness. A media query adds no specificity, so this one-class
   selector loses to `.nibble.is-thinking .nibble-eye` above, which is three classes and sets
   animation-name outright. Without it the idle state stopped correctly and the thinking state kept
   animating, so a reader who had asked for less motion got moving eyes for the whole of every turn,
   which is exactly when they were looking at the panel. */
@media (prefers-reduced-motion: reduce) {
    .nibble-eye[b-vxrimd7osy],
    .nibble-lid[b-vxrimd7osy] {
        animation: none !important;
    }
}
/* /Components/Controls/NotificationInboxPanel.razor.rz.scp.css */
.notification-backdrop[b-zocqo4grts] {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.4);
    z-index: 1059;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease-out;
}

.notification-backdrop.open[b-zocqo4grts] {
    opacity: 1;
    pointer-events: auto;
}

.notification-panel[b-zocqo4grts] {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(400px, 100%);
    background: #fff;
    z-index: 1060;
    transform: translateX(100%);
    transition: transform 0.25s ease-out;
    overflow: hidden;
    box-shadow: -4px 0 20px rgba(0, 0, 0, 0.2);
    display: flex;
    flex-direction: column;
}

.notification-panel.open[b-zocqo4grts] {
    transform: translateX(0);
}

.notification-panel-header[b-zocqo4grts] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.875rem 1rem;
    border-bottom: 1px solid rgba(0, 0, 0, 0.1);
    flex-shrink: 0;
    background: #fff;
    gap: 0.5rem;
}

.notification-panel-title[b-zocqo4grts] {
    font-size: 1.1rem;
    font-weight: 600;
    margin: 0;
    flex: 1;
}

.notification-panel-body[b-zocqo4grts] {
    flex: 1;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

.notification-empty[b-zocqo4grts] {
    padding: 2.5rem 1rem;
    text-align: center;
    color: #888;
    font-size: 0.9rem;
}

.notification-item[b-zocqo4grts] {
    display: flex;
    align-items: flex-start;
    padding: 0.875rem 1rem;
    border-bottom: 1px solid rgba(0, 0, 0, 0.07);
    cursor: pointer;
    gap: 0.5rem;
    min-height: 48px;
    border-left: 3px solid transparent;
}

.notification-item:hover[b-zocqo4grts] {
    background: rgba(0, 0, 0, 0.03);
}

.notification-item.unread[b-zocqo4grts] {
    background: rgba(13, 110, 253, 0.05);
    border-left-color: #0d6efd;
}

.notification-item.unread .notification-item-title[b-zocqo4grts] {
    font-weight: 600;
}

.notification-item-content[b-zocqo4grts] {
    flex: 1;
    min-width: 0;
}

.notification-item-title[b-zocqo4grts] {
    font-size: 0.875rem;
    line-height: 1.3;
    margin-bottom: 0.2rem;
    word-break: break-word;
}

.notification-item-message[b-zocqo4grts] {
    font-size: 0.8rem;
    color: #555;
    line-height: 1.3;
    margin-bottom: 0.25rem;
    word-break: break-word;
}

.notification-item-time[b-zocqo4grts] {
    font-size: 0.75rem;
    color: #999;
}

.notification-delete-btn[b-zocqo4grts] {
    flex-shrink: 0;
    color: #aaa;
    min-width: 36px;
    min-height: 36px;
    align-self: center;
}

.notification-delete-btn:hover[b-zocqo4grts] {
    color: #dc3545;
}
/* /Components/Controls/RecipeCarousel.razor.rz.scp.css */
/* ── Outer container ───────────────────────────────────────────── */

.carousel-container[b-qacm6e40e1] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.25rem;
    margin-top: 100px;
}

/* ── 3D wheel scene ────────────────────────────────────────────── */

.carousel-scene[b-qacm6e40e1] {
    /* perspective set dynamically by JS based on radius */
    perspective-origin: 50% 40%;
    width: 100%;
    height: 460px;
    position: relative;
    /* z-index:0 creates a stacking context that boxes in the 3D rendering
       so side cards can bleed freely without painting over the controls below */
    z-index: 0;
}

/* Centering wrapper — keeps the wheel in the middle of the scene.
   top matches perspective-origin Y (40%) so the perspective projection
   has no vertical shift and the center card stays within the scene height.
   JS only ever touches .carousel-wheel's transform, not this. */
.carousel-wheel-wrap[b-qacm6e40e1] {
    position: absolute;
    top: 40%;
    left: 50%;
    transform: translateX(-50%) translateY(-50%);
    transform-style: preserve-3d;
}

/* The cylinder that rotates — JS manages the rotateY transform */
.carousel-wheel[b-qacm6e40e1] {
    width: 320px;
    height: 380px;
    transform-style: preserve-3d;
    transition: transform 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* ── Individual cards ──────────────────────────────────────────── */

.carousel-card[b-qacm6e40e1] {
    position: absolute;
    width: 320px;
    height: 380px;
    transform-style: preserve-3d;
    /* JS sets: transform: rotateY(Xdeg) translateZ(Rpx) */
}

.carousel-card-face[b-qacm6e40e1] {
    position: absolute;
    inset: 0;
    border-radius: 1rem;
    overflow: hidden;
    background-color: var(--color-bg-elevated, #2A3050);
    background-image: var(--card-bg);
    background-size: cover;
    background-position: center;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
    backface-visibility: hidden;
}

.carousel-card-face--no-photo[b-qacm6e40e1] {
    background-image: linear-gradient(160deg, #3a3a3a 0%, #2F6B3E 100%);
}

.carousel-card-back[b-qacm6e40e1] {
    position: absolute;
    inset: 0;
    border-radius: 1rem;
    background: var(--color-bg-elevated, #2A3050);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
    backface-visibility: hidden;
    transform: rotateY(180deg);
    pointer-events: none; /* decorative — must not intercept taps */
}

/* In Chromium M94 (fridge), transform-style: preserve-3d containers claim a
   flat 2D hit area that swallows taps before they reach descendants.
   Disable pointer events on all 3D containers and re-enable only on the
   actual interactive elements inside the active card. */
.carousel-wheel-wrap[b-qacm6e40e1],
.carousel-wheel[b-qacm6e40e1],
.carousel-card[b-qacm6e40e1],
.carousel-card-face[b-qacm6e40e1] {
    pointer-events: none;
}

.recipe-card-actions a[b-qacm6e40e1],
.recipe-card-actions button[b-qacm6e40e1],
.day-btn[b-qacm6e40e1] {
    pointer-events: auto;
}

/* ── Overlay gradient ──────────────────────────────────────────── */

.recipe-card-overlay[b-qacm6e40e1] {
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.2) 55%, transparent 100%);
    border-radius: inherit;
    transition: opacity 0.4s;
    pointer-events: none; /* decorative gradient — must not intercept taps */
}

/* Darken non-active cards so the active one pops */
.carousel-card:not(.active) .recipe-card-overlay[b-qacm6e40e1] {
    background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 100%);
}

/* While a shuffle spin is in flight (class toggled by carousel.js), hide the
   recipe info and drop the active card to the same dark overlay the other cards
   use, so every card looks uniform and nothing but the info rides the spin. The
   info pops back on the landed card when the spin ends and the class is removed. */
.carousel-spinning .recipe-card-content[b-qacm6e40e1] {
    display: none;
}

.carousel-spinning .carousel-card.active .recipe-card-overlay[b-qacm6e40e1] {
    background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 100%);
}

/* ── Card content ──────────────────────────────────────────────── */

.recipe-card-content[b-qacm6e40e1] {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1;
    padding: 0.9rem 1rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.recipe-card-body[b-qacm6e40e1] {
    color: #fff;
}

.recipe-card-title[b-qacm6e40e1] {
    font-size: 1rem;
    font-weight: 700;
    margin: 0 0 0.2rem;
    line-height: 1.25;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}

/* ── Active-only content ───────────────────────────────────────── */

.recipe-card-meta[b-qacm6e40e1] {
    font-size: 0.75rem;
    color: rgba(255, 255, 255, 0.85);
    margin: 0;
    display: none;
    gap: 0.75rem;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.recipe-card-nutrition[b-qacm6e40e1] {
    display: none;
    flex-wrap: wrap;
    gap: 0.25rem 0.5rem;
    margin: 0;
}

.nutrition-item[b-qacm6e40e1] {
    font-size: 0.7rem;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.9);
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid rgba(255, 255, 255, 0.15);
    border-radius: 0.3rem;
    padding: 0.15rem 0.4rem;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
    white-space: nowrap;
}

.recipe-card-actions[b-qacm6e40e1] {
    display: none;
    gap: 0.4rem;
}

.carousel-card.active .recipe-card-meta[b-qacm6e40e1] {
    display: flex;
}

.carousel-card.active .recipe-card-actions[b-qacm6e40e1] {
    display: flex;
}

.carousel-card.active .recipe-card-nutrition[b-qacm6e40e1] {
    display: flex;
}

/* ── Action buttons ────────────────────────────────────────────── */

.recipe-btn-view[b-qacm6e40e1] {
    flex: 1;
    font-weight: 600;
    font-size: 0.8rem;
}

.recipe-btn-plan[b-qacm6e40e1] {
    font-weight: 600;
    font-size: 0.8rem;
    transition: background-color 0.15s;
}

.recipe-btn-plan.active[b-qacm6e40e1] {
    background-color: var(--atebit-accent, #E2B43A);
    border-color: var(--atebit-accent, #E2B43A);
}

/* ── Day picker ────────────────────────────────────────────────── */

.day-picker[b-qacm6e40e1] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.2rem;
    align-items: center;
    justify-content: center;
}

.day-btn[b-qacm6e40e1] {
    background: rgba(255, 255, 255, 0.15);
    border: 1px solid rgba(255, 255, 255, 0.3);
    border-radius: 0.4rem;
    color: #fff;
    font-size: 0.65rem;
    font-weight: 700;
    padding: 0.3rem 0.4rem;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
    text-align: center;
    letter-spacing: 0.02em;
}

.day-btn:hover:not(:disabled)[b-qacm6e40e1] {
    background: var(--atebit-accent, #E2B43A);
    border-color: var(--atebit-accent, #E2B43A);
    color: #1F3326;
}

.day-btn:disabled[b-qacm6e40e1] {
    opacity: 0.5;
    cursor: not-allowed;
}

.day-picker-label[b-qacm6e40e1] {
    font-size: 0.65rem;
    font-weight: 700;
    color: rgba(255, 255, 255, 0.7);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    margin-right: 0.1rem;
}

.day-btn--back[b-qacm6e40e1] {
    background: transparent;
    border-color: rgba(255, 255, 255, 0.2);
    margin-left: auto;
}

/* ── Nav controls ──────────────────────────────────────────────── */

.carousel-controls[b-qacm6e40e1] {
    position: relative;
    z-index: 1;
    display: flex;
    gap: 0.75rem;
}

.carousel-nav-btn[b-qacm6e40e1] {
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 50%;
    border: 2px solid var(--atebit-primary, #2F6B3E);
    background: var(--atebit-bg, #F7F4EC);
    color: var(--atebit-primary, #2F6B3E);
    font-size: 1.1rem;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

.carousel-nav-btn:hover[b-qacm6e40e1] {
    background: var(--atebit-primary, #2F6B3E);
    color: #fff;
}

/* ── Empty state ───────────────────────────────────────────────── */

.empty-state[b-qacm6e40e1] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    padding: 4rem 2rem;
    color: var(--atebit-muted, #6c757d);
    text-align: center;
}

.empty-icon[b-qacm6e40e1] {
    font-size: 3rem;
    opacity: 0.4;
}
/* /Components/Controls/RecipeGrid.razor.rz.scp.css */
.recipe-grid[b-inc7i7uvlu] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 1.25rem;
}

.rg-card[b-inc7i7uvlu] {
    position: relative;
    border-radius: 1rem;
    overflow: hidden;
    min-height: 260px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    background-image: var(--card-bg);
    background-size: cover;
    background-position: center;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
    transition: transform 0.15s, box-shadow 0.15s;
}

.rg-card:hover[b-inc7i7uvlu] {
    transform: translateY(-3px);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
}

.rg-card--no-photo[b-inc7i7uvlu] {
    background-image: linear-gradient(160deg, #3a3a3a 0%, #2F6B3E 100%);
}

.rg-card-overlay[b-inc7i7uvlu] {
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.78) 0%, rgba(0, 0, 0, 0.18) 55%, transparent 100%);
    border-radius: inherit;
}

.rg-card--no-photo .rg-card-overlay[b-inc7i7uvlu] {
    background: linear-gradient(to top, rgba(0, 0, 0, 0.5) 0%, transparent 60%);
}

.rg-card-content[b-inc7i7uvlu] {
    position: relative;
    z-index: 1;
    padding: 0.9rem 1rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.rg-card-body[b-inc7i7uvlu] { color: #fff; }

.rg-card-title[b-inc7i7uvlu] {
    font-size: 1rem;
    font-weight: 700;
    margin: 0 0 0.2rem;
    line-height: 1.25;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}

.rg-card-meta[b-inc7i7uvlu] {
    font-size: 0.75rem;
    color: rgba(255, 255, 255, 0.82);
    margin: 0;
    display: flex;
    gap: 0.75rem;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.rg-card-actions[b-inc7i7uvlu] {
    display: flex;
    gap: 0.4rem;
}

.rg-btn-view[b-inc7i7uvlu] {
    flex: 1;
    font-weight: 600;
    font-size: 0.8rem;
}

.rg-btn-plan[b-inc7i7uvlu] {
    font-weight: 600;
    font-size: 0.8rem;
    transition: background-color 0.15s;
}

.rg-btn-plan.active[b-inc7i7uvlu] {
    background-color: var(--atebit-accent, #E2B43A);
    border-color: var(--atebit-accent, #E2B43A);
}

/* ── Day picker ────────────────────────────────────────────────── */

.rg-day-picker[b-inc7i7uvlu] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.2rem;
    align-items: center;
    justify-content: center;
}

.rg-day-btn[b-inc7i7uvlu] {
    background: rgba(255, 255, 255, 0.15);
    border: 1px solid rgba(255, 255, 255, 0.3);
    border-radius: 0.4rem;
    color: #fff;
    font-size: 0.65rem;
    font-weight: 700;
    padding: 0.3rem 0.4rem;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
    text-align: center;
    letter-spacing: 0.02em;
}

.rg-day-btn:hover:not(:disabled)[b-inc7i7uvlu] {
    background: var(--atebit-accent, #E2B43A);
    border-color: var(--atebit-accent, #E2B43A);
    color: #1F3326;
}

.rg-day-btn:disabled[b-inc7i7uvlu] {
    opacity: 0.5;
    cursor: not-allowed;
}

.rg-day-picker-label[b-inc7i7uvlu] {
    font-size: 0.65rem;
    font-weight: 700;
    color: rgba(255, 255, 255, 0.7);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    margin-right: 0.1rem;
}

.rg-day-btn--back[b-inc7i7uvlu] {
    background: transparent;
    border-color: rgba(255, 255, 255, 0.2);
    margin-left: auto;
}

/* ── Empty state ───────────────────────────────────────────────── */

.rg-empty-state[b-inc7i7uvlu] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    padding: 4rem 2rem;
    color: var(--atebit-muted, #6c757d);
    text-align: center;
}

.rg-empty-icon[b-inc7i7uvlu] {
    font-size: 3rem;
    opacity: 0.4;
}
/* /Components/Controls/RowActionMenu.razor.rz.scp.css */
/* The row overflow menu's shell (AB#933). The items style themselves in RowActionMenuItem.razor.css:
   Blazor's scoped CSS keys on the component that *renders* an element, so a descendant selector written
   here would never match markup another component emits.

   Everything here stays inside the fridge's ~M94 budget: no :has(), container queries, CSS nesting,
   @layer, or view transitions. */

.row-menu-wrap[b-en25jpyzuk] {
    position: relative;
    display: inline-flex;
    flex-shrink: 0;
}

/* Fixed, not absolute. The menu sits inside .table-container, which sets overflow-y: auto so a long list
   can scroll; that makes it a scroll container which clips an absolutely-positioned menu at its edge, and
   a row near the bottom of the pantry opened one that was cut in half.

   Coordinates come from anchoredMenu.js, which measures the kebab and the menu and flips or clamps as
   needed. It starts hidden at 0,0 so it cannot flash in the corner before it is placed. */
.row-menu[b-en25jpyzuk] {
    position: fixed;
    top: 0;
    left: 0;
    visibility: hidden;
    z-index: 1090;
    min-width: 12rem;
    background: var(--color-bg-elevated);
    color: var(--color-text-primary);
    border: 1px solid var(--color-border);
    border-radius: 6px;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);
    padding: 4px;
    display: flex;
    flex-direction: column;
}

/* Catches the tap that dismisses the menu, and with it the AC that only one row menu is open at a time:
   while this is up, the first tap anywhere else lands here and closes the menu rather than opening a
   second one. Transparent rather than dimmed — this is a small row menu, not a modal, and darkening the
   whole list to close it would be heavy-handed. */
.row-menu-backdrop[b-en25jpyzuk] {
    position: fixed;
    inset: 0;
    z-index: 10;
    background: transparent;
    border: none;
    padding: 0;
    cursor: default;
}
/* /Components/Controls/RowActionMenuItem.razor.rz.scp.css */
/* One row-menu action (AB#933). Separate from RowActionMenu.razor.css because Blazor's scoped CSS keys on
   the component that renders the element, and these are rendered here.

   No :has(), container queries, CSS nesting, @layer or view transitions: the fridge runs ~M94. */

.row-menu-item[b-f4wqhoqoro] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    /* The touch floor applies inside the menu as much as it does to the buttons that used to be in the
       row. --touch-min is opted into per surface rather than inherited, so it is restated here. */
    min-height: var(--touch-min);
    padding: 0 0.75rem;
    background: none;
    border: none;
    border-radius: 4px;
    color: var(--color-text-primary);
    font-size: 0.9rem;
    text-align: left;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
}

.row-menu-item:hover[b-f4wqhoqoro],
.row-menu-item:focus-visible[b-f4wqhoqoro] {
    background: var(--color-bg-subtle);
    color: var(--color-text-primary);
    text-decoration: none;
}

/* The glyph keeps the meaning it carried as an inline button, so the two readings of the same action stay
   recognisably the same thing. Fixed width so the labels line up into a column. */
.row-menu-item > .bi[b-f4wqhoqoro] {
    flex-shrink: 0;
    width: 1.25rem;
    text-align: center;
    color: var(--color-text-secondary);
}

.row-menu-item-danger[b-f4wqhoqoro],
.row-menu-item-danger > .bi[b-f4wqhoqoro] {
    color: var(--color-danger);
}

.row-menu-item-danger:hover[b-f4wqhoqoro],
.row-menu-item-danger:focus-visible[b-f4wqhoqoro] {
    background: rgba(255, 77, 106, 0.08);
    color: var(--color-danger);
}

/* Muted rather than hidden: the point of disabling instead of dropping is that the action still tells you
   it exists. Keyed on aria-disabled, not :disabled, because the item keeps the disabled attribute off so
   it stays in the tab order — see the note in RowActionMenuItem.razor. Last in the file so it beats the
   hover rules, which still match on a disabled item.

   cursor: default so it does not promise a press it will not take. No pointer-events: none, or the title
   explaining why it is unavailable would never appear. */
.row-menu-item[aria-disabled="true"][b-f4wqhoqoro],
.row-menu-item[aria-disabled="true"] > .bi[b-f4wqhoqoro] {
    color: var(--color-text-muted);
}

.row-menu-item[aria-disabled="true"][b-f4wqhoqoro] {
    background: none;
    cursor: default;
}
/* /Components/Controls/ToastLink.razor.rz.scp.css */
/* The call to action on a toast that has somewhere to go (AB#913).

   These sit on Blazored's toast, which is a white card with near-black text - the only light surface in
   the app - so the colours here are the toast's own, not the dark theme's tokens. Anything pulled from
   atebit-theme.css was drawn for #1A1F2E and disappears against #fff.

   Fridge budget (Samsung Internet 10, ~Chromium M94): no :has(), @container, CSS nesting, @layer or view
   transitions below. */

.toast-link[b-0ckt3c73mf] {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.toast-link-message[b-0ckt3c73mf] {
    overflow-wrap: break-word;
    word-break: break-word;
}

/* A bordered button rather than a coloured word: the fridge has no hover, so the affordance has to be
   visible at rest, and it must not rest on colour alone. The border and the arrow both say "this is a
   control" without needing either. Full width and 48px tall to clear the touch floor - the toast is
   20rem wide, so there is room for the whole row to be the target. */
.toast-link-cta[b-0ckt3c73mf] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    width: 100%;
    min-height: 48px;
    padding: 0 0.75rem;
    background-color: rgba(0, 0, 0, 0.04);
    border: 1px solid rgba(0, 0, 0, 0.35);
    border-radius: 0.25rem;
    color: inherit;
    font: inherit;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}

.toast-link-cta:hover[b-0ckt3c73mf] {
    background-color: rgba(0, 0, 0, 0.08);
}

/* Stated rather than left to the UA default, which draws a thin ring the fridge's own focus styling
   does not always render clearly on a light card. */
.toast-link-cta:focus-visible[b-0ckt3c73mf] {
    outline: 2px solid #1D1D1B;
    outline-offset: 2px;
}

.toast-link-cta-text[b-0ckt3c73mf] {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* /Components/Controls/WaitlistSignupForm.razor.rz.scp.css */
/* Waitlist signup form (AB#809) — form styling on top of the shared .landing look in app.css,
   mirroring the Contact page's form. Fridge-safe (Chromium M94): no :has(), @container, @layer,
   subgrid, or CSS nesting. The .landing custom properties (--l-*) inherit down from PublicShell. */

.waitlist-form[b-ge0hkm6jet] {
    text-align: left;
    max-width: 480px;
    margin: 1.5rem auto 0;
}

.waitlist-field[b-ge0hkm6jet] {
    margin-bottom: 1.25rem;
}

.waitlist-field label[b-ge0hkm6jet] {
    display: block;
    margin-bottom: 0.4rem;
    font-weight: 600;
    font-size: 0.95rem;
    color: var(--l-text-strong);
}

.waitlist-field .optional[b-ge0hkm6jet] {
    font-weight: 400;
    color: var(--l-muted);
    font-size: 0.85rem;
}

.waitlist-field input[b-ge0hkm6jet] {
    display: block;
    width: 100%;
    min-height: 48px;
    padding: 0.7rem 0.9rem;
    font-family: inherit;
    font-size: 1rem;
    line-height: 1.5;
    color: var(--l-text-strong);
    background: var(--l-card-2);
    border: 1px solid var(--l-border-strong);
    border-radius: 8px;
    transition: border-color 0.15s, box-shadow 0.15s;
    -webkit-appearance: none;
    appearance: none;
}

.waitlist-field input[b-ge0hkm6jet]::placeholder {
    color: var(--l-muted);
}

.waitlist-field input:focus[b-ge0hkm6jet] {
    outline: none;
    border-color: var(--l-cyan);
    box-shadow: 0 0 14px rgba(0, 212, 255, 0.25);
}

.waitlist-field input:disabled[b-ge0hkm6jet] {
    opacity: 0.6;
}

.waitlist-turnstile[b-ge0hkm6jet] {
    margin-bottom: 1.25rem;
}

.waitlist-actions[b-ge0hkm6jet] {
    margin-top: 1.6rem;
}

.waitlist-actions .btn[b-ge0hkm6jet] {
    min-width: 200px;
}

/* p.waitlist-consent, not just the class: it must reach the specificity of app.css's
   `.landing .panel p` (which zeroes top margins inside panels) so the breathing room
   below the button actually applies. Equal specificity + later stylesheet = this wins. */
p.waitlist-consent[b-ge0hkm6jet] {
    margin-top: 1.75rem;
    font-size: 0.85rem;
    color: var(--l-muted);
    text-align: center;
}

/* Honeypot: moved off-screen rather than display:none so naive bots still fill it in. */
.hp-field[b-ge0hkm6jet] {
    position: absolute;
    left: -9999px;
    top: auto;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.waitlist-success[b-ge0hkm6jet] {
    margin: 1.5rem auto 0;
    max-width: 480px;
    padding: 1.2rem 1.4rem;
    border: 1px solid var(--l-cyan);
    border-radius: 10px;
    background: rgba(0, 212, 255, 0.08);
    box-shadow: 0 0 18px rgba(0, 212, 255, 0.18);
}

.waitlist-success p[b-ge0hkm6jet] {
    margin-bottom: 0.4rem;
}

.waitlist-success p:last-child[b-ge0hkm6jet] {
    margin-bottom: 0;
}

.waitlist-error[b-ge0hkm6jet] {
    margin-bottom: 1.25rem;
    padding: 0.9rem 1.1rem;
    border: 1px solid #E85D5D;
    border-radius: 10px;
    background: rgba(232, 93, 93, 0.10);
    color: #F0A8A8;
    font-size: 0.95rem;
}

@media (max-width: 576px) {
    .waitlist-actions .btn[b-ge0hkm6jet] {
        width: 100%;
    }
}
/* /Components/Dialogs/Assistant/AssistantTurnDetail.razor.rz.scp.css */
/*
   Captured text: the user's message, each round's model output, tool arguments and results, and the
   rendered answer.

   These were <pre> blocks that scrolled sideways, which is the wrong axis for prose. A model's answer
   is one very long line, so a horizontal scrollbar showed a single row of it and hid the rest behind a
   drag. Wrapping puts the whole thing on screen as several rows and leaves only vertical scrolling,
   which is the direction the reader is already moving.

   pre-wrap rather than plain wrapping: the leading indentation and blank lines in a tool payload are
   part of reading it, so newlines and runs of spaces still have to survive. overflow-wrap: anywhere is
   what handles the one thing pre-wrap cannot break, an unbroken 300-character JSON string or URL, which
   would otherwise push the block wide again on its own.
*/
.transcript[b-zflaunqojq] {
    white-space: pre-wrap;
    overflow-wrap: anywhere;

    /* Long enough to read a paragraph in place, short enough that one enormous tool result cannot push
       the Close button off the bottom of the dialog. Past this it scrolls, vertically. */
    max-height: 22rem;
    overflow-y: auto;
}
/* /Components/Dialogs/Pantry/ReceiptReviewDialog.razor.rz.scp.css */
/* Design system rule 15, borrowed for the receipt review (spike, not yet a committed rule).

   The dialog was already a stack of cards, so the grouping was never the problem. The problem was
   inside each one: six inputs of the same weight across two rows, so thirty-eight lines read as a wall
   of boxes rather than as a list of groceries. What rule 15 contributes is the identity line (the name
   leading, one muted line of details under it, the numbers in columns down the right) and the reserved
   left rule.

   The depth is the pantry's, exactly: a recessed region, cards on it, fields inside them. Two earlier
   passes got this wrong in opposite directions. Making the fields transparent until hovered left a
   screen whose whole job is correcting the parser looking like static text. Lifting the card to
   --color-bg-elevated instead put the app's standard --color-bg-subtle inputs two steps below it, so
   every field punched a dark hole in the card. The fix for both is to leave the fields completely alone
   and let them look like every other input in every other dialog. */

/* The equivalent of .table-container on a list page: the region the cards sit on, one step *down* from
   the dialog so the cards can be one step up from it without any of them leaving the palette. */
.receipt-list[b-cgl1q7fse5] {
    max-height: 500px;
    overflow-y: auto;
    /* Setting overflow on one axis computes the other to auto, so this box clips. The padding is what
       keeps the cards' edges and their focus rings inside it. */
    padding: 0.5rem;
    background-color: var(--color-bg-base);
    border: 1px solid var(--color-border);
    border-radius: var(--bs-border-radius, 0.375rem);
}

.receipt-card[b-cgl1q7fse5] {
    padding: 0.35rem 0.5rem;
    background-color: var(--color-bg-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--bs-border-radius, 0.375rem);
    /* The accent rule as an inset shadow rather than a 3px border-left. A border-left overrides the 1px
       the shorthand just set on that side, so a card with no state to show lost its left edge entirely
       while keeping the other three. An inset shadow paints inside the border box: the border stays
       whole, the rule needs no transparent placeholder, and nothing shifts when it lights up. */
    box-shadow: inset 3px 0 0 0 transparent;
}

/* The pantry colours this rule for selection. Here every line arrives ticked, so a rule on the included
   ones would be thirty-eight of them saying nothing. It marks the exceptions instead: something already
   in the pantry, or something that cannot be saved as it stands. */
.receipt-card.is-dup[b-cgl1q7fse5] { box-shadow: inset 3px 0 0 0 var(--color-warning); }
.receipt-card.is-bad[b-cgl1q7fse5] { box-shadow: inset 3px 0 0 0 var(--color-danger); }
.receipt-card.is-excluded[b-cgl1q7fse5] { opacity: 0.45; }

/* Two rows, not one card of six equal boxes. The pantry can put its details on a muted second line
   because a pantry row is read; here every detail is a control on the 48px touch floor, so nesting them
   under the name bought nothing — the "quiet" line came out exactly as tall and as loud as the name
   above it. What actually separates the two lines is what is on them: the identity line carries the
   things you scan for (name, brand, how many, what it cost), and the second carries the measurement you
   only look at when the parser got it wrong. */
.receipt-head[b-cgl1q7fse5] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.receipt-id[b-cgl1q7fse5] {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.receipt-id .form-control[b-cgl1q7fse5] { flex: 1 1 auto; min-width: 0; }

/* Nothing here overrides the fields' colours. --color-bg-subtle on --color-bg-surface is what an input
   looks like everywhere else in the app, so it is what an input looks like here. */

/* Heights are a different matter. Rule 10 puts every dialog control on the 48px touch floor, and that
   is what defeated two earlier attempts at a hierarchy here: a "quiet" detail line came out exactly as
   tall as the name above it, so the card read as six equal boxes however they were arranged.
   Receipt import is a desktop job (drag an .eml in, or pull one from Gmail) and this dialog is not
   reached from the fridge, which is the same exemption rule 10 already grants the pinned desktop
   sidebar: a surface that never renders on the touch panel is not held to the touch panel's floor.
   The identity line keeps the full 48px. Only the detail line goes down, and going down is the whole
   point — it is what finally makes it read as detail. */
.receipt-meta .form-control[b-cgl1q7fse5],
.receipt-meta .form-select[b-cgl1q7fse5] {
    min-height: 2.25rem;
    padding-top: 0.15rem;
    padding-bottom: 0.15rem;
    font-size: 0.8125rem;
}

/* Indented to start under the name rather than under the checkbox, so the second line reads as
   belonging to the line above it instead of as a second row of the card. */
.receipt-meta[b-cgl1q7fse5] {
    display: flex;
    align-items: center;
    /* Wraps, because at the fridge's 720px it has to. Held on one line the row simply ran past the card
       and took the "Required" message with it, so the one thing telling you why a line cannot be saved
       was the first thing to disappear on the screen with the least room. Nothing wraps at desktop
       width; the fields are fixed and there is slack to spare. */
    flex-wrap: wrap;
    gap: 0.35rem;
    margin-top: 0.3rem;
    padding-left: 3.4rem;
    font-size: 0.8125rem;
    color: var(--color-text-muted);
}

/* Each label and its control wrap as one unit. Loose in the flex row they wrapped independently, so at
   fridge width "Category" was left stranded at the end of one line with its field on the next. */
.receipt-meta .fld[b-cgl1q7fse5] {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    white-space: nowrap;
}

.receipt-meta > .note[b-cgl1q7fse5],
.receipt-meta > .text-danger[b-cgl1q7fse5] { white-space: nowrap; }

/* Size and unit are one measurement, so they are one shape. Two separate bordered boxes for "0.13" and
   "oz" was the clearest case of the card having more outlines than it had ideas. */
.receipt-measure[b-cgl1q7fse5] { flex: 0 0 auto; width: 12.5rem; }
/* Fixed rather than shared, so the select keeps enough room for the "Pick a unit" placeholder and its
   invalid icon together. Split evenly, the placeholder was clipped mid-word on exactly the rows that
   most needed reading. */
.receipt-measure .form-control[b-cgl1q7fse5] { flex: 0 0 5rem; }

/* The per-piece weight is a note, not a field, so it gets room on both sides rather than sitting flush
   against the next label as if it were one. */
.receipt-meta .note[b-cgl1q7fse5] {
    margin: 0 0.4rem;
    font-style: italic;
    color: var(--color-text-muted);
}

/* With every field wearing the same chrome, the hierarchy has to come from type: the name is a size up
   and white, the details a size down and muted. */
.receipt-name[b-cgl1q7fse5] {
    font-weight: 600;
    color: var(--color-text-primary);
}

/* Left-aligned, like every other text field. Right-aligning it lined the brands up with the prices,
   which made two unrelated columns look like one.

   Wide enough for the brands that actually arrive. The name takes every bit of slack in the row, so at
   9.5rem the brand was the one field being starved: 134px of content box against the 170px
   "Signature SELECT/FARMS" needs, which clipped "Rolls Famous Bake House" mid-word while the name sat on
   498px for a longest-case 338px. 14rem leaves both with room to spare (~36px on the brand, ~88px on the
   name) measured against the longest values in a real order.

   Allowed to shrink, unlike the other fixed fields in this row. It is the widest of them, so if the
   dialog is ever narrow enough to run out of room it should give some back rather than make the name
   absorb the whole squeeze on its own. */
.receipt-brand[b-cgl1q7fse5] {
    flex: 0 1 auto;
    width: 14rem;
}

.receipt-qty[b-cgl1q7fse5] {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.15rem;
    color: var(--color-text-muted);
}

.receipt-qty .form-control[b-cgl1q7fse5] {
    width: 3.5rem;
    text-align: center;
    /* Chromium reserves space for the spinners whether or not they are shown, so a centred value lands
       visibly left of centre until they are removed. Same treatment as .pantry-staple-qty. */
    -moz-appearance: textfield;
    appearance: textfield;
}

.receipt-qty .form-control[b-cgl1q7fse5]::-webkit-outer-spin-button,
.receipt-qty .form-control[b-cgl1q7fse5]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* Fixed width and right-aligned, so the prices form a column down the dialog rather than trailing each
   name at whatever length it happened to be. */
.receipt-price[b-cgl1q7fse5] {
    flex: 0 0 auto;
    width: 4.5rem;
    text-align: right;
    font-weight: 600;
}

/* Fixed, not flexible. Letting it take the slack stretched one field across half the card and made the
   category the loudest thing on a line whose job is to be quiet. The space it gives back is not wasted:
   the measurement line now stops well short of the brand and price above it, which is what separates
   the two lines without a rule between them. */
.receipt-category[b-cgl1q7fse5] { flex: 0 0 auto; width: 15rem; max-width: 100%; }

.receipt-meta .lbl[b-cgl1q7fse5] { color: var(--color-text-muted); }
/* /Components/Dialogs/ShoppingLists/AddFromPantryDialog.razor.rz.scp.css */
/* The pantry can be long, so the rows scroll inside the dialog rather than growing it past the screen.
   Capped in vh so the cap tracks the viewport: on the fridge's 1080px portrait door there is room for
   far more rows than on a phone, and a fixed pixel cap would waste one or overflow the other. */
.pantry-pick-list[b-ji8apolt1a] {
    max-height: 50vh;
    overflow-y: auto;
}

/* The name wraps rather than truncating: a picker exists to be read, and "organic whole milk half
   gallon" cut off at the second word is not something anyone can pick from. */
.pantry-pick-name[b-ji8apolt1a] {
    word-break: break-word;
}

.pantry-pick-row[b-ji8apolt1a] {
    padding-top: 0.25rem;
    padding-bottom: 0.25rem;
}

/* Subordinate to the name it follows: it answers "which one" once you have already found the product,
   so it must not compete with the product for the eye. Shrinks away last if the row gets tight. */
.pantry-pick-hint[b-ji8apolt1a] {
    color: var(--color-text-muted);
    font-size: 0.875rem;
    white-space: nowrap;
}

.pantry-pick-search[b-ji8apolt1a] {
    padding-left: 2rem;
}

.pantry-pick-search-icon[b-ji8apolt1a] {
    position: absolute;
    left: 0.6rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--color-text-muted);
    pointer-events: none;
}
/* /Components/Dialogs/ShoppingLists/AddRecipeToShoppingListDialog.razor.rz.scp.css */
/* The recipe's name under the dialog title rather than in it: a title that grows to "Add Slow Cooker
   Butter Chicken with Basmati to Shopping List" wraps to three lines and buries the action. */
.recipe-shop-subtitle[b-s8spyo85cj] {
    margin-bottom: 0.75rem;
}

/* The batch control reads as one row: a word, the stepper, and what it currently amounts to. Wraps
   rather than squeezing the stepper, because the stepper is the part that gets tapped. */
.recipe-shop-batch[b-s8spyo85cj] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}

.recipe-shop-batch-label[b-s8spyo85cj] {
    font-weight: 600;
}

/* Wide enough that stepping 1 → 2 → 3 does not shuffle the + button sideways under the finger that is
   still pressing it. */
.recipe-shop-batch-value[b-s8spyo85cj] {
    min-width: 4rem;
    pointer-events: none;
}

.recipe-shop-batch-hint[b-s8spyo85cj] {
    color: var(--color-text-muted);
}

/* Long recipes scroll inside the dialog rather than growing it past the screen. In vh so the cap tracks
   the viewport: the fridge's 1080px portrait door fits far more rows than a phone does. */
.recipe-shop-list[b-s8spyo85cj] {
    max-height: 50vh;
    overflow-y: auto;
}

/* The name wraps rather than truncating: a picker exists to be read from. */
.recipe-shop-name[b-s8spyo85cj] {
    word-break: break-word;
}

.recipe-shop-row[b-s8spyo85cj] {
    padding-top: 0.25rem;
    padding-bottom: 0.25rem;
}

/* Badge and amount both sit at the end of the row, in that order, subordinate to the name. What you are
   buying is what you scan for; whether it is already down, and how much of it, is what you read once you
   have found the line.

   Whichever of the two comes first takes the auto margin that pushes the pair over. The badge appears on
   rows the recipe gave no amount for, so anchoring the pair to the amount alone left it stranded against
   the name on exactly those rows. An adjacent-sibling rule rather than :has(), which the fridge's
   Chromium 94 does not support. */
.recipe-shop-onlist[b-s8spyo85cj],
.recipe-shop-amount[b-s8spyo85cj] {
    margin-left: auto;
}

.recipe-shop-onlist + .recipe-shop-amount[b-s8spyo85cj] {
    margin-left: 0;
}

.recipe-shop-amount[b-s8spyo85cj] {
    color: var(--color-text-muted);
    white-space: nowrap;
}
/* /Components/Dialogs/ShoppingLists/GenerateShoppingListDialog.razor.rz.scp.css */
.gsl-subtitle[b-j94hjccbgq] {
    margin-bottom: 0.5rem;
}

/* Says the two things people assume the opposite of: that pressing Generate has already done something,
   and that "covered" means the pantry was charged. Sits above the rows rather than beside the button,
   because it has to be read before the rows are, not after. */
.gsl-note[b-j94hjccbgq] {
    color: var(--color-text-muted);
    font-size: 0.9rem;
    margin-bottom: 0.75rem;
}

/* A week's worth of ingredients scrolls inside the dialog rather than growing it past the screen. In vh
   so the cap tracks the viewport: the fridge's 1080px portrait door fits far more rows than a phone. */
.gsl-list[b-j94hjccbgq] {
    max-height: 55vh;
    overflow-y: auto;
}

.gsl-row[b-j94hjccbgq] {
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
}

/* Excluded rows stay legible rather than disappearing. A row faded to nothing reads as "this was dropped",
   and the whole point of showing what the pantry covers is that the decision can still be reversed. */
.gsl-row-excluded[b-j94hjccbgq] {
    opacity: 0.65;
}

/* The name wraps rather than truncating: this list exists to be read from. */
.gsl-name[b-j94hjccbgq] {
    font-weight: 600;
    word-break: break-word;
}

.gsl-badge[b-j94hjccbgq] {
    flex-shrink: 0;
    margin-left: auto;
}

/* The arithmetic, indented under the name so the rows scan as name-then-detail. Wraps rather than
   squeezing, since "Need 3 cups" and "Pantry covers 1.5 cups" are each meaningless truncated. */
.gsl-math[b-j94hjccbgq],
.gsl-from[b-j94hjccbgq],
.gsl-reason[b-j94hjccbgq],
.gsl-sources[b-j94hjccbgq] {
    padding-left: 2rem;
    font-size: 0.9rem;
    word-break: break-word;
}

.gsl-math[b-j94hjccbgq] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.75rem;
}

.gsl-needed[b-j94hjccbgq] {
    color: var(--color-text);
}

.gsl-covered[b-j94hjccbgq],
.gsl-from[b-j94hjccbgq],
.gsl-sources[b-j94hjccbgq] {
    color: var(--color-text-muted);
}

.gsl-reason[b-j94hjccbgq] {
    color: var(--bs-warning-text-emphasis, #664d03);
}

/* The one editable thing on the row, kept on its own line so the box is never competing with the name for
   width on a narrow door. */
.gsl-buy[b-j94hjccbgq] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding-left: 2rem;
    margin-top: 0.35rem;
}

.gsl-buy-label[b-j94hjccbgq] {
    margin-bottom: 0;
    color: var(--color-text-muted);
    font-size: 0.9rem;
}

/* Wide enough for a real amount, narrow enough that it does not read as the row's main content. */
.gsl-buy-input[b-j94hjccbgq] {
    max-width: 7rem;
    min-height: 48px;
}

.gsl-buy-unit[b-j94hjccbgq] {
    color: var(--color-text-muted);
}
/* /Components/Dialogs/ShoppingLists/PickShoppingListDialog.razor.rz.scp.css */
/* Each row is the action, not a label beside one, so it carries the app's minimum touch target itself. */
.pick-shopping-list-choice[b-er23nd6jsk] {
    display: flex;
    align-items: center;
    min-height: 48px;
}
/* /Components/Layout/MainLayout.razor.rz.scp.css */
.page[b-3tihf129go] {
    position: relative;
    display: flex;
    flex-direction: column;
}

.page-body[b-3tihf129go] {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

main[b-3tihf129go] {
    flex: 1;
}

/* ── Pinned nav sidebar at desktop (AB#928, design rule 11) ───────────────────
   .sidebar becomes display: contents so NavMenu's own .top-row and .nav-scrollable
   become grid items of this row: the nav takes the full-height first column and the
   utility bar sits over the content column instead of over the 220px nav. Their
   placement lives in NavMenu.razor.css, where those elements carry its scope
   attribute. Below 992px none of this applies and the CSS-only checkbox fold-out
   behaves exactly as it did. */
@media (min-width: 992px) {
    /* A definite height is what makes the 1fr row resolve to "the rest of the window" rather than to
       the nav's own content height. Without it a nav list longer than the window stretches the grid
       and pushes sign-out off the bottom, instead of scrolling inside its column. */
    .page[b-3tihf129go] {
        height: 100vh;
    }

    .page-body[b-3tihf129go] {
        display: grid;
        grid-template-columns: 220px 1fr;
        grid-template-rows: 3.5rem 1fr;
    }

    .sidebar[b-3tihf129go] {
        display: contents;
    }

    main[b-3tihf129go] {
        grid-column: 2;
        grid-row: 2;
        min-width: 0;
        min-height: 0;
    }
}

/* ── Docked chat panel (AB#1013) ──────────────────────────────────────────────
   The chat used to float over the bottom-right corner of whatever was behind it, which is no use
   when the assistant is driving the page you are trying to watch. Open, it becomes the last column
   of this grid and the page reflows into what is left; closed, .chat-docked is gone, the widget is
   position: fixed again and the grid is back to the two columns above. The widget's own placement
   lives in AiChatWidget.razor.css, where it carries that component's scope.

   The breakpoint is deliberately the same 992px the pinned nav uses, which is comfortably above the
   fridge's 720px: below it the nav is a fold-out over a single content column and there is not enough
   width left to reflow into, so the fridge and portrait tablets keep the overlay they have today. */
@media (min-width: 992px) {
    .page-body.chat-docked[b-3tihf129go] {
        /* Room enough to read a conversation in, capped so it never eats a wide window (AB#1013). */
        --chat-dock-width: clamp(20rem, 26vw, 24rem);

        grid-template-columns: 1fr var(--chat-dock-width);
    }

    /* The nav has given its column up in this range - see NavMenu.razor.css. */
    .page-body.chat-docked main[b-3tihf129go] {
        grid-column: 1;
    }
}

/* Wide enough for the pinned nav and the dock at once, so the nav column comes back. */
@media (min-width: 1280px) {
    .page-body.chat-docked[b-3tihf129go] {
        grid-template-columns: 220px 1fr var(--chat-dock-width);
    }

    .page-body.chat-docked main[b-3tihf129go] {
        grid-column: 2;
    }
}

.top-row[b-3tihf129go] {
    background-color: #f7f7f7;
    border-bottom: 1px solid #d6d5d5;
    justify-content: flex-end;
    height: 3.5rem;
    display: flex;
    align-items: center;
}

    .top-row[b-3tihf129go]  a, .top-row[b-3tihf129go]  .btn-link {
        white-space: nowrap;
        margin-left: 1.5rem;
        text-decoration: none;
    }

    .top-row[b-3tihf129go]  a:hover, .top-row[b-3tihf129go]  .btn-link:hover {
        text-decoration: underline;
    }

    .top-row[b-3tihf129go]  a:first-child {
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .top-row[b-3tihf129go] {
        justify-content: space-between;
    }

    .top-row[b-3tihf129go]  a, .top-row[b-3tihf129go]  .btn-link {
        margin-left: 0;
    }

#blazor-error-ui[b-3tihf129go] {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss[b-3tihf129go] {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }


/* /Components/Layout/NavMenu.razor.rz.scp.css */
.navbar-toggler[b-7n83us0okr] {
    display: none; /* Hides the actual checkbox */
}

.navbar-toggler-label[b-7n83us0okr] {
    cursor: pointer;
    width: 3rem;
    height: 2.5rem;
    color: white;
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 4px;
    background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
    flex-shrink: 0;
}

.navbar-toggler:checked + .navbar-toggler-label[b-7n83us0okr] {
    background-color: rgba(255, 255, 255, 0.5);
}


.top-row[b-7n83us0okr] {
    height: 3.5rem;
    background-color: rgba(0,0,0,0.4);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 0.75rem;
}

.nav-menu-icon[b-7n83us0okr] {
    color: white;
    font-size: 1.5rem;
    margin-right: 10px;
}

.nav-item[b-7n83us0okr] {
    font-size: 0.9rem;
    padding-bottom: 0.5rem;
}

    .nav-item:first-of-type[b-7n83us0okr] {
        padding-top: 1rem;
    }

    .nav-item:last-of-type[b-7n83us0okr] {
        padding-bottom: 1rem;
    }

    .nav-item[b-7n83us0okr]  .nav-link {
        color: var(--color-text-secondary);
        background: none;
        border: none;
        border-radius: 4px;
        height: 3rem;
        display: flex;
        align-items: center;
        line-height: 3rem;
        width: 100%;
        white-space: nowrap;
    }

.nav-item[b-7n83us0okr]  a.active {
    background-color: var(--color-bg-elevated);
    color: var(--color-cyan);
    border-left: 3px solid var(--color-cyan);
    border-radius: 0;
}

.nav-item[b-7n83us0okr]  .nav-link:hover {
    background-color: rgba(0, 212, 255, 0.06);
    color: var(--color-text-primary);
}

.nav-greeting[b-7n83us0okr] {
    color: rgba(255, 255, 255, 0.85);
    font-size: 0.85rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
    padding: 0 0.5rem;
}

.nav-right[b-7n83us0okr] {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

.nav-feedback-btn[b-7n83us0okr] {
    color: rgba(255, 255, 255, 0.75);
    font-size: 1.25rem;
    padding: 0.25rem 0.5rem;
    line-height: 1;
}

.nav-feedback-btn:hover[b-7n83us0okr] {
    color: var(--color-cyan);
}

.nav-bell-btn[b-7n83us0okr] {
    position: relative;
}

.bell-icon-wrap[b-7n83us0okr] {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.notification-badge[b-7n83us0okr] {
    position: absolute;
    top: -7px;
    right: -9px;
    background: var(--color-danger);
    color: white;
    border-radius: 10px;
    font-size: 0.6rem;
    font-weight: 700;
    min-width: 16px;
    height: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 3px;
    line-height: 1;
    pointer-events: none;
}

/* Unread announcements (AB#429). Nothing to count here, unlike the bell, so the same badge is drawn as
   a plain dot: "there is something new" is the whole message. */
.notification-badge-dot[b-7n83us0okr] {
    min-width: 10px;
    width: 10px;
    height: 10px;
    padding: 0;
    top: -4px;
    right: -6px;
}

.nav-scrollable[b-7n83us0okr] {
    display: none;
}

.navbar-toggler:checked ~ .nav-scrollable[b-7n83us0okr] {
    display: block;
    position: fixed;
    /* Anchored to the bottom of the nav bar. The impersonation banner (AB#1078) sits above the nav and
       raises this offset by its own height so the fold-out menu still meets the nav bar. */
    top: var(--nav-overlay-top, 3.5rem);
    left: 0;
    /* Auto-size to the widest label so items never wrap (AB#1179). 260px is the floor (previous fixed
       width); capped to the viewport so a long label can't push it off-screen on the fridge. */
    width: max-content;
    min-width: 260px;
    max-width: calc(100vw - 3rem);
    z-index: 999;
    background-color: var(--atebit-sidebar-bg);
    max-height: calc(100vh - var(--nav-overlay-top, 3.5rem));
    overflow-y: auto;
    box-shadow: 4px 4px 20px rgba(0, 0, 0, 0.5);
    border-radius: 0 0 8px 0;
}

/* Same scrollbar as .body-container in app.css - the app's one scrollbar treatment. Deliberately not
   inside the desktop media query: the fold-out scrolls too on a short window, and a native chrome-grey
   bar with stepper arrows reads as borrowed there just as much as it does in the pinned column. */
.nav-scrollable[b-7n83us0okr] {
    scrollbar-width: thin; /* Firefox */
    scrollbar-color: var(--color-border) transparent; /* Firefox */
}

.nav-scrollable[b-7n83us0okr]::-webkit-scrollbar {
    width: 10px;
}

.nav-scrollable[b-7n83us0okr]::-webkit-scrollbar-track {
    background: transparent;
}

.nav-scrollable[b-7n83us0okr]::-webkit-scrollbar-thumb {
    background-color: var(--color-border);
    border-radius: 6px;
}

.nav-scrollable[b-7n83us0okr]::-webkit-scrollbar-thumb:hover {
    background-color: var(--color-text-muted);
}

.nav-backdrop[b-7n83us0okr] {
    display: none;
    position: fixed;
    top: var(--nav-overlay-top, 3.5rem);
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 998;
    cursor: default;
}

.navbar-toggler:checked ~ .nav-backdrop[b-7n83us0okr] {
    display: block;
}

.nav-submenu[b-7n83us0okr] {
    border-left: 2px solid var(--color-border);
    margin-left: 1.75rem;
    overflow: hidden;
    animation: submenu-open-b-7n83us0okr 0.2s ease-out;
}

@keyframes submenu-open-b-7n83us0okr {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
}

.nav-submenu .nav-item:first-of-type[b-7n83us0okr] {
    padding-top: 0;
}

.nav-submenu .nav-item:last-of-type[b-7n83us0okr] {
    padding-bottom: 0;
}

/* The brand row only exists for the pinned sidebar; the fold-out hangs off a nav bar. */
.nav-brand[b-7n83us0okr] {
    display: none;
    flex: 0 0 3.5rem;
    align-items: center;
    justify-content: center;
    padding: 0 1rem;
    font-weight: 700;
    font-size: 1.05rem;
    letter-spacing: -0.01em;
    border-bottom: 1px solid var(--color-border);
}

.nav-brand em[b-7n83us0okr] {
    font-style: normal;
    color: var(--color-cyan);
}

/* ── Pinned desktop sidebar (AB#928, design rule 11) ──────────────────────────
   At >=992px the nav stops being a fold-out and becomes a persistent 220px column.
   MainLayout turns .page-body into the grid and .sidebar into display: contents, so
   the two elements below are placed here, where they carry this component's scope.

   This is a media query over the existing markup on purpose, not a JS rewrite: the
   checkbox toggle is what makes the menu work on the fridge, whose engine can neither
   report its soft keyboard nor drive Bootstrap's JS dropdowns reliably. Everything
   here is inert below the breakpoint, so that path is untouched. */
@media (min-width: 992px) {
    /* No hamburger and nothing to overlay: one nav treatment per width, no rail state. */
    .navbar-toggler-label[b-7n83us0okr],
    .nav-backdrop[b-7n83us0okr],
    .navbar-toggler:checked ~ .nav-backdrop[b-7n83us0okr] {
        display: none;
    }

    .top-row[b-7n83us0okr] {
        grid-column: 2;
        grid-row: 1;
        padding: 0 1rem;
    }

    /* Checked or not, the pinned column looks the same - the toggle simply stops mattering. */
    .nav-scrollable[b-7n83us0okr],
    .navbar-toggler:checked ~ .nav-scrollable[b-7n83us0okr] {
        grid-column: 1;
        grid-row: 1 / -1;
        display: flex;
        flex-direction: column;
        position: static;
        width: auto;
        min-width: 0;
        max-width: none;
        max-height: none;
        overflow-y: auto;
        background-color: var(--atebit-sidebar-bg);
        border-right: 1px solid var(--color-border);
        border-radius: 0;
        box-shadow: none;
    }

    .nav-brand[b-7n83us0okr] {
        display: flex;
    }

    /* Both containers are display: block in the fold-out, which is why `hr.mt-auto` is inert there.
       Making the list a flex column is what pins sign-out and the version to the bottom. */
    .nav-scrollable > nav[b-7n83us0okr] {
        display: flex;
        flex: 1 1 auto;
        padding: 0.5rem 0.5rem 0.35rem;
    }

    /* 44px is a pointer target, deliberately under the 48px touch floor: the sanctioned exception in
       design rule 10, because this column never renders on the touch fridge. The fold-out keeps 3rem. */
    /* !important only because Bootstrap's spacing utilities are: the markup's px-3 / ps-4 carry the
       fold-out's indent, and handing that job to .nav-submenu at this width is the only way the
       deepest label ("Ingredient Rules") stops clipping against the 220px column. */
    .nav-item[b-7n83us0okr] {
        padding-left: 0 !important;
        padding-right: 0 !important;
        padding-bottom: 0.15rem;
    }

    .nav-item:first-of-type[b-7n83us0okr] {
        padding-top: 0;
    }

    .nav-item:last-of-type[b-7n83us0okr] {
        padding-bottom: 0.15rem;
    }

    .nav-item[b-7n83us0okr]  .nav-link {
        height: 44px;
        line-height: 44px;
        padding: 0 0.6rem;
        font-size: 0.9375rem;
    }

    .nav-item[b-7n83us0okr]  a.active {
        padding-left: calc(0.6rem - 3px);
    }

    .nav-item[b-7n83us0okr]  .nav-menu-icon {
        font-size: 1.05rem;
        width: 1.25rem;
        text-align: center;
        flex-shrink: 0;
        margin-right: 0.6rem;
    }

    .nav-submenu[b-7n83us0okr] {
        margin-left: 1.05rem;
    }

    .nav-version[b-7n83us0okr] {
        padding: 0.3rem 0.6rem 0.5rem;
    }
}

/* ── The chat dock takes the column back (AB#1013) ────────────────────────────
   A pinned 220px nav and a docked chat panel together leave under 700px of page between them,
   which is not enough for the list pages the dock exists to let you watch. So in that range the
   nav hands its column back and returns to the fold-out it uses below 992px - the width goes to
   the page, and the menu is a tap away instead of a column away. Above 1280px there is room for
   both and none of this applies.

   These are undo rules for the pinned block above, so the two have to move together. Only what
   makes the column a column is undone: the item metrics up there suit a 260px fold-out as well as
   they suit a 220px column, and reverting Bootstrap's !important spacing utilities to get them
   back costs more than it buys. */
@media (min-width: 992px) and (max-width: 1279.98px) {
    .page-body.chat-docked .navbar-toggler-label[b-7n83us0okr] {
        display: block;
    }

    .page-body.chat-docked .navbar-toggler:checked ~ .nav-backdrop[b-7n83us0okr] {
        display: block;
    }

    /* Every column except the dock's, which runs the full height beside it. */
    .page-body.chat-docked .top-row[b-7n83us0okr] {
        grid-column: 1 / -2;
        padding: 0 0.75rem;
    }

    .page-body.chat-docked .nav-brand[b-7n83us0okr] {
        display: none;
    }

    .page-body.chat-docked .nav-scrollable[b-7n83us0okr] {
        display: none;
    }

    .page-body.chat-docked .navbar-toggler:checked ~ .nav-scrollable[b-7n83us0okr] {
        display: flex;
        position: fixed;
        top: var(--nav-overlay-top, 3.5rem);
        left: 0;
        width: max-content;
        min-width: 260px;
        max-width: calc(100vw - 3rem);
        max-height: calc(100vh - var(--nav-overlay-top, 3.5rem));
        z-index: 999;
        border-right: none;
        border-radius: 0 0 8px 0;
        box-shadow: 4px 4px 20px rgba(0, 0, 0, 0.5);
    }
}
/* /Components/Pages/Categories/Categories.razor.rz.scp.css */
/* Two grids on one page, where the shared table styles assume one.

   .body-container is a flex column sized by max-height with no definite height, so it ends up as tall
   as its content. flex-grow distributes *free* space, and a content-sized container has none — which
   is why `flex: 1` alone left both sections at their natural height with the rest of the page empty
   below them.

   The fix is a floor per section rather than a height on the container. Pinning the container to
   100vh would work too, but it would force every visit to /categories to be exactly viewport-tall,
   which pushes the page past the viewport (and raises a document scrollbar) whenever the impersonation
   or local-dev banner is showing above it. A min-height leaves the container's max-height in charge. */

/* Half the container's content box each.
   100vh - 4.6rem is the shared max-height (3.5rem navbar + 1.1rem padding), less this container's own
   2rem padding-bottom and the 2rem gap between the sections: (100vh - 8.6rem) / 2. Both sections get
   the same floor and neither can grow past it — the grid inside scrolls instead — so the split stays
   even no matter how the row counts differ. */
.category-section[b-7ohhlbh6ya] {
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    min-height: calc((100vh - 8.6rem) / 2);
}

.category-section + .category-section[b-7ohhlbh6ya] {
    margin-top: 2rem;
}

/* The shared .table-container floor is 250px, sized for a page with one grid. Here the section's
   share is what decides the height, and leaving 250px in place would stop a section shrinking to it.
   min-height: 0 on .table-responsive is the flex default of `auto` being overridden for the same
   reason, one level up. */
.category-section .table-responsive[b-7ohhlbh6ya],
.category-section .table-container[b-7ohhlbh6ya] {
    min-height: 0;
}

/* Two icon buttons need about 60px; the rest of the row belongs to the name. A fixed width rather
   than a percentage because the content is fixed: 10% would be ~200px of dead space on a desktop and
   ~70px (too tight for the buttons) on the 720px fridge, whereas 6rem parks them hard against the
   right edge at every width. The Name column takes the remainder on its own.

   ::deep because the th/td are rendered by QuickGrid, not by this component, so they never carry
   this stylesheet's scope attribute — a plain selector here would match nothing. */
.category-section[b-7ohhlbh6ya]  .col-actions {
    width: 6rem;
}
/* /Components/Pages/ChatPanel.razor.rz.scp.css */
/* ── Outer panel ──────────────────────────────────────────────── */

.chat-panel[b-5o9153en69] {
    position: relative;
    border: 1px solid var(--color-border);
    border-radius: 1rem;
    background: var(--color-bg-surface);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    height: 100%;
}

/* Magenta glow in top-left corner — the AI panel signature */
.chat-panel[b-5o9153en69]::before {
    content: '';
    position: absolute;
    top: -40px;
    left: -40px;
    width: 180px;
    height: 180px;
    background: var(--gradient-glow-magenta);
    pointer-events: none;
    z-index: 0;
}

/* ── Header ───────────────────────────────────────────────────── */

.chat-header[b-5o9153en69] {
    /* AB#1033. Sizes NibbleAvatar without ::deep: --nibble-size is a custom property, so it inherits
       into the child component's own scope, which a scoped class selector could not reach. A little
       above the 1.1rem the sparkle used, because he is a picture rather than a glyph and reads smaller
       than a font icon in the same box. */
    --nibble-size: 1.6rem;
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--color-border);
    font-weight: 700;
    font-size: 0.95rem;
    font-family: 'Space Grotesk', system-ui, sans-serif;
    letter-spacing: 0.04em;
    color: var(--color-magenta);
}

/* The header buttons' icons only: the sparkle this was written for is Nibble. */
.chat-header .bi[b-5o9153en69] {
    font-size: 1.1rem;
    color: var(--color-magenta);
}

/* AB#1017. The header grew a second button (start a new conversation), so the push to the right moved off
   the close button and onto a row holding both. Either one can be absent — close only when the widget
   passes a handler, new only once there is something to clear — and the row lays out the same regardless. */
.chat-header-actions[b-5o9153en69] {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

.chat-header-btn[b-5o9153en69] {
    background: none;
    border: none;
    color: var(--color-text-muted);
    cursor: pointer;
    padding: 0.25rem;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    min-width: 32px;
    border-radius: 0.4rem;
    font-size: 0.95rem;
    transition: background 0.15s, color 0.15s;
}

.chat-header-btn:hover:not(:disabled)[b-5o9153en69] {
    background: rgba(255, 255, 255, 0.08);
    color: var(--color-text-primary);
}

/* Disabled only ever means "a turn is in flight", which the send button already shows a spinner for, so
   this just needs to stop reading as pressable. */
.chat-header-btn:disabled[b-5o9153en69] {
    opacity: 0.4;
    cursor: default;
}

/* ── Message thread ───────────────────────────────────────────── */

.chat-messages[b-5o9153en69] {
    position: relative;
    z-index: 1;
    flex: 1;
    overflow-y: auto;
    padding: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    min-height: 120px;
    scroll-behavior: auto;
}

.chat-empty[b-5o9153en69] {
    color: var(--color-text-muted);
    font-size: 0.875rem;
    text-align: center;
    padding: 1.5rem 1rem;
    margin: auto;
    max-width: 320px;
}

/* ── Bubbles ──────────────────────────────────────────────────── */

.chat-bubble[b-5o9153en69] {
    max-width: 85%;
    padding: 0.6rem 0.9rem;
    border-radius: 1rem;
    font-size: 0.9rem;
    line-height: 1.5;
    word-break: break-word;
    white-space: pre-wrap;
}

.chat-bubble--user[b-5o9153en69] {
    align-self: flex-end;
    background: var(--color-cyan);
    color: #0A0E1A;
    border-bottom-right-radius: 0.25rem;
    white-space: pre-wrap;
}

.chat-bubble--ai[b-5o9153en69] {
    align-self: flex-start;
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    color: var(--color-text-primary);
    border-bottom-left-radius: 0.25rem;
    white-space: normal;
}

/* ── Markdown content inside AI bubbles ─────────────────────── */

.chat-markdown[b-5o9153en69] {
    font-size: 0.9rem;
    line-height: 1.6;
}

.chat-markdown p[b-5o9153en69] {
    margin: 0 0 0.5rem;
}

.chat-markdown p:last-child[b-5o9153en69] {
    margin-bottom: 0;
}

.chat-markdown ul[b-5o9153en69],
.chat-markdown ol[b-5o9153en69] {
    margin: 0.25rem 0 0.5rem 1.25rem;
    padding: 0;
}

.chat-markdown li[b-5o9153en69] {
    margin-bottom: 0.15rem;
}

.chat-markdown strong[b-5o9153en69] {
    font-weight: 700;
}

.chat-markdown code[b-5o9153en69] {
    font-family: 'JetBrains Mono', 'Fira Code', monospace;
    font-size: 0.82em;
    background: rgba(0, 212, 255, 0.08);
    color: var(--color-cyan);
    padding: 0.1em 0.3em;
    border-radius: 0.25rem;
}

.chat-markdown h1[b-5o9153en69],
.chat-markdown h2[b-5o9153en69],
.chat-markdown h3[b-5o9153en69] {
    font-family: 'Space Grotesk', system-ui, sans-serif;
    font-size: 1rem;
    font-weight: 700;
    margin: 0.5rem 0 0.25rem;
}

/* ── Streaming cursor ─────────────────────────────────────────── */

.chat-cursor[b-5o9153en69] {
    display: inline-block;
    width: 2px;
    height: 1em;
    background: var(--color-magenta);
    margin-left: 1px;
    vertical-align: text-bottom;
    animation: chat-blink-b-5o9153en69 0.7s step-end infinite;
}

@keyframes chat-blink-b-5o9153en69 {
    0%, 100% { opacity: 1; }
    50%       { opacity: 0; }
}

/* ── Typing indicator ─────────────────────────────────────────── */

.chat-typing[b-5o9153en69] {
    display: inline-flex;
    gap: 4px;
    align-items: center;
    height: 1.2em;
}

.chat-typing span[b-5o9153en69] {
    display: inline-block;
    width: 7px;
    height: 7px;
    background: var(--color-magenta);
    border-radius: 50%;
    animation: chat-dot-bounce-b-5o9153en69 1.2s infinite ease-in-out;
}

.chat-typing span:nth-child(1)[b-5o9153en69] { animation-delay: 0s; }
.chat-typing span:nth-child(2)[b-5o9153en69] { animation-delay: 0.2s; }
.chat-typing span:nth-child(3)[b-5o9153en69] { animation-delay: 0.4s; }

@keyframes chat-dot-bounce-b-5o9153en69 {
    0%, 80%, 100% { transform: scale(0.7); opacity: 0.5; }
    40%            { transform: scale(1);   opacity: 1; }
}

/* The caption beside the dots (AB#478). Quieter than the answer it is standing in for: this is the panel
   saying what it is doing, not the assistant talking, and the moment real text arrives it is replaced.
   Sits on its own line rather than beside the dots so a longer phrase does not push the bubble wide on a
   720px door. */
.chat-working[b-5o9153en69] {
    display: block;
    margin-top: 4px;
    font-size: 0.8rem;
    font-style: italic;
    color: var(--bs-secondary-color, #6c757d);
}

/* The dots are decoration once there is a caption to read; leaving them animating for a reader who has
   asked for less motion is noise with no information in it. The caption stays either way. */
@media (prefers-reduced-motion: reduce) {
    .chat-typing span[b-5o9153en69] {
        animation: none;
    }
}

/* ── Attachment chip ──────────────────────────────────────────── */

.chat-attachment-chip[b-5o9153en69] {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0 1rem 0.5rem;
    padding: 0.35rem 0.6rem;
    background: rgba(0, 212, 255, 0.08);
    border: 1px solid rgba(0, 212, 255, 0.25);
    border-radius: 0.5rem;
    font-size: 0.8rem;
    color: var(--color-cyan);
    font-weight: 600;
}

.chat-attachment-chip span[b-5o9153en69] {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.chat-attachment-remove[b-5o9153en69] {
    background: none;
    border: none;
    color: var(--color-cyan);
    padding: 0;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    font-size: 1rem;
    min-width: 24px;
    min-height: 24px;
    justify-content: center;
}

/* ── Drop overlay ─────────────────────────────────────────────── */

.chat-drop-overlay[b-5o9153en69] {
    position: absolute;
    inset: 0;
    background: rgba(0, 212, 255, 0.06);
    border: 2px dashed var(--color-cyan);
    border-radius: 1rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--color-cyan);
    pointer-events: none;
    z-index: 10;
}

.chat-drop-overlay .bi[b-5o9153en69] {
    font-size: 2rem;
}

/* ── Input row ────────────────────────────────────────────────── */

.chat-input-row[b-5o9153en69] {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: flex-end;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    border-top: 1px solid var(--color-border);
    background: var(--color-bg-surface);
}

.chat-textarea[b-5o9153en69] {
    flex: 1;
    resize: none;
    border: 1.5px solid var(--color-border);
    border-radius: 0.6rem;
    padding: 0.5rem 0.75rem;
    font-size: 0.9rem;
    font-family: 'Inter', system-ui, sans-serif;
    line-height: 1.5;
    background: var(--color-bg-subtle);
    color: var(--color-text-primary);
    min-height: 48px;
    max-height: 120px;
    overflow-y: auto;
    transition: border-color 0.15s;
}

.chat-textarea[b-5o9153en69]::placeholder {
    color: var(--color-text-muted);
}

.chat-textarea:focus[b-5o9153en69] {
    outline: none;
    border-color: var(--color-cyan);
    box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.12);
}

.chat-textarea:disabled[b-5o9153en69] {
    opacity: 0.6;
    cursor: not-allowed;
}

/* ── Input action buttons ─────────────────────────────────────── */

.chat-input-actions[b-5o9153en69] {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.chat-attach-btn[b-5o9153en69] {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    border: 1.5px solid var(--color-border);
    border-radius: 0.6rem;
    color: var(--color-text-muted);
    background: var(--color-bg-subtle);
    cursor: pointer;
    font-size: 1.1rem;
    transition: border-color 0.15s, color 0.15s;
}

.chat-attach-btn:hover[b-5o9153en69] {
    border-color: var(--color-cyan);
    color: var(--color-cyan);
}

/* ::deep because the button itself is a BusyButton (AB#1056): the element carries that component's
   scope attribute, not this one's, so the scope has to hang off .chat-input-actions instead. */
.chat-input-actions[b-5o9153en69]  .chat-send-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    background: var(--gradient-brand);
    color: #fff;
    border: none;
    border-radius: 0.6rem;
    font-size: 1.1rem;
    cursor: pointer;
    transition: opacity 0.15s;
}

.chat-input-actions[b-5o9153en69]  .chat-send-btn:hover:not(:disabled) {
    opacity: 0.85;
}

.chat-input-actions[b-5o9153en69]  .chat-send-btn:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

/* ── Disclaimer ───────────────────────────────────────────────── */

/* Sits below the input so it reads as a footnote to the panel rather than a message in it. flex: none
   because .chat-messages is the flex child that gives, and a squeezed disclaimer would clip mid-sentence.
   Every line here costs the conversation the same height, which is why the copy is three short sentences
   and the full version lives in the Terms.

   13px rather than the 12px this file uses elsewhere for muted text: the fridge is 720px wide, so it
   never enters the max-width: 575.98px block below, and it is read standing back from the door. */
.chat-disclaimer[b-5o9153en69] {
    position: relative;
    z-index: 1;
    flex: none;
    margin: 0;
    padding: 0 1rem 0.6rem;
    background: var(--color-bg-surface);
    color: var(--color-text-muted);
    font-size: 0.8125rem;
    line-height: 1.35;
    text-align: center;
}

/* ── Docked (AB#1013) ─────────────────────────────────────────── */

/* Docked, the panel is not a card on top of the page any more - it is the right-hand edge of the
   window. Rounded corners and a border all the way round would leave the page backdrop showing in the
   gaps, which is the seam the floating panel is meant to have and a docked one is not. Only the left
   border survives, as the seam the resize grip sits on. */
@media (min-width: 992px) {
    .page-body.chat-docked .chat-panel[b-5o9153en69] {
        border-radius: 0;
        border-top: none;
        border-right: none;
        border-bottom: none;
    }

    /* The dock runs the full height of the grid, so its header sits in the same band as the nav's
       brand row and the utility bar. Those are both 3.5rem; this header's height came from its
       padding and the title's line box and landed around 46px, so it read as a third bar that missed
       the other two by ten pixels. Pinned to the same height, the three bottom borders make one line
       across the window. */
    .page-body.chat-docked .chat-header[b-5o9153en69] {
        height: 3.5rem;
        padding: 0 1rem;
    }
}

/* ── Responsive: fridge (≤576px portrait) ─────────────────────── */

@media (max-width: 575.98px) {
    .chat-panel[b-5o9153en69] {
        border-radius: 0.75rem;
    }

    .chat-bubble[b-5o9153en69] {
        max-width: 92%;
        font-size: 0.95rem;
    }

    .chat-textarea[b-5o9153en69] {
        font-size: 1rem;
    }
}
/* /Components/Pages/HomeDashboard.razor.rz.scp.css */
/* ======================================================================
   The recipe-browser chrome this page used to be (AB#925).

   The home page is now the design-system dashboard — stat tiles and
   summary panels, styled from app.css — so nothing below matches any
   markup today. It is kept, not deleted, because the carousel and grid
   components it dresses are also still here, waiting for a surface to
   render them: the header, the view toggle and the category filter are
   what that surface needs, and re-deriving them later from scratch is
   the only alternative to keeping them now.

   One rule *was* removed rather than kept: a negative left margin on
   .body-container that let the carousel bleed its peek cards edge to
   edge. That one is not inert — it pulls the whole page left — and a
   dashboard has nothing to bleed.
   ====================================================================== */

.home-page[b-6f7mg3qjbn] {
    padding: 1.5rem 2rem;
}

.home-header[b-6f7mg3qjbn] {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    margin-bottom: 1.5rem;
}

.home-title[b-6f7mg3qjbn] {
    font-size: 1.75rem;
    font-weight: 800;
    margin-bottom: 0.1rem;
}

.home-subtitle[b-6f7mg3qjbn] {
    color: var(--atebit-muted, #6c757d);
    margin: 0;
    font-size: 0.9rem;
}

.home-header-actions[b-6f7mg3qjbn] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.home-header-actions .btn[b-6f7mg3qjbn] {
    min-height: 48px;
    display: inline-flex;
    align-items: center;
}

.view-toggle[b-6f7mg3qjbn] {
    display: flex;
    border: 2px solid var(--atebit-primary, #2F6B3E);
    border-radius: 0.5rem;
    overflow: hidden;
    min-height: 48px;
}

.view-toggle-btn[b-6f7mg3qjbn] {
    background: transparent;
    border: none;
    color: var(--atebit-primary, #2F6B3E);
    padding: 0.3rem 0.6rem;
    font-size: 1rem;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
    line-height: 1;
    align-self: stretch;
    display: flex;
    align-items: center;
    justify-content: center;
}

.view-toggle-btn:hover:not(.active)[b-6f7mg3qjbn] {
    background: rgba(0, 212, 255, 0.08);
}

.view-toggle-btn.active[b-6f7mg3qjbn] {
    background: var(--atebit-primary, #00D4FF);
    color: #0A0E1A;
}

/* ── Category filter ────────────────────────────────────────── */

.filter-wrap[b-6f7mg3qjbn] {
    position: relative;
}

.filter-btn[b-6f7mg3qjbn] {
    position: relative;
    background: transparent;
    border: 2px solid var(--atebit-primary, #2F6B3E);
    border-radius: 0.5rem;
    color: var(--atebit-primary, #2F6B3E);
    padding: 0.3rem 0.55rem;
    font-size: 1rem;
    cursor: pointer;
    line-height: 1;
    transition: background 0.15s, color 0.15s;
    min-width: 48px;
    min-height: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.filter-btn:hover[b-6f7mg3qjbn],
.filter-btn--active[b-6f7mg3qjbn] {
    background: var(--atebit-primary, #00D4FF);
    color: #0A0E1A;
}

.filter-active-dot[b-6f7mg3qjbn] {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 8px;
    height: 8px;
    background: var(--color-magenta);
    border-radius: 50%;
    border: 1px solid var(--atebit-primary, #00D4FF);
}

.filter-btn--active .filter-active-dot[b-6f7mg3qjbn] {
    border-color: #0A0E1A;
}

.filter-backdrop[b-6f7mg3qjbn] {
    position: fixed;
    inset: 0;
    z-index: 99;
}

.filter-panel[b-6f7mg3qjbn] {
    position: absolute;
    top: calc(100% + 0.4rem);
    right: 0;
    min-width: 200px;
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: 0.6rem;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
    padding: 0.6rem 0.75rem;
    z-index: 101;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.filter-panel-header[b-6f7mg3qjbn] {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.2rem;
}

.filter-panel-title[b-6f7mg3qjbn] {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--atebit-muted, #6c757d);
}

.filter-clear-btn[b-6f7mg3qjbn] {
    font-size: 0.75rem;
    color: var(--atebit-primary, #2F6B3E);
    text-decoration: none;
}

.filter-clear-btn:disabled[b-6f7mg3qjbn] {
    color: var(--atebit-muted, #6c757d);
    pointer-events: none;
}

/* /Components/Pages/HowItWorks/HowItWorksGuide.razor.rz.scp.css */
/* Everything on this page sits on a photograph (AB#1024), so the two rules that shape this file are
   that nothing may be loose text on the picture, and that no colour may be picked to be quiet against
   an opaque card — muted grey on a photograph is not quiet, it is absent. */

/* The pane. `.card` earns it the page's single blur from app.css; everything below is a tint on top
   of that, never a second blur. */
.hiw-pane[b-dk6duwslwa] {
    max-width: 1000px;
    margin: 0 auto;
    padding: 1.25rem;
}

/* Flexibility note - reassures that the full cycle is optional (AB#1182).
   It keeps its cyan identity as a tint over the pane rather than as a background of its own. The
   colour is laid on as an image layer because an element has only one background-color and the tint
   underneath it is doing the other half of the job. */
.hiw-flexibility-note[b-dk6duwslwa] {
    display: flex;
    gap: 0.75rem;
    align-items: flex-start;
    margin: 0 0 1.25rem;
    padding: 0.85rem 1rem;
    border-radius: 8px;
    background-color: rgba(42, 48, 80, 0.38);
    background-image: linear-gradient(rgba(0, 212, 255, 0.09), rgba(0, 212, 255, 0.09));
    border-left: 3px solid var(--color-primary);
}

.hiw-flexibility-note i[b-dk6duwslwa] {
    color: var(--color-primary);
    font-size: 1.1rem;
    line-height: 1.5;
    flex-shrink: 0;
}

/* The figure and the steps, one block. Stacked below 992px (the fridge's 720 door never leaves that
   branch), side by side above it, where the ring on its own otherwise filled the first screen and
   left a page about five steps opening on none of them. */
.hiw-body[b-dk6duwslwa] {
    display: grid;
    gap: 1.5rem;
}

@media (min-width: 992px) {
    .hiw-body[b-dk6duwslwa] {
        grid-template-columns: 320px 1fr;
        align-items: start;
    }
}

/* The core loop figure. Drawn rather than photographed, so it has no background of its own to fight
   the backdrop with, and it takes its colour from the theme instead of from pixels. No surface of
   its own: the pane is its floor. */
.hiw-loop[b-dk6duwslwa] {
    display: flex;
    justify-content: center;
    align-items: center;
}

.hiw-loop-svg[b-dk6duwslwa] {
    width: 100%;
    max-width: 320px;
    height: auto;
}

.hiw-ring[b-dk6duwslwa] {
    fill: none;
    stroke: rgba(255, 255, 255, 0.14);
    stroke-width: 2;
}

/* The arc is the arrowhead's tail: without one the head is a triangle rather than a direction.
   Each arc and its head take the colour of the node they leave, so the ring alternates cyan and
   magenta the way the nodes do rather than laying one flat colour over both. */
.hiw-arc[b-dk6duwslwa] {
    fill: none;
    stroke-width: 2.5;
    stroke-linecap: round;
}

.hiw-arc--cyan[b-dk6duwslwa] { stroke: var(--color-cyan); }
.hiw-arc--magenta[b-dk6duwslwa] { stroke: var(--color-magenta); }

.hiw-arrow path[b-dk6duwslwa] { stroke: none; }
.hiw-arrow.hiw-arc--cyan path[b-dk6duwslwa] { fill: var(--color-cyan); }
.hiw-arrow.hiw-arc--magenta path[b-dk6duwslwa] { fill: var(--color-magenta); }

/* Each node is an opaque disc, not a translucent one. It sits directly on the photograph rather than
   inside a card, and a see-through disc would show the picture through the glyph drawn on it. */
.hiw-node > circle[b-dk6duwslwa] {
    fill: #171C2E;
    stroke-width: 2;
}

.hiw-node path[b-dk6duwslwa],
.hiw-node rect[b-dk6duwslwa],
.hiw-node g circle[b-dk6duwslwa] {
    fill: none;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.hiw-node--cyan > circle[b-dk6duwslwa] { stroke: rgba(0, 212, 255, 0.55); }
.hiw-node--cyan path[b-dk6duwslwa],
.hiw-node--cyan rect[b-dk6duwslwa],
.hiw-node--cyan g circle[b-dk6duwslwa] { stroke: var(--color-cyan); }

.hiw-node--magenta > circle[b-dk6duwslwa] { stroke: rgba(200, 80, 192, 0.55); }
.hiw-node--magenta path[b-dk6duwslwa],
.hiw-node--magenta rect[b-dk6duwslwa],
.hiw-node--magenta g circle[b-dk6duwslwa] { stroke: var(--color-magenta); }

/* The repeat glyph in the middle of the ring, which is the one thing the list below cannot say. */
.hiw-loop-mark path[b-dk6duwslwa] {
    fill: none;
    stroke: rgba(255, 255, 255, 0.45);
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Step cards, one under the next in whichever column they land in. */
.hiw-list[b-dk6duwslwa] {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    gap: 0.75rem;
}

/* A tint on the pane rather than glass of its own, stepped up from it so the card edge still lands
   where the eye says it is. Same reasoning as the pantry's group cards (AB#942): a see-through card
   on a see-through card reads as a darker patch instead of a card, and it would put five more
   backdrop-filters on a page that needs one.

   The padding belongs to the anchor, not here, so the whole card is the target rather than a region
   inside it. */
.hiw-step[b-dk6duwslwa] {
    background-color: rgba(42, 48, 80, 0.38);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: var(--bs-border-radius, 0.375rem);
    overflow: hidden;
}

.hiw-step-link[b-dk6duwslwa] {
    display: flex;
    gap: 0.85rem;
    align-items: flex-start;
    /* Comfortably past the 48px touch floor on its own, before the copy inside it is counted. */
    padding: 1rem;
    text-decoration: none;
    color: inherit;
}

.hiw-step-link:hover[b-dk6duwslwa],
.hiw-step-link:focus-visible[b-dk6duwslwa] {
    background-color: rgba(255, 255, 255, 0.06);
}

.hiw-step-link:hover .hiw-step-label[b-dk6duwslwa] {
    color: var(--color-primary);
}

.hiw-step-icon[b-dk6duwslwa] {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background-color: rgba(0, 212, 255, 0.12);
    color: var(--color-primary);
    font-size: 1.1rem;
    flex-shrink: 0;
}

.hiw-step-text[b-dk6duwslwa] {
    display: block;
    min-width: 0;
}

.hiw-step-num[b-dk6duwslwa] {
    display: block;
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--color-primary);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin-bottom: 1px;
}

.hiw-step-label[b-dk6duwslwa] {
    display: block;
    font-weight: 600;
    color: var(--color-text-primary);
}

.hiw-step-desc[b-dk6duwslwa] {
    display: block;
    font-size: 0.875rem;
    color: var(--color-text-secondary);
}

/* /Components/Pages/Meals/MealPlans.razor.rz.scp.css */
/* Sits after the week label and its arrows, and wraps below them rather than squeezing them on a narrow
   door: the arrows are what get tapped constantly, this is pressed once a week. */
/* ::deep because this is a BusyButton (AB#1056), whose element belongs to that component's scope
   rather than this one's. The scope hangs off .mp-week-nav, which is still ours. */
.mp-week-nav[b-vzvmcnf37d]  .mp-generate-list {
    margin-left: 0.5rem;
    min-height: 44px;
}

.mp-week-nav[b-vzvmcnf37d] {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-bottom: 1.25rem;
}

.mp-week-label[b-vzvmcnf37d] {
    font-weight: 600;
    font-size: 1.1rem;
    min-width: 200px;
    text-align: center;
}

/* ── Draft bar (AB#478) ──
   Between the week nav and the grid, in the flow rather than floating. The chat panel already owns the
   bottom right until AB#1013 docks it, and a bar pinned down there would be covered by the thing that
   put it on screen. Wraps on a narrow door so the buttons drop under the sentence rather than crushing
   it: the sentence is what tells the user nothing is saved, and it has to survive at 720px. */
.mp-draft-bar[b-vzvmcnf37d] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 1rem;
    padding: 0.75rem 1rem;
    border: 1px dashed var(--bs-secondary-color, #6c757d);
    border-radius: 8px;
    background: var(--bs-secondary-bg, #e9ecef);
    color: var(--bs-body-color);
}

.mp-draft-bar-icon[b-vzvmcnf37d] {
    font-size: 1.1rem;
    flex-shrink: 0;
}

.mp-draft-bar-text[b-vzvmcnf37d] {
    flex: 1;
    min-width: 12rem;
    font-size: 0.9rem;
}

.mp-draft-bar-actions[b-vzvmcnf37d] {
    display: flex;
    gap: 0.5rem;
    flex-shrink: 0;
}

/* The bar's buttons are the two most consequential controls on the page while a draft is up, and they
   sit next to each other, so neither drops below the touch floor on the fridge. */
.mp-draft-bar-actions[b-vzvmcnf37d]  .btn {
    min-height: 48px;
}

.mp-loading[b-vzvmcnf37d], .mp-empty[b-vzvmcnf37d] {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 3rem;
    gap: 1rem;
}

/* --bs-secondary-color, not --bs-secondary: the first is Bootstrap's muted body text, the second is the
   theme colour, which atebit-theme.css sets to brand magenta. This empty state was asking for grey and
   getting magenta (AB#880). Same for .mp-add-btn below. */
.mp-empty-icon[b-vzvmcnf37d] {
    font-size: 3rem;
    color: var(--bs-secondary-color);
}

.mp-empty-text[b-vzvmcnf37d] {
    color: var(--bs-secondary-color);
    margin: 0;
}

/* ── The week grid on a backdrop (AB#944) ──
   These live here rather than in app.css beside the other backdrop rules, and that is forced rather
   than chosen: scoped CSS is rewritten to `.mp-meal-box[b-xxxxxxxxxx]` and its bundle loads after
   app.css, so a `.page-backdrop .mp-meal-box` written over there matches at equal specificity and
   loses on source order. There is a note in app.css pointing here.

   This page is the one exception to "one blur per page", and it is a deliberate, measured-by-eye
   exception rather than an oversight. Everywhere else the glass goes on a single wrapper and the
   things inside it become tints, because a virtualized list has no bound on how many cells it might
   paint. The week grid has a hard bound of seven days times the household's meal times, and it has no
   wrapper worth having: a card around the whole week is either stretched to fill the door, which
   looked like a slab, or sized to its content, which leaves the bottom of the fridge as bare
   magnified photograph. Cells floating on the picture is the layout, so the glass has to be on the
   cells. Four meal times is 28 live blurs; that is the cost, it is bounded, and it has not been
   measured on Family Hub hardware.

   A cell's box is `--bs-body-bg` off a backdrop, the flattest colour in the theme, which here would be
   an opaque tile with the photograph only ever visible in the 4px gutters. It becomes the standard
   glass instead, the same values every other glassed surface in app.css uses.

   Deliberately left alone: .mp-meal-content inside it, which takes the meal time's own colour inline
   at 0.3 alpha. That tint is already translucent so it composes over the glass without help, and it is
   the one thing on the page the backdrop treatment must not touch.

   The blur is on a pseudo-element rather than on .mp-meal-box itself, and that is load-bearing rather
   than stylistic (AB#1035). A backdrop-filter makes its element a containing block for every
   position: fixed descendant, exactly as a transform does. The chip's kebab menu is fixed precisely so
   it can escape .mp-grid's overflow clipping; with the filter on the box, the viewport coordinates
   anchoredMenu.js writes were resolved against the cell instead and the menu landed hundreds of pixels
   away and clipped, so tapping the kebab looked like it did nothing at all. Measured: an identical
   fixed child asked for (500, 200) lands there inside a plain box and at (621, 703) inside a blurred
   one. A pseudo-element paints the same glass without being an ancestor of anything.

   Safe here only because .mp-meal-box does not scroll. On a scroll container an absolutely positioned
   pane scrolls away with the content, so this treatment is only available to a surface that does not
   move. Written here as if .table-responsive were such a container, which it is not: it hands its
   overflow to a .table-container child, so AB#1037 gave every glass surface in app.css the same pane,
   and the "does not scroll" precondition is asserted there rather than assumed. */
.page-backdrop .mp-meal-box[b-vzvmcnf37d] {
    position: relative;
    background: transparent;
    border-color: rgba(255, 255, 255, 0.12);
}

.page-backdrop .mp-meal-box[b-vzvmcnf37d]::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: rgba(34, 40, 64, 0.55);
    -webkit-backdrop-filter: blur(14px) saturate(120%);
    backdrop-filter: blur(14px) saturate(120%);
    pointer-events: none;
}

/* The pane is positioned and the cell's contents are not, so without this the glass paints over the
   meals instead of behind them. Position the content rather than pushing the pane to z-index: -1: a
   negative layer would fall behind the page's own background, and raising a stacking context on the box
   to contain it would cap the menu's z-index against the rest of the grid. */
.page-backdrop .mp-meal-box > *[b-vzvmcnf37d] {
    position: relative;
}

/* Column and row titles: a tint, and no blur. Doubling the blur count for chrome carrying three
   characters is not a trade worth making, and it does not need one. These are small, they are darker
   than the cells so the grid's labels sit behind its contents, and nothing scrolls underneath them,
   so unlike a list page's sticky <thead> they need no opaque floor either.

   .mp-slot-name and .mp-portrait-slot-header are absent on purpose. Both carry the meal time's colour
   as an inline style, which wins over anything written here, and that is the correct outcome. */
.page-backdrop .mp-slot-header[b-vzvmcnf37d],
.page-backdrop .mp-day-header[b-vzvmcnf37d],
.page-backdrop .mp-portrait-day-header[b-vzvmcnf37d],
.page-backdrop .mp-portrait-corner[b-vzvmcnf37d] {
    background: rgba(18, 22, 42, 0.6);
}

/* The day totals are a footer band, so they take the footer's tint (.table-footer in app.css): enough
   darkening to read as a summary of the grid rather than another row of it. */
.page-backdrop .mp-day-total-label[b-vzvmcnf37d],
.page-backdrop .mp-day-total-cell[b-vzvmcnf37d],
.page-backdrop .mp-portrait-day-total-bar[b-vzvmcnf37d] {
    background: rgba(18, 22, 42, 0.45);
}

/* The nutrition pill is the innermost surface on the page — a chip inside a cell inside the card — so
   it gets the least of everything. Opaque here would be a solid lozenge floating on glass. */
.page-backdrop .mp-nutrition-row[b-vzvmcnf37d] {
    background: rgba(18, 22, 42, 0.45);
}

.page-backdrop .mp-nutrition-row:hover[b-vzvmcnf37d] {
    background: rgba(42, 48, 80, 0.62);
}

/* Hover on the add button, restated as a tint for the reason the list pages restate their row hover:
   an opaque fill appearing inside a see-through cell reads as a bug, not as a hover. */
.page-backdrop .mp-add-btn:hover[b-vzvmcnf37d] {
    background: rgba(42, 48, 80, 0.55);
}

/* ── Landscape grid (default) ── */
/* The 240px floor on the day columns originally sized a chip's three 48px action buttons; AB#879 collapsed
   those into one kebab, so that math no longer applies. The floor stays at 240px for a different reason:
   with 80px constant per column (cell padding, the 48px thumbnail, chip padding and gaps) and a 48px kebab,
   240 is what leaves the meal name a readable ~90px instead of an ellipsis and two letters. A plain 1fr is
   really minmax(auto, 1fr), which lets the columns collapse to min-content instead: a column holding a long
   recipe name would steal width from its neighbours.

   This floor is also what the portrait breakpoint below is derived from, so the two stay in step: change
   240 here and the 1890px there has to move with it. */
.mp-grid[b-vzvmcnf37d] {
    display: grid;
    grid-template-columns: 120px repeat(7, minmax(240px, 1fr));
    gap: 4px;
    /* Belt and braces. The breakpoint below is meant to hand over to the portrait grid before the week can
       overflow, so this should never engage; it is here so a future column-width change degrades to a
       scroll rather than to content clipped off the page. */
    overflow-x: auto;
}

/* ── Portrait grid (hidden by default, shown in portrait orientation) ── */
.mp-portrait-grid[b-vzvmcnf37d] {
    display: none;
    gap: 4px;
    overflow-x: auto;

    /* The rowheader column, holding a day name over its date. Stated once here because three rules need
       it: the two that build the grid and the width cap, which measures from it.

       It was 64px when this layout only ever ran on a narrow screen, where every pixel it took came off a
       meal cell that was already tight. One value rather than a wide one and a narrow one, because the
       fridge can afford it: at 720px the door has 672px of content, so this costs each of two meal cells
       16px and leaves them 284px, still a comfortable line and a half of a meal name beside a 48px
       thumbnail. A second breakpoint here would be two numbers to keep in step for that. */
    --mp-day-col: 96px;
}

/* Orientation alone was the wrong signal. It is right for the fridge and for a tablet held upright, but on
   a desktop it only asks whether the window is taller than it is wide, which says nothing about whether
   seven day columns fit. A 1280x900 window is landscape orientation and roughly 550px short of the grid,
   so the week ran off the page and had to be scrolled sideways. Width is the question that actually
   matters, so ask both.

   1890px is where the landscape grid stops fitting: 120 + 7*240 of columns + 8*4 of gaps = 1832px of grid,
   plus ~58px of page chrome (1.5rem of content padding each side and the body-container scrollbar). Above
   it the week fits at its designed size; below it the portrait grid, which is built for a narrow viewport,
   takes over rather than the landscape one being squeezed. Both grids are always in the DOM, so this is
   purely a matter of which one is shown. */
@media (orientation: portrait), (max-width: 1889.98px) {
    .mp-grid[b-vzvmcnf37d] {
        display: none;
    }

    .mp-portrait-grid[b-vzvmcnf37d] {
        display: grid;
        grid-template-columns: var(--mp-day-col) repeat(var(--slot-count), minmax(0, 1fr));
    }

    .mp-portrait-day-total-bar[b-vzvmcnf37d] {
        grid-column: 2 / -1;
    }
}

/* A day per row on every screen, not only where the week does not fit (AB#1039).

   The breakpoint above still describes when the landscape grid stops fitting, and is left intact because
   it is what comes back: the switch is DayPerRowAlways in MealPlans.razor, and turning it off removes
   this class and hands the decision to the media query again. Nothing else has to move.

   These are the same three declarations as the block above rather than a shared rule, because a class
   condition and a media condition cannot be written as one selector. They win wherever both apply, being
   a class deeper, so the order of the two blocks does not matter.

   The .mp-portrait- names are now doing duty in landscape too. They are left alone on purpose: renaming
   them would be a wide diff to undo if the switch is ever flipped back, and the name is accurate about
   the shape it draws rather than about the device it draws on. */
.mp-day-rows-only .mp-grid[b-vzvmcnf37d] {
    display: none;
}

.mp-day-rows-only .mp-portrait-grid[b-vzvmcnf37d] {
    display: grid;
    grid-template-columns: var(--mp-day-col) repeat(var(--slot-count), minmax(0, 1fr));
    /* The columns are 1fr, so on a wide desktop they take the whole window: a household with two meal
       times would get two 900px cells holding one chip each. 460px is about where a meal stops needing
       more room — it is what the longest name in a real week ("Taco night with ground beef, pico and
       cheese in taco shells") sits on in a single line — so the grid stops growing there and leaves the
       rest of the window empty rather than stretching into it.

       Scaled by the meal-time count rather than fixed, since a household with four meal times has four
       columns to fit. Only reached because this layout now runs at widths it never used to: under the
       media query above it only ran below 1890px, where a cap would almost never have engaged. */
    max-width: calc(var(--mp-day-col) + var(--slot-count) * 460px);
}

.mp-day-rows-only .mp-portrait-day-total-bar[b-vzvmcnf37d] {
    grid-column: 2 / -1;
}

.mp-portrait-corner[b-vzvmcnf37d] {
    background: var(--bs-secondary-bg);
    border-radius: 4px;
}

.mp-portrait-slot-header[b-vzvmcnf37d] {
    padding: 0.4rem 0.25rem;
    font-weight: 600;
    font-size: 0.75rem;
    text-align: center;
    border-radius: 4px;
}

.mp-portrait-day-header[b-vzvmcnf37d] {
    background: var(--bs-secondary-bg);
    padding: 0.4rem 0.25rem;
    font-weight: 600;
    font-size: 0.75rem;
    text-align: center;
    border-radius: 4px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.mp-portrait-day-header.mp-today[b-vzvmcnf37d] {
    background: #d1e7dd;
    color: #0a3622;
    border: 2px solid #198754;
}

.mp-slot-header[b-vzvmcnf37d],
.mp-day-header[b-vzvmcnf37d] {
    background: var(--bs-secondary-bg);
    padding: 0.5rem;
    font-weight: 600;
    font-size: 0.8rem;
    text-align: center;
    border-radius: 4px;
}

.mp-day-header.mp-today[b-vzvmcnf37d] {
    background: #d1e7dd;
    color: #0a3622;
    border: 2px solid #198754;
}

.mp-slot-header[b-vzvmcnf37d] {
    text-align: left;
}

.mp-day-name[b-vzvmcnf37d] {
    font-size: 0.85rem;
    font-weight: 700;
}

.mp-day-date[b-vzvmcnf37d] {
    font-size: 0.75rem;
    opacity: 0.8;
}

.mp-slot-name[b-vzvmcnf37d] {
    display: flex;
    align-items: flex-start;
    padding: 0.5rem;
    font-weight: 600;
    font-size: 0.85rem;
    background: var(--bs-secondary-bg);
    border-radius: 4px;
}

.mp-cell[b-vzvmcnf37d] {
    min-height: 60px;
    display: flex;
    flex-direction: column;
}

/* ── Meal group box (clickable card inside each cell) ── */
.mp-meal-box[b-vzvmcnf37d] {
    flex: 1;
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: 6px;
    padding: 0.35rem;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.mp-meal-content[b-vzvmcnf37d] {
    flex: 1;
    border: 1px solid var(--bs-border-color);
    border-radius: 4px;
    padding: 0.25rem;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

/* ── Nutrition summary row / MealDetail link ── */
.mp-nutrition-row[b-vzvmcnf37d] {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 3px;
    font-size: 0.68rem;
    /* Neutral muted text, not the secondary theme colour. Ate-bit brands --bs-secondary-* magenta
       (AB#880), and a nutrition readout is information, not an accent. */
    color: var(--bs-secondary-color);
    background: var(--bs-secondary-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: 999px;
    padding: 2px 7px;
    text-decoration: none;
    overflow: hidden;
    white-space: nowrap;
    cursor: pointer;
}

/* Neutral throughout, like the resting state: this pill is a readout, so hovering it should say
   "clickable", not introduce a colour the row does not otherwise use (AB#880). The fill drops to the
   recessed surface and the border and text step up, which is the same press-and-brighten the stock
   Bootstrap colours gave before the theme renamed --bs-secondary to brand magenta. */
.mp-nutrition-row:hover[b-vzvmcnf37d] {
    background: var(--bs-tertiary-bg);
    border-color: var(--color-text-muted);
    color: var(--bs-body-color);
}

.mp-nutrition-text[b-vzvmcnf37d] {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.mp-nutrition-chevron[b-vzvmcnf37d] {
    font-size: 0.6rem;
    flex-shrink: 0;
    opacity: 0.7;
}

/* ── Add button ── */
.mp-add-btn[b-vzvmcnf37d] {
    background: none;
    border: 1px dashed var(--bs-border-color);
    border-radius: 4px;
    width: 100%;
    padding: 2px;
    font-size: 0.8rem;
    color: var(--bs-secondary-color);
    cursor: pointer;
    margin-top: auto;
}

.mp-add-btn:hover[b-vzvmcnf37d] {
    background: var(--bs-secondary-bg);
    color: var(--bs-body-color);
}

/* ── Cooked cell state ── */
/* A scrim over the cell rather than opacity on it (AB#1038). An opacity below 1 creates a stacking
   context, and the chip's kebab menu is a DOM descendant of this box, so its z-index: 1090 was capped
   inside the cell rather than competing with the rest of the grid: on a cooked meal the menu opened at
   half strength with the following day's box painted over its lower half, and its actions could not be
   read or tapped.

   Where the menu *landed* was never affected, because opacity establishes a stacking context but not a
   containing block for position: fixed. That is why the trap probe AB#1035 left behind stayed green
   through this: it asks where a fixed child resolves, and the answer here was correct. This is the
   other half of the same family, what paints over the menu rather than where it goes.

   A pseudo-element dims the same pixels without being above anything the menu needs to beat. It paints
   after the cell's contents, which the backdrop rule above leaves at z-index auto, and below the menu,
   which carries a positive z-index of its own. The colour is the page's own backdrop tint, so a cooked
   cell still reads as receding into the photograph the way fading it did.

   Nothing else on a cell may take opacity for the same reason. .mp-chip-dragging is the one exception
   and is safe: no menu is open while a chip is being dragged. */
.mp-cell-cooked .mp-meal-box[b-vzvmcnf37d] {
    position: relative;
}

.mp-cell-cooked .mp-meal-box[b-vzvmcnf37d]::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: rgba(26, 31, 46, 0.5);
    pointer-events: none;
}

/* ── Drag states (AB#879) ──
   Cell-level only. Everything that styles the chip itself lives in MealChip.razor.css, because Blazor's
   scoped CSS keys on the component that renders an element and this page no longer renders chips.

   These stay inside the fridge's ~M94 budget: no :has(), container queries, CSS nesting, @layer, or view
   transitions. The fridge is a target for this feature, so a modern selector would break the one device
   the drag was proven on. */

/* Every cell that can take the chip, marked for the whole drag so the user is not hunting for drop
   zones. Dashed border as well as a tint, so the cue is not colour alone. */
.mp-cell-valid .mp-meal-box[b-vzvmcnf37d] {
    border-style: dashed;
    border-color: var(--bs-primary-border-subtle);
    background: var(--bs-primary-bg-subtle);
    transition: background-color 120ms ease, border-color 120ms ease;
}

/* The armed cell is a different kind of change, not just a stronger tint: solid border, an inset ring,
   and a slight lift, so it survives both colour blindness and the fridge's washed-out panel. */
.mp-cell-armed .mp-meal-box[b-vzvmcnf37d] {
    border-style: solid;
    border-color: var(--bs-primary);
    background: var(--bs-primary-bg-subtle);
    box-shadow: inset 0 0 0 2px var(--bs-primary-border-subtle);
    transform: scale(1.02);
}

@media (prefers-reduced-motion: reduce) {
    .mp-cell-valid .mp-meal-box[b-vzvmcnf37d],
    .mp-cell-armed .mp-meal-box[b-vzvmcnf37d] {
        transition: none;
        transform: none;
    }
}


/* ── Day total footer row ── */
.mp-day-total-label[b-vzvmcnf37d] {
    background: var(--bs-tertiary-bg);
    padding: 0.4rem 0.5rem;
    font-weight: 600;
    font-size: 0.75rem;
    border-radius: 4px;
    display: flex;
    align-items: center;
}

.mp-day-total-cell[b-vzvmcnf37d] {
    background: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: 4px;
    padding: 0.35rem;
    min-height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.mp-day-total-nutrition[b-vzvmcnf37d] {
    font-size: 0.68rem;
    color: var(--bs-secondary-color);
    font-weight: 500;
    text-align: center;
}

/* ── Portrait total bar — spans all meal columns below each day row ── */
.mp-portrait-day-header.mp-day-row-span[b-vzvmcnf37d] {
    grid-row: span 2;
}

.mp-portrait-day-total-bar[b-vzvmcnf37d] {
    font-size: 0.68rem;
    color: var(--bs-secondary-color);
    font-weight: 500;
    text-align: center;
    padding: 2px 6px;
    background: var(--bs-tertiary-bg);
    border-radius: 4px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 20px;
}


/* /Components/Pages/Privacy.razor.rz.scp.css */
.privacy-card[b-qoayzrfmry] {
    background: var(--color-bg-surface, #222840);
    border: 1px solid var(--color-border, #2E3550);
    border-radius: 1.25rem;
    padding: 2.5rem 2.75rem;
    width: 100%;
    max-width: 680px;
    margin: 0 auto;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
    position: relative;
    overflow: hidden;
}

.privacy-card[b-qoayzrfmry]::before {
    content: '';
    position: absolute;
    top: -60px;
    right: -60px;
    width: 240px;
    height: 240px;
    background: radial-gradient(ellipse at center, rgba(0, 212, 255, 0.08) 0%, transparent 70%);
    pointer-events: none;
}

.privacy-logo-wrap[b-qoayzrfmry] {
    text-align: center;
    margin-bottom: 1.25rem;
}

.privacy-logo[b-qoayzrfmry] {
    width: 64px;
    height: 64px;
    border-radius: 12px;
}

.privacy-title[b-qoayzrfmry] {
    font-family: 'Space Grotesk', system-ui, sans-serif;
    font-size: 1.75rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--color-text-primary, #fff);
    margin-bottom: 0.25rem;
    text-align: center;
}

.privacy-updated[b-qoayzrfmry] {
    font-size: 0.8rem;
    color: var(--color-text-muted, #5A6480);
    text-align: center;
    margin-bottom: 1.75rem;
}

.privacy-card h2[b-qoayzrfmry] {
    font-family: 'Space Grotesk', system-ui, sans-serif;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--color-cyan, #00D4FF);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-top: 1.75rem;
    margin-bottom: 0.5rem;
}

.privacy-card p[b-qoayzrfmry] {
    font-size: 0.92rem;
    line-height: 1.7;
    color: var(--color-text-secondary, #A0AABB);
    margin-bottom: 0.75rem;
}

.privacy-card ul[b-qoayzrfmry] {
    padding-left: 1.5rem;
    margin-bottom: 0.75rem;
}

.privacy-card li[b-qoayzrfmry] {
    font-size: 0.92rem;
    line-height: 1.7;
    color: var(--color-text-secondary, #A0AABB);
    margin-bottom: 0.2rem;
}

.privacy-card strong[b-qoayzrfmry] {
    color: var(--color-text-primary, #fff);
    font-weight: 600;
}

.privacy-card a[b-qoayzrfmry] {
    color: var(--color-cyan, #00D4FF);
    text-decoration: none;
}

.privacy-card a:hover[b-qoayzrfmry] {
    color: var(--color-primary-hover, #00BBDD);
}

.privacy-footer[b-qoayzrfmry] {
    margin-top: 2rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--color-border, #2E3550);
    text-align: center;
    font-size: 0.875rem;
}
/* /Components/Pages/Public/About.razor.rz.scp.css */
/* Scoped layout for the public About page. General marketing styles live in the .landing section of
   app.css; only the hero photograph is page-specific. Fridge-safe (Chromium M94): no :has(),
   @container, @layer, subgrid, nesting, or text-wrap. The .landing CSS custom properties (--l-*)
   cascade in from the MarketingShell wrapper. */

/* Neon-kitchen hero shot (AB#812). Framed to match the .panel blocks it sits between, so the page
   reads as one column rather than a photo dropped between cards. The gradient hairline is the same
   signature rule the panels and feature blocks use. */
.about-hero-shot[b-s3fgsntn8k] {
    position: relative;
    margin: 0 0 clamp(2rem, 5vw, 3rem);
    border: 1px solid var(--l-border);
    border-radius: 20px;
    overflow: hidden;
    line-height: 0;
}

.about-hero-shot[b-s3fgsntn8k]::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    background: var(--l-grad);
    opacity: 0.85;
    z-index: 1;
}

.about-hero-shot img[b-s3fgsntn8k] {
    display: block;
    width: 100%;
    height: auto;
}
/* /Components/Pages/Public/Contact.razor.rz.scp.css */
/* Contact page (AB#746) — form styling on top of the shared .landing look in app.css.
   Fridge-safe (Chromium M94): no :has(), @container, @layer, subgrid, or CSS nesting.
   The .landing custom properties (--l-*) inherit down from MarketingShell's wrapper. */

.contact-panel[b-n39nf5jx6o] {
    max-width: 640px;
    margin-left: auto;
    margin-right: auto;
}

.contact-form[b-n39nf5jx6o] {
    text-align: left;
    margin-top: 1.5rem;
}

.contact-field[b-n39nf5jx6o] {
    margin-bottom: 1.25rem;
}

.contact-field label[b-n39nf5jx6o] {
    display: block;
    margin-bottom: 0.4rem;
    font-weight: 600;
    font-size: var(--l-fs-meta);
    color: var(--l-text-strong);
}

.contact-field .optional[b-n39nf5jx6o] {
    font-weight: 400;
    color: var(--l-muted);
    font-size: var(--l-fs-fine);
}

.contact-field input[b-n39nf5jx6o],
.contact-field textarea[b-n39nf5jx6o] {
    display: block;
    width: 100%;
    min-height: 48px;
    padding: 0.7rem 0.9rem;
    font-family: inherit;
    font-size: var(--l-fs-body);
    line-height: 1.5;
    color: var(--l-text-strong);
    background: var(--l-card-2);
    border: 1px solid var(--l-border-strong);
    border-radius: 8px;
    transition: border-color 0.15s, box-shadow 0.15s;
    -webkit-appearance: none;
    appearance: none;
}

.contact-field textarea[b-n39nf5jx6o] {
    resize: vertical;
    min-height: 140px;
}

.contact-field input[b-n39nf5jx6o]::placeholder,
.contact-field textarea[b-n39nf5jx6o]::placeholder {
    color: var(--l-muted);
}

.contact-field input:focus[b-n39nf5jx6o],
.contact-field textarea:focus[b-n39nf5jx6o] {
    outline: none;
    border-color: var(--l-cyan);
    box-shadow: 0 0 14px rgba(0, 212, 255, 0.25);
}

.contact-field input:disabled[b-n39nf5jx6o],
.contact-field textarea:disabled[b-n39nf5jx6o] {
    opacity: 0.6;
}

.contact-actions[b-n39nf5jx6o] {
    margin-top: 1.6rem;
}

.contact-actions .btn[b-n39nf5jx6o] {
    min-width: 200px;
}

/* Honeypot: moved off-screen rather than display:none so naive bots still fill it in. */
.hp-field[b-n39nf5jx6o] {
    position: absolute;
    left: -9999px;
    top: auto;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.contact-success[b-n39nf5jx6o] {
    margin-top: 1.5rem;
    padding: 1.2rem 1.4rem;
    border: 1px solid var(--l-cyan);
    border-radius: 10px;
    background: rgba(0, 212, 255, 0.08);
    box-shadow: 0 0 18px rgba(0, 212, 255, 0.18);
}

.contact-success p[b-n39nf5jx6o] {
    margin-bottom: 0.4rem;
}

.contact-success p:last-child[b-n39nf5jx6o] {
    margin-bottom: 0;
}

.contact-error[b-n39nf5jx6o] {
    margin-top: 1.25rem;
    padding: 0.9rem 1.1rem;
    border: 1px solid #E85D5D;
    border-radius: 10px;
    background: rgba(232, 93, 93, 0.10);
    color: #F0A8A8;
    font-size: var(--l-fs-meta);
}

/* Portrait-first is the default single-column flow; nothing extra needed down to 720px,
   but give the panel a little breathing room on narrow screens. */
@media (max-width: 576px) {
    .contact-actions .btn[b-n39nf5jx6o] {
        width: 100%;
    }
}
/* /Components/Pages/Public/Faq.razor.rz.scp.css */
/* FAQ accordion (AB#749): native <details>/<summary> cards styled to match the arcade panels in the
   .landing marketing CSS (app.css). Inherits the .landing custom properties (--l-card, --l-border,
   etc.) since custom properties cascade into scoped markup. Fridge-safe (Chromium M94): no :has(),
   @container, CSS nesting, @layer, or subgrid — attribute selectors, flexbox, and transforms only.
   Summary rows keep a >=48px touch target for the Family Hub touchscreen. */

.faq-list[b-rdfjj7lnlj] {
    max-width: 720px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
}

.faq-item[b-rdfjj7lnlj] {
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: 14px;
    overflow: hidden;
    transition: border-color 0.15s ease;
}

.faq-item[open][b-rdfjj7lnlj] {
    border-color: var(--l-border-strong);
    box-shadow: 0 0 22px rgba(0, 212, 255, 0.08);
}

.faq-item summary[b-rdfjj7lnlj] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    min-height: 56px;
    padding: 1rem 1.25rem;
    cursor: pointer;
    list-style: none;
    -webkit-tap-highlight-color: transparent;
}

/* Hide the default disclosure triangle (M94 needs the -webkit pseudo). */
.faq-item summary[b-rdfjj7lnlj]::-webkit-details-marker {
    display: none;
}

.faq-item summary[b-rdfjj7lnlj]::marker {
    content: '';
}

.faq-q[b-rdfjj7lnlj] {
    font-family: 'Space Grotesk', system-ui, sans-serif;
    font-weight: 600;
    font-size: var(--l-fs-title);
    line-height: 1.35;
    color: var(--l-text-strong);
}

.faq-item[open] .faq-q[b-rdfjj7lnlj] {
    color: var(--l-white);
}

/* Chevron drawn with borders so no icon font or image is needed; rotates when open. */
.faq-chevron[b-rdfjj7lnlj] {
    flex-shrink: 0;
    width: 12px;
    height: 12px;
    border-right: 2px solid var(--l-cyan);
    border-bottom: 2px solid var(--l-cyan);
    transform: rotate(45deg);
    transition: transform 0.2s ease;
    margin-top: -4px;
}

.faq-item[open] .faq-chevron[b-rdfjj7lnlj] {
    transform: rotate(225deg);
    margin-top: 4px;
}

.faq-item summary:focus-visible[b-rdfjj7lnlj] {
    outline: 2px solid var(--l-cyan);
    outline-offset: -2px;
    border-radius: 14px;
}

.faq-a[b-rdfjj7lnlj] {
    padding: 0 1.25rem 1.15rem;
    text-align: left;
}

.faq-a p[b-rdfjj7lnlj] {
    font-size: var(--l-fs-body);
    line-height: 1.6;
    color: var(--l-text);
    margin: 0;
}

.faq-a strong[b-rdfjj7lnlj] {
    color: var(--l-text-strong);
    font-weight: 600;
}

/* Portrait fridge (720x1080) and phones: slightly tighter cards, same touch targets. */
@media (max-width: 720px) {
    .faq-item summary[b-rdfjj7lnlj] {
        padding: 0.9rem 1rem;
    }

    .faq-a[b-rdfjj7lnlj] {
        padding: 0 1rem 1rem;
    }
}
/* /Components/Pages/Public/Features.razor.rz.scp.css */
/* Scoped layout for the public Features page (AB#747). General marketing styles live in the
   .landing section of app.css; only the feature-block layout is page-specific. Fridge-safe
   (Chromium M94): no :has(), @container, @layer, subgrid, nesting, or text-wrap. The .landing
   CSS custom properties (--l-*) cascade in from the MarketingShell wrapper. */

.feature-block[b-b643qm6m8c] {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 1.75rem;
    background: var(--l-card);
    border: 1px solid var(--l-border);
    border-radius: 20px;
    padding: clamp(2rem, 4vw, 2.75rem);
    overflow: hidden;
}

/* Signature gradient hairline along the top, matching .landing .feature. */
.feature-block[b-b643qm6m8c]::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    background: var(--l-grad);
    opacity: 0.85;
}

/* Photography column (AB#812), replacing the pixel-art sprites. Each shot carries a rounded frame
   and an accent glow matched to its block so it reads as part of the neon-kitchen set. Capped and
   centred because this column goes full width when the block stacks below 760px, where an
   unconstrained photo would dwarf the copy beside it. */
.fb-art[b-b643qm6m8c] {
    display: flex;
    justify-content: center;
    align-items: center;
}
.fb-art img[b-b643qm6m8c] {
    display: block;
    width: 100%;
    max-width: 420px;
    height: auto;
    margin: 0 auto;
    border-radius: 12px;
    border: 1px solid var(--l-border);
}
.fb-art--cyan img[b-b643qm6m8c] { box-shadow: 0 0 22px rgba(0, 212, 255, 0.22); }
.fb-art--magenta img[b-b643qm6m8c] { box-shadow: 0 0 22px rgba(200, 80, 192, 0.22); }

/* The fridge is the only portrait frame; left at the shared cap it towers over its two bullets. */
.fb-art--portrait img[b-b643qm6m8c] { max-width: 260px; }

/* AB#1033. Nibble's block has no photograph, so the frame the other four get from their image has to
   be built here. A disc rather than a rounded rectangle, because a character wants a portrait frame
   and not a screenshot frame, and the dark centre inside the brand gradient for the same reason the
   chat bubble has one: he is white with a cyan and magenta edge, and both halves of that edge vanish
   against a mid-tone. */
.fb-art--nibble .nibble-disc[b-b643qm6m8c] {
    --nibble-size: 168px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 232px;
    height: 232px;
    max-width: 100%;
    border-radius: 50%;
    background:
        radial-gradient(circle at 50% 50%, var(--l-bg) 0 68%, transparent 69%),
        var(--l-grad);
    box-shadow: 0 0 22px rgba(0, 212, 255, 0.22);
}

.fb-copy[b-b643qm6m8c] { text-align: center; }
.fb-copy p[b-b643qm6m8c] { font-size: var(--l-fs-body); }

/* Magenta variant recolours the shared .landing .feature-icon chip (cyan is its default). */
.feature-block--magenta .feature-icon[b-b643qm6m8c] { color: var(--l-magenta); }

/* Icon and title on one line, icon leading. Stacked (below 760px) the whole copy column is centred,
   so the pair centres with it; side-by-side it flips to flex-start with the rest of the copy.
   align-items: center rather than baseline — the icon is a 48px chip, not a glyph, so matching the
   heading's baseline would sit it visibly low. */
.fb-head[b-b643qm6m8c] {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.9rem;
    margin-bottom: 0.85rem;
    text-align: left;
}

/* The chip must not absorb the squeeze when a long title wraps beside it. */
.fb-head .feature-icon[b-b643qm6m8c] { flex-shrink: 0; }

/* .landing h2 carries the 0.85rem gap that now belongs to the row. */
.fb-head h2[b-b643qm6m8c] { margin-bottom: 0; }

/* Bullet list: square pixel markers in the block's accent colour. */
.fb-points[b-b643qm6m8c] {
    list-style: none;
    margin: 1.15rem auto 0;
    max-width: 460px;
    text-align: left;
}
.fb-points li[b-b643qm6m8c] {
    position: relative;
    padding-left: 1.35rem;
    margin-bottom: 0.6rem;
    font-size: var(--l-fs-body);
}
.fb-points li[b-b643qm6m8c]::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0.55em;
    width: 8px;
    height: 8px;
    background: var(--l-cyan);
}
.feature-block--magenta .fb-points li[b-b643qm6m8c]::before { background: var(--l-magenta); }

/* Inline link to /fridge: 48px touch target for the Family Hub. */
.fb-link[b-b643qm6m8c] {
    display: inline-flex;
    align-items: center;
    min-height: 48px;
    margin-top: 0.5rem;
    font-weight: 600;
}

/* Side-by-side from 760px up (the 720px fridge portrait stays stacked, which reads better there). */
@media (min-width: 760px) {
    .feature-block[b-b643qm6m8c] { flex-direction: row; align-items: center; gap: 2.5rem; }
    .feature-block--rev[b-b643qm6m8c] { flex-direction: row-reverse; }
    .fb-art[b-b643qm6m8c] { flex: 0 0 34%; }
    .fb-copy[b-b643qm6m8c] { flex: 1 1 auto; text-align: left; }
    .fb-head[b-b643qm6m8c] { justify-content: flex-start; }
    .fb-points[b-b643qm6m8c] { margin-left: 0; margin-right: 0; }
}
/* /Components/Pages/Public/Fridge.razor.rz.scp.css */
/* Page-specific styles for the public fridge landing (AB#751). Everything else reuses the shared
   .landing CSS in app.css. Fridge-safe (Chromium M94): plain selectors only - no CSS nesting,
   :has(), @container, @layer, or subgrid. The --l-* custom properties are inherited from the
   .landing wrapper in MarketingShell. */

/* --- Hero fridge illustration (pixel-art rect sprite, same idiom as the HomeLanding sprites) --- */
.fridge-sprite[b-0jo3yfucgz] {
    display: block;
    width: 108px;
    height: 156px;
    margin: 0 auto 1.6rem;
    filter: drop-shadow(0 0 14px rgba(0, 212, 255, 0.35));
    animation: fridge-hum-b-0jo3yfucgz 3.2s ease-in-out infinite;
}
.fridge-sprite svg[b-0jo3yfucgz] {
    width: 100%;
    height: 100%;
    shape-rendering: crispEdges;
}
@keyframes fridge-hum-b-0jo3yfucgz {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-6px); }
}
@media (prefers-reduced-motion: reduce) {
    .fridge-sprite[b-0jo3yfucgz] { animation: none; }
}

/* --- Setup steps: numbered list with arcade-style badges. Touch-safe row heights (48px+). --- */
.setup-intro[b-0jo3yfucgz] {
    text-align: center;
    margin-bottom: 1.75rem;
}
.setup-steps[b-0jo3yfucgz] {
    list-style: none;
    margin: 0 auto 1.5rem;
    padding: 0;
    max-width: 640px;
}
.setup-steps li[b-0jo3yfucgz] {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: 0.85rem 0;
    min-height: 48px;
    border-bottom: 1px solid var(--l-border);
}
.setup-steps li:last-child[b-0jo3yfucgz] {
    border-bottom: none;
}
.step-num[b-0jo3yfucgz] {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 10px;
    background: var(--l-card-2);
    border: 1px solid var(--l-border-strong);
    color: var(--l-cyan);
    text-shadow: 0 0 8px rgba(0, 212, 255, 0.45);
}
.setup-steps li:nth-child(even) .step-num[b-0jo3yfucgz] {
    color: var(--l-magenta);
    text-shadow: 0 0 8px rgba(200, 80, 192, 0.45);
}
.step-text[b-0jo3yfucgz] {
    flex: 1 1 auto;
    padding-top: 0.45rem;
    color: var(--l-text);
    /* The panel centres its text, which is right for the heading and intro but wrong for these rows:
       centred copy beside a fixed left-hand number badge gives every step a different left edge and
       reads as ragged. Instructions are a list, so they align to the badge. */
    text-align: left;
}
.setup-note[b-0jo3yfucgz] {
    text-align: center;
    font-size: var(--l-fs-meta);
    color: var(--l-muted);
    max-width: 640px;
    margin: 0 auto;
}

/* At fridge width (720px portrait falls in the sm-and-up range) keep the hero sprite a touch
   larger so it reads from across the kitchen. */
@media (min-width: 576px) {
    .fridge-sprite[b-0jo3yfucgz] {
        width: 126px;
        height: 182px;
    }
}
/* /Components/Pages/Public/HowItWorksPublic.razor.rz.scp.css */
/* Public How It Works step panels (AB#748). Page-specific styling only - shared marketing look
   (.panel, .section-head, .cta-row, etc.) comes from the .landing rules in app.css. Fridge-safe
   (Chromium M94): no :has(), @container, @layer, subgrid, or CSS nesting. Portrait-first single
   column; two-up on wide screens. Color vars (--l-cyan, --l-magenta, ...) inherit from .landing. */

.hiw-steps[b-xrvy8f5auq] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.25rem;
}

/* These wear .landing .panel for their surface, but a panel centres its content and a step card
   should read like the feature grid cards on the home page: icon and title flush left, body copy
   left-aligned underneath (AB#927). Undo the panel's centring here rather than at the panel level,
   which the marketing sections still want. */
.hiw-step[b-xrvy8f5auq] {
    position: relative;
    overflow: hidden;
    text-align: left;
    /* .landing .panel's own radius (20px) and clamped padding are tuned for full-width marketing
       sections. These are cards in a grid, so they take the .landing .feature card's geometry
       instead — same radius, same padding, same chip, same hairline. */
    border-radius: 16px;
    padding: 1.75rem 1.6rem;
}

/* Identical to .landing .feature::before: one signature-gradient hairline, 2px, at 0.85 opacity.
   The per-variant gradients this used to carry made the step cards read as a different component
   from the feature cards sitting a scroll away; the accent now lives only in the icon, exactly as
   it does there. */
.hiw-step[b-xrvy8f5auq]::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    background: var(--l-grad);
    opacity: 0.85;
}

/* Icon leads the title on one line, matching .fb-head on Features and the .feature grid cards
   (AB#927). The step number that used to lead this row is gone — the <ol> still carries the ordering
   for assistive tech, and the badge was aria-hidden anyway, so nothing was lost but noise. */
.hiw-step-head[b-xrvy8f5auq] {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    margin-bottom: 0.75rem;
}

/* Same chip as .landing .feature-icon: 48px, 12px radius, border-strong. */
.hiw-step-icon[b-xrvy8f5auq] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    border-radius: 12px;
    background: var(--l-card-2);
    border: 1px solid var(--l-border-strong);
    color: var(--l-cyan);
    flex-shrink: 0;
}

.hiw-step--magenta .hiw-step-icon[b-xrvy8f5auq] { color: var(--l-magenta); }

.hiw-step-icon svg[b-xrvy8f5auq] {
    width: 24px;
    height: 24px;
}

/* Sits inside .hiw-step-head now, which carries the gap before the body copy. */
.hiw-step h3[b-xrvy8f5auq] {
    color: var(--l-text-strong);
    font-size: var(--l-fs-title);
    font-weight: 600;
    margin-bottom: 0;
}

/* max-width: none undoes the shared .landing .panel cap of 660px. That cap only ever bit on
   .hiw-step--wide (which spans both columns); now that the card is left-aligned the copy should run
   the card's full width, the way the feature grid cards do. */
.hiw-step p[b-xrvy8f5auq] {
    font-size: var(--l-fs-body);
    max-width: none;
    margin: 0;
}

/* "...then it starts over" note under the steps */
.hiw-repeat[b-xrvy8f5auq] {
    display: flex;
    align-items: flex-start;
    gap: 0.9rem;
    max-width: 640px;
    margin: 1.75rem auto 0;
    padding: 1rem 1.25rem;
    border: 1px dashed var(--l-border-strong);
    border-radius: 12px;
    background: rgba(30, 36, 56, 0.6);
}

.hiw-repeat-icon[b-xrvy8f5auq] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    color: var(--l-purple);
    flex-shrink: 0;
}

.hiw-repeat-icon svg[b-xrvy8f5auq] {
    width: 24px;
    height: 24px;
}

.hiw-repeat p[b-xrvy8f5auq] {
    margin: 0;
    font-size: var(--l-fs-body);
}

/* Two-up on wide screens; the fridge (720px portrait) and phones stay single column */
@media (min-width: 860px) {
    .hiw-steps[b-xrvy8f5auq] {
        grid-template-columns: 1fr 1fr;
        gap: 1.5rem;
    }

    /* Odd step out: the final "Cook a meal" payoff spans both columns instead of orphaning */
    .hiw-step--wide[b-xrvy8f5auq] {
        grid-column: 1 / -1;
    }
}
/* /Components/Pages/Share/SharedShoppingListPage.razor.rz.scp.css */
/* The shared shopping list page (AB#1051).

   Read one-handed, at arm's length, in a shop with bad light, by somebody who has never seen this app
   before. That is a narrower brief than any page inside the app and it drives every choice here: rows
   are large and flat, there is nothing to discover by hovering, and the only thing that can be pressed
   is the whole row.

   Chromium M94 (the fridge) is the floor, so no @container, no :has(), no nesting, no subgrid. Nothing
   below needs any of them. */

.shared-list[b-51eem96h1s] {
    max-width: 44rem;
    margin: 0 auto;
}

/* ── Heading ──────────────────────────────────────────────────────── */

.shared-list-header[b-51eem96h1s] {
    margin-bottom: 1.25rem;
}

.shared-list-header h1[b-51eem96h1s] {
    font-size: 1.5rem;
    margin: 0 0 0.35rem;
    color: var(--color-text-primary);
    /* The list's own name, typed by a household. Long ones wrap rather than run off a 375px screen. */
    overflow-wrap: anywhere;
}

.shared-list-meta[b-51eem96h1s] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    font-size: 0.875rem;
    color: var(--color-text-secondary);
}

.shared-list-live[b-51eem96h1s] {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--color-cyan);
}

/* A steady dot, not a pulsing one. This page is read while walking, and something blinking in the
   corner of a list you are scanning is a distraction rather than a reassurance. */
.shared-list-live-dot[b-51eem96h1s] {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: var(--color-cyan);
}

/* ── Notices ──────────────────────────────────────────────────────── */

.shared-list-notice[b-51eem96h1s],
.shared-list-error[b-51eem96h1s],
.shared-list-empty[b-51eem96h1s] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    border: 1px solid var(--color-border);
    border-radius: 0.5rem;
    background: var(--color-bg-surface);
    font-size: 0.9375rem;
}

.shared-list-notice[b-51eem96h1s] {
    color: var(--color-text-secondary);
}

.shared-list-error[b-51eem96h1s] {
    color: var(--color-danger);
    border-color: var(--color-danger);
}

.shared-list-empty[b-51eem96h1s] {
    color: var(--color-text-secondary);
    justify-content: center;
}

.shared-list-notice[b-51eem96h1s],
.shared-list-error[b-51eem96h1s] {
    margin-bottom: 1rem;
}

/* ── Rows ─────────────────────────────────────────────────────────── */

.shared-list-items[b-51eem96h1s] {
    list-style: none;
    margin: 0;
    padding: 0;
}

.shared-list-heading[b-51eem96h1s] {
    /* An aisle, not a row: no border, no padding to match, and set apart from the items under it so a
       thumb scrolling past can find where one aisle ends. */
    margin: 1.25rem 0 0.35rem;
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.shared-list-heading:first-child[b-51eem96h1s] {
    margin-top: 0;
}

.shared-list-item[b-51eem96h1s] {
    border-bottom: 1px solid var(--color-border-subtle);
}

.shared-list-item:last-child[b-51eem96h1s] {
    border-bottom: none;
}

/* The row is the target. A label wraps the checkbox, so pressing anywhere along the line ticks the
   item — which is what somebody holding a basket in the other hand can actually manage. Well past the
   48px floor because the miss here costs a wrong item rather than a wrong tab. */
.shared-list-row[b-51eem96h1s] {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    min-height: var(--touch-min);
    padding: 0.6rem 0.25rem;
    margin: 0;
    color: var(--color-text-primary);
}

/* Only the tickable row, which is a label wrapping a checkbox. A read-only list renders the same class
   on a span, and a pointer cursor there promises a press that would never answer. */
label.shared-list-row[b-51eem96h1s] {
    cursor: pointer;
}

/* No hover rule anywhere on this page. On the fridge and on a phone there is no hover, and a highlight
   that only some readers ever see is a highlight the layout cannot rely on. */

.shared-list-row .form-check-input[b-51eem96h1s] {
    flex: 0 0 auto;
    width: 1.5rem;
    height: 1.5rem;
    margin: 0;
}

/* The read-only stand-in for the checkbox. Same width, so a read-only list and a tickable one line up
   identically and neither reads as the other with something missing. */
.shared-list-mark[b-51eem96h1s] {
    flex: 0 0 auto;
    width: 1.5rem;
    text-align: center;
    color: var(--color-text-muted);
}

.shared-list-text[b-51eem96h1s] {
    flex: 1 1 auto;
    min-width: 0;
}

.shared-list-description[b-51eem96h1s] {
    display: block;
    overflow-wrap: anywhere;
}

.shared-list-hint[b-51eem96h1s] {
    display: block;
    font-size: 0.8125rem;
    color: var(--color-text-muted);
}

.shared-list-qty[b-51eem96h1s] {
    flex: 0 0 auto;
    min-width: 2rem;
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--color-text-secondary);
}

/* Ticked. Struck through and dropped to the secondary colour rather than filled or hidden: what has
   been picked up stays legible and stays where it was, so the row under a thumb never moves. */
.shared-list-item.is-checked .shared-list-description[b-51eem96h1s] {
    text-decoration: line-through;
}

.shared-list-item.is-checked .shared-list-row[b-51eem96h1s],
.shared-list-item.is-checked .shared-list-qty[b-51eem96h1s] {
    color: var(--color-text-secondary);
}

/* ── Expiry, and the dead link ────────────────────────────────────── */

.shared-list-footer[b-51eem96h1s] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 1.5rem;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border-subtle);
    font-size: 0.875rem;
    color: var(--color-text-muted);
}

.shared-list-unavailable[b-51eem96h1s] {
    text-align: center;
    padding: 3rem 1rem;
    color: var(--color-text-secondary);
}

.shared-list-unavailable i[b-51eem96h1s] {
    font-size: 3rem;
    display: block;
    margin-bottom: 1rem;
    color: var(--color-text-muted);
}

.shared-list-unavailable h1[b-51eem96h1s] {
    font-size: 1.25rem;
    color: var(--color-text-primary);
    margin-bottom: 0.5rem;
}
/* /Components/Pages/ShoppingLists/ShoppingListDetail.razor.rz.scp.css */
/* The name field is the page title, so it reads as a heading rather than a form control: no box until
   it is focused, which is what says the text is editable without the page growing a second toolbar.
   Size and weight are inherited from the title row it now sits in (AB#923), so a rename field and a
   printed page title are the same words at the same size on every detail page. */
.shopping-list-title[b-9uw12rne1s] {
    font-size: inherit;
    font-weight: inherit;
    min-height: var(--touch-min);
    padding-left: 0;
    background-color: transparent;
    border-color: transparent;
}

.shopping-list-title:hover[b-9uw12rne1s] {
    border-color: var(--color-border);
}

.shopping-list-title:focus[b-9uw12rne1s] {
    background-color: var(--color-bg-subtle);
    padding-left: 0.75rem;
}

/* Rule 10 for the controls this page adds. The app puts its inputs on the touch floor per context —
   `.filter-bar .btn`, `.search-field input`, `.blazor-dialog-body .form-control` — and this page's are
   in none of them, so they rendered at Bootstrap's 38px beside 48px buttons and the add row could not
   line up at either edge.

   This is not the per-screen override AB#902 reverted. That one pushed these two screens *past* the
   rest of the app, which made them the only ones with taller controls; this brings them up to the same
   floor every other control row already sits on.

   ::deep because the add row's fields are InputText/InputNumber components: CSS isolation only stamps
   its scope attribute on elements written in this file, so a plain descendant selector would compile to
   something their rendered <input> never matches. */
.shopping-add-row[b-9uw12rne1s]  .form-control {
    min-height: var(--touch-min);
}

.shopping-items .list-group-item[b-9uw12rne1s] {
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
}

/* The footer of the export menu. A rule separates it from the items so the text does not read as a fifth
   thing to press, and the whole block is muted and smaller because it is a caution rather than a choice.

   No ::deep: this markup is written in ShoppingListDetail.razor, inside the menu's ChildContent, so it
   carries this page's scope attribute even though the box around it belongs to RowActionMenu.

   max-width rather than a width, and wrapping text: the menu sizes itself to its widest child, and a
   single-line note would stretch a 12rem menu of four short labels into a paragraph-wide one. */
.export-note[b-9uw12rne1s] {
    display: flex;
    gap: 0.5rem;
    max-width: 15rem;
    margin-top: 4px;
    padding: 0.5rem 0.75rem 0.25rem;
    border-top: 1px solid var(--color-border);
    color: var(--color-text-secondary);
    font-size: 0.8rem;
    line-height: 1.35;
}

.export-note > .bi[b-9uw12rne1s] {
    flex-shrink: 0;
    /* Nudged onto the first line's baseline; flex-start alone leaves the glyph riding high. */
    padding-top: 0.05rem;
}

/* Same reasoning as the title: every field on a row is always editable, so a list of twenty items must
   not read as a wall of twenty boxed forms. The box appears on hover and focus, which is where it is
   needed, and the row reads as a list the rest of the time. */
.shopping-field[b-9uw12rne1s] {
    /* On the floor for the same reason the add row is: these are the controls a thumb lands on in a
       store, and they were the shortest thing on the page at 38px. Borderless until hover keeps twenty
       of them from reading as twenty boxed forms; it was never a reason for them to be small. */
    min-height: var(--touch-min);
    background-color: transparent;
    border-color: transparent;
}

.shopping-field:hover[b-9uw12rne1s] {
    border-color: var(--color-border);
}

.shopping-field:focus[b-9uw12rne1s] {
    background-color: var(--color-bg-subtle);
}

/* A name too long for even the widened column ends in an ellipsis rather than mid-word (AB#1064). An
   <input> is the one control that overflows silently: the text scrolls out of view with no wrap and no
   marker, so "Habanero & Green Chile Smoked Chicken Sausage" read as "Habanero & Green Chile Smoked
   Chic" and nothing on the row said there was more.

   text-overflow applies to an input only while it is not focused, which is exactly the division wanted
   here: the ellipsis is a reading cue, and clicking in to edit hands the field back to the caret so the
   end of the text is still reachable. */
.shopping-item-description:not(:focus)[b-9uw12rne1s] {
    text-overflow: ellipsis;
}

/* The row's notes, in their own column beside the name rather than stacked under it. Stacked, a row
   carrying a note stood two lines tall next to a row that carried none, and a list of twenty read as
   ragged even though every row holds the same fields. From `sm` up this is also the column that takes
   the row's leftover width, which is what holds the controls out at the right edge; see below.

   Below `sm` the notes fall back under the row, indented by the description input's own padding so
   they start under the first letter of the name rather than hanging off the left edge. A phone has no
   second column to give, and a ragged list is better than a name squeezed to three characters. */
.shopping-item-notes[b-9uw12rne1s] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.875rem;
    padding-left: 0.75rem;
}

/* From `sm` up the notes are the column that takes the row's leftover width, and their text sits at its
   left edge, hard against the name. They used to be content-sized and right-aligned, which read as
   "beside the name" on the fridge only because the row was narrow enough that there was nowhere else to
   be. On a desktop window the same rule put "Need 6 cups" out at the right margin, a foot from the
   "rolled oats" it describes and close enough to the quantity box to read as a second number competing
   with it. A note is about the item, so it belongs against the item's name at every width, and the
   slack belongs between the note and the controls.

   Growing rather than content-sized is also what keeps the quantity and delete columns pinned right.
   Nothing else on the row grows, so left content-sized the leftover width piled up after the last
   column and every row ended wherever its own notes happened to.

   A long recipe name no longer needs its own cap: the column cannot exceed the space the name left it,
   so the name wraps inside it. Wrapping rather than truncating, still, because a cut-off recipe name
   loses exactly the part that makes it recognisable, which is the opposite of what a line answering
   "why is this on here" is for.

   The right padding is the gap to the quantity box, and it has to be spelled out because that box is
   borderless until it is hovered: on a tight row this column sits at its floor and a muted "Need 6 cups"
   would end against a muted "Qty" placeholder, reading as one run-on phrase rather than a note and a
   field. The floor is what keeps the shortest amount on one line once the name has taken everything it
   is allowed to, and 8rem is that amount plus the padding: enough for "Need 12 tbsp" without wrapping. */
@media (min-width: 576px) {
    .shopping-item-notes[b-9uw12rne1s] {
        justify-content: flex-start;
        min-width: 8rem;
        padding-left: 0;
        padding-right: 1.5rem;
        /* One line. An ingredient several recipes all asked for carries all of their names, and six of
           them comma-joined wrapped onto a second line, so that row stood twice as tall as the thirty
           around it and the list came apart exactly where it was busiest. It truncates instead; the
           full text is on the title attribute. */
        flex-wrap: nowrap;
    }

    /* Fixed slots, so what follows them starts at the same place on every row. The package size and the
       amount needed are both optional, and in a plain flex row an absent one collapses and drags the
       source left, which left "Household staple" and the recipe that asked starting wherever their own
       row happened to end. Reserving the width is the same trade `.shopping-qty-col` already makes to
       keep the add row and the item rows reading as one grid.

       Only from `sm` up. Below it the notes wrap under the row, where there is no column to line up
       with and reserving a phone's width for an empty slot buys nothing. */
    .shopping-note-slot[b-9uw12rne1s] {
        flex: 0 0 auto;
        /* Its own content can be longer than the slot; let it sit rather than push the row wider. */
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .shopping-note-package[b-9uw12rne1s] {
        width: 6.5rem;
    }

    .shopping-note-needed[b-9uw12rne1s] {
        width: 7.5rem;
    }
}

/* The aisle a row is filed under, and the way to correct it (AB#1036). A select rather than a label
   because the aisle is worked out by matching the item's name against the pantry, and a name match can
   land wrong; an aisle you can see but not fix is worse than one you never saw.

   Capped, because it is the least important thing in a column whose job is the package size and the
   recipe that asked. Left uncapped it took the width of the longest category the household has and
   pushed both of those off the row on a fridge door.

   The cap has to clear a real aisle name, though, and real ones are long. A household's categories come
   from the supermarket, so they read "Dairy, Eggs & Cheese", "Cookies, Snacks & Candy", "Condiments,
   Spice & Bake" — around twenty-five characters, not the tidy single words a made-up example uses. At
   9rem they truncated mid-word into the chevron, which made the one control on the row whose whole job
   is to say where something is filed the one control that could not say it.

   A max-width rather than a width, so a short name still takes only the room it needs and the package
   hint moves up beside it. */
.shopping-item-category[b-9uw12rne1s] {
    max-width: 16rem;
    /* Bootstrap paints a chevron into the padding; without the room for it a category name runs
       underneath the arrow. */
    padding-right: 1.75rem;
}

/* The aisle heading. Not a row: no border, no hover, no touch floor — it is a label over the rows
   below it, and giving it row treatment made it look like an item you could tick off. */
.shopping-category-heading[b-9uw12rne1s] {
    padding: 0.75rem 0 0.25rem;
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
    list-style: none;
}

/* Except the first, which would otherwise open the panel with a gap the panel's own padding already
   provides. */
.shopping-category-heading:first-child[b-9uw12rne1s] {
    padding-top: 0;
}

/* Capped rather than greedy. The name is the only field on the row that could use unlimited width, and
   handing it all of it is what pushed everything describing the item out to the right margin: on a wide
   window "rolled oats" sat at one end of a 1200px field with its note at the other. Two words is a long
   grocery item and 20rem holds around forty characters, so the cap costs nothing anybody types and buys
   back the distance between an item and the line describing it.

   The cap only binds above the fridge, where there is more width than the row can use: at 720px the name
   settles around 18rem on its own and never reaches it. Tightening this number is therefore a desktop
   change and cannot narrow the field on the device the page was laid out for.

   A cap on the width rather than a wide flex-basis, which is the version of this that does not work: the
   row wraps, and a flex line is broken up using each column's basis *before* anything is allowed to
   shrink. A 20rem basis therefore does not shrink to fit a 720px fridge, it folds the quantity and
   delete onto a second line. Growing from zero into a cap never puts that pressure on the line.

   The lopsided grow is what makes the name take the slack first and the notes take only what is left
   over: both columns grow from a zero basis, so without it they would split the row down the middle and
   a two-word item name would sit in a field twice the size of its own text. It also means every row
   divides the same way regardless of what its notes say, which is what keeps the notes lined up in a
   column instead of starting at a different place on each row. */
@media (min-width: 576px) {
    .shopping-item-name-col[b-9uw12rne1s] {
        flex-grow: 999;
        max-width: 20rem;
    }
}

/* 20rem holds about 36 characters of a real grocery name, which is well short of what one is called:
   "Coffee mate Coconut Creme Liquid Coffee Creamer" is 47 and arrived cut to "Coffee mate Coconut Creme
   Liquid C" (AB#1064).

   28rem, measured rather than picked. Five real receipt and pantry names were rendered in Inter at the
   field's own size: the widest, the Coffee mate one, needs 417px including the form-control's padding
   and border. 26rem is 416px and misses it by a pixel; 28rem is 448px, fits all five, and holds 51
   characters of a worst-case name made only of wide letters. That is the 45-to-50 the bug asked for
   with a little room, and no more than that: every rem here is distance between an item and its note.

   Raised at `lg` rather than by editing the 20rem above, because the reason the original cap exists
   does not go away. It is that distance, and the trade is only worth re-taking where the row has width
   going spare. At 992px and up it has, which is the bug's own observation: the notes and category
   columns sit in mostly empty space. Below it nothing changes, which settles the fridge at 720px and
   tablet portrait at 768px by leaving them alone rather than by argument. */
@media (min-width: 992px) {
    .shopping-item-name-col[b-9uw12rne1s] {
        max-width: 28rem;
    }
}

/* A quantity on a shopping list is a count of things to pick up — one or two digits, occasionally
   three. A twelfth of the row was sized for the grid rather than for what goes in it, and it left the
   field looking like the widest thing on a row it says the least about. Fixed rather than a column
   fraction so the add row and the item rows below it stay the same width at every size, which is what
   makes the two read as one grid. Below `sm` it stays col-4, pairing with the add row's col-8. */
@media (min-width: 576px) {
    .shopping-qty-col[b-9uw12rne1s] {
        width: 4.75rem;
    }
}

/* Muted and unboxed: these are the shelf and the recipe answering "which one" and "who asked", not
   questions being put to the shopper, and they must not read as a second field beside the name. */
.shopping-package-hint[b-9uw12rne1s],
.shopping-amount-needed[b-9uw12rne1s] {
    color: var(--color-text-muted);
}

/* The run of recipes a row came from, and the staple label that answers the same question. It takes
   whatever the fixed slots before it left, and truncates rather than wrapping. An ingredient the whole
   week wanted names every recipe that asked for it, and those names comma-joined are longer than any
   row: wrapped, one busy row stood two lines tall among thirty that stood one. min-width: 0 because a
   flex item will not shrink below its content without it, so the ellipsis would never engage and the
   row would simply widen. */
.shopping-item-source[b-9uw12rne1s] {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The names inside are links, so they are coloured like links. They are the only thing on a row that
   navigates, and making them look like the muted notes beside them would hide the one tap that answers
   "why is this on here". The icon and the commas between them stay plain text, which is what they are:
   a row from three recipes has three links, not one long one. */
.shopping-item-source a[b-9uw12rne1s] {
    text-decoration: none;
}

.shopping-item-source a:hover[b-9uw12rne1s] {
    text-decoration: underline;
}

/* Struck through with the name above it: a ticked item is done, and a size hint that stays bright reads
   as something still outstanding. The recipe link is deliberately left out of this — it is a way back to
   something, not a statement about what is outstanding, and a struck-through link reads as broken. */
.shopping-item-checked .shopping-package-hint[b-9uw12rne1s],
.shopping-item-checked .shopping-amount-needed[b-9uw12rne1s] {
    text-decoration: line-through;
}

.shopping-item-checked .shopping-item-description[b-9uw12rne1s] {
    text-decoration: line-through;
    color: var(--color-text-secondary);
}

/* Printed only, so it is out of the way on screen. */
.shopping-print-header[b-9uw12rne1s] {
    display: none;
}

/* ─── On paper (AB#1036) ────────────────────────────────────────────────────────────────────────────
   What a printed shopping list is: the name, the day it was taken, and the items. Everything that
   exists to change the list goes, because paper cannot be changed — the add form, the delete buttons,
   the quantity boxes as boxes, the shopped notice, the progress rail, the export menu itself.

   The app's chrome (nav, chat, toasts, banners) is hidden by the print block in app.css, which is not
   this page's business and would be wrong to state twice. What is left here is the page's own content.

   ::deep throughout, because almost none of these elements are written in this file: the title row and
   the panels come from DetailPageLayout and DetailPanel, and CSS isolation only stamps its scope
   attribute on markup a component renders itself. The scope lands on `.body-container`, which is this
   page's own outermost element, so every rule below reads as "inside this page".

   Nothing here is past the fridge's ~M94 budget, which matters less for a stylesheet nobody prints from
   a fridge door but is the standing rule and costs nothing to keep. */
@media print {
    /* Two things at once. The height and overflow are the scroll container: left alone it is one
       viewport tall with its overflow hidden, so a list of thirty items prints the first eight and
       stops. The background is the photograph and its tint, which is most of a colour cartridge and
       which turns black text on white paper grey. */
    .body-container[b-9uw12rne1s] {
        height: auto;
        overflow: visible;
        background: none;
        margin-left: 0;
        margin-right: 0;
        padding: 0;
    }

    .shopping-print-header[b-9uw12rne1s] {
        display: block;
        margin-bottom: 1rem;
    }

    .shopping-print-header h2[b-9uw12rne1s] {
        margin: 0;
        font-size: 1.25rem;
    }

    .shopping-print-header p[b-9uw12rne1s] {
        margin: 0;
    }

    /* The title row goes with them: its name is an <input>, and the print header above says the same
       thing in text. The back link, the export menu and the state button are all ways of leaving or
       changing the list, which paper is not. */
    .body-container[b-9uw12rne1s]  .page-header,
    .body-container[b-9uw12rne1s]  [data-testid="btn-back"],
    .body-container[b-9uw12rne1s]  .detail-rail,
    .body-container[b-9uw12rne1s]  [data-testid="panel-add-item"],
    .body-container[b-9uw12rne1s]  [data-testid="shopped-notice"],
    .body-container[b-9uw12rne1s]  [data-testid="empty-shopping-list"] .btn {
        display: none;
    }

    /* The items panel loses its card. On screen the border separates it from the panels around it;
       printed it is the only thing on the sheet, and a box around the whole page is a box around
       nothing. */
    .body-container[b-9uw12rne1s]  .detail-panel {
        background: none;
        border: none;
        padding: 0;
        margin-bottom: 0;
    }

    .body-container[b-9uw12rne1s]  .detail-panel-header {
        display: none;
    }

    /* The row's own controls, as marks on paper rather than as fields. The tick box prints as an empty
       box or a ticked one, which is exactly what somebody working down a list on paper wants, so it
       stays; the quantity prints as its value with the field around it gone; the remove button and the
       notes have no meaning on a sheet that cannot be edited. */
    .shopping-items .btn[b-9uw12rne1s],
    .shopping-item-notes[b-9uw12rne1s] {
        display: none;
    }

    /* The name prints whole. The screen cap exists to keep an item near the note describing it, and
       print hides the notes entirely, so on paper it buys nothing and costs the end of the name: a
       sheet reading "Coffee mate Coconut Creme Liquid Co…" is the one thing a printed list must not do.
       Both halves are needed. The `lg` cap cannot match a sheet narrower than 992px, which leaves the
       20rem one binding, and the ellipsis is unconditional, so without this it would follow the name
       onto paper and mark the truncation rather than undo it. */
    .shopping-item-name-col[b-9uw12rne1s] {
        max-width: none;
    }

    .shopping-item-description[b-9uw12rne1s] {
        text-overflow: clip;
    }

    .shopping-items .list-group-item[b-9uw12rne1s] {
        background: none;
        border: none;
        border-bottom: 1px solid #999;
        padding: 0.25rem 0;
        color: #000;
        /* A row split across a page break loses its tick box or its name to the other sheet. */
        page-break-inside: avoid;
    }

    /* The aisle headings are the reason the printout is worth carrying round a shop, so they print in
       black and heavier than the screen shows them. break-after stops a heading printing as the last
       line of a sheet with its aisle beginning on the next one. */
    .shopping-category-heading[b-9uw12rne1s] {
        color: #000;
        font-size: 0.9rem;
        padding: 0.6rem 0 0.15rem;
        page-break-after: avoid;
    }

    /* The 48px thumb target around the checkbox, which on paper is an inch of white space per row and
       would push a twenty-item list onto two pages. */
    .shopping-items .check-target[b-9uw12rne1s] {
        min-height: 0;
        min-width: 0;
    }

    .shopping-field[b-9uw12rne1s],
    .shopping-item-description[b-9uw12rne1s] {
        border: none;
        background: none;
        color: #000;
        min-height: 0;
        padding: 0;
    }
}
/* /Components/Pages/Terms.razor.rz.scp.css */
.privacy-card[b-mp7a2pz7rb] {
    background: var(--color-bg-surface, #222840);
    border: 1px solid var(--color-border, #2E3550);
    border-radius: 1.25rem;
    padding: 2.5rem 2.75rem;
    width: 100%;
    max-width: 680px;
    margin: 0 auto;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
    position: relative;
    overflow: hidden;
}

.privacy-card[b-mp7a2pz7rb]::before {
    content: '';
    position: absolute;
    top: -60px;
    right: -60px;
    width: 240px;
    height: 240px;
    background: radial-gradient(ellipse at center, rgba(0, 212, 255, 0.08) 0%, transparent 70%);
    pointer-events: none;
}

.privacy-logo-wrap[b-mp7a2pz7rb] {
    text-align: center;
    margin-bottom: 1.25rem;
}

.privacy-logo[b-mp7a2pz7rb] {
    width: 64px;
    height: 64px;
    border-radius: 12px;
}

.privacy-title[b-mp7a2pz7rb] {
    font-family: 'Space Grotesk', system-ui, sans-serif;
    font-size: 1.75rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--color-text-primary, #fff);
    margin-bottom: 0.25rem;
    text-align: center;
}

.privacy-updated[b-mp7a2pz7rb] {
    font-size: 0.8rem;
    color: var(--color-text-muted, #5A6480);
    text-align: center;
    margin-bottom: 1.75rem;
}

.privacy-card h2[b-mp7a2pz7rb] {
    font-family: 'Space Grotesk', system-ui, sans-serif;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--color-cyan, #00D4FF);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-top: 1.75rem;
    margin-bottom: 0.5rem;
}

.privacy-card p[b-mp7a2pz7rb] {
    font-size: 0.92rem;
    line-height: 1.7;
    color: var(--color-text-secondary, #A0AABB);
    margin-bottom: 0.75rem;
}

.privacy-card ul[b-mp7a2pz7rb] {
    padding-left: 1.5rem;
    margin-bottom: 0.75rem;
}

.privacy-card li[b-mp7a2pz7rb] {
    font-size: 0.92rem;
    line-height: 1.7;
    color: var(--color-text-secondary, #A0AABB);
    margin-bottom: 0.2rem;
}

.privacy-card strong[b-mp7a2pz7rb] {
    color: var(--color-text-primary, #fff);
    font-weight: 600;
}

.privacy-card a[b-mp7a2pz7rb] {
    color: var(--color-cyan, #00D4FF);
    text-decoration: none;
}

.privacy-card a:hover[b-mp7a2pz7rb] {
    color: var(--color-primary-hover, #00BBDD);
}

.privacy-footer[b-mp7a2pz7rb] {
    margin-top: 2rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--color-border, #2E3550);
    text-align: center;
    font-size: 0.875rem;
}
