﻿.loadingContainer {
    width: 50%;
    margin-left: auto;
    margin-right: auto;
    margin-top: 10%;
    font-size: 18px;
    text-align: center;
}
/* Roster calendar ------------------------------------------------------------------------------
   The time grid is a plain CSS grid of day columns; shifts are absolutely positioned inside their
   column, with top/height derived from their start and duration as a percentage of the 24h day. That
   keeps the layout arithmetic in one place (the percentage) and lets the grid scale with the viewport
   without the component knowing any pixel heights. */
.roster-grid {
    position: relative;
    display: grid;
    /* The column template is set inline - the grid is days across, each split into the market's roles, so
       the count depends on the view and on how many roles are shown. The two header rows use the same
       template, which is what keeps each label over its own column. */
    /* One pixel per minute, so a 30 minute shift stays legible without a zoom control. The height itself is
       set inline: the grid is cropped to the market's trading hours rather than always drawing 24 hours. */
    touch-action: none; /* the drag gestures are pointer-driven; let them win over scroll */
    user-select: none;
}

.roster-head {
    display: grid;
    font-size: 0.7rem;
    font-weight: 600;
}

.roster-day-head {
    text-align: center;
    padding: 2px 0;
    border-left: 2px solid var(--mud-palette-lines-default, rgba(146, 172, 214, 0.45));
}

/* One per position, under the day it belongs to. Deliberately small and quiet: it repeats across every day
   of a week, and the day above it is what the eye should land on first. */
.roster-role-head {
    text-align: center;
    padding: 1px 2px;
    font-weight: 500;
    opacity: 0.75;
    border-left: 1px solid var(--mud-palette-lines-default, rgba(146, 172, 214, 0.25));
}

.roster-day-head + .roster-day-head,
.roster-role-head.roster-day-start {
    border-left-width: 2px;
}

/* Lays down a role's full complement for the day. Hidden until the column is under the pointer: it repeats
   once per role per day, and a row of plus signs across a fortnight would drown out the role names. */
.roster-role-add {
    margin-left: 2px;
    padding: 0 2px;
    font-size: 0.6rem;
    line-height: 1;
    opacity: 0;
    cursor: pointer;
    background: none;
    border: none;
    color: inherit;
    transition: opacity 120ms ease-in-out;
}

.roster-role-head:hover .roster-role-add,
.roster-role-add:focus-visible {
    opacity: 1;
}

/* Touch has no hover to reveal it with, so it simply stays visible. */
@media (hover: none) {
    .roster-role-add {
        opacity: 0.8;
    }
}

.roster-day {
    position: relative;
    border-left: 1px solid var(--mud-palette-lines-default, rgba(146, 172, 214, 0.25));
}

/* The first role of each day, so the day boundary reads more strongly than the role boundaries inside it. */
.roster-day.roster-day-start {
    border-left: 2px solid var(--mud-palette-lines-default, rgba(146, 172, 214, 0.45));
}

.roster-hour {
    position: absolute;
    left: 0;
    right: 0;
    border-top: 1px dashed var(--mud-palette-lines-default, rgba(146, 172, 214, 0.18));
    pointer-events: none;
}

/* Opaque, so the columns scrolling past underneath do not read through the hours. */
.roster-hour-gutter {
    background: var(--mud-palette-surface, #fff);
}

.roster-hour-label {
    position: absolute;
    left: 0;
    transform: translateY(-50%);
    font-size: 0.7rem;
    opacity: 0.7;
    pointer-events: none;
}

.roster-shift {
    position: absolute;
    left: 2px;
    right: 2px;
    border-radius: 6px;
    border-left: 4px solid currentColor;
    padding: 2px 6px;
    font-size: 0.72rem;
    line-height: 1.15;
    overflow: hidden;
    cursor: grab;
    background: var(--mud-palette-surface, #fff);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}

.roster-shift:active {
    cursor: grabbing;
}

/* The three states an administrator scans for: fully staffed, someone yet to accept, nobody at all. */
.roster-shift-filled { color: #0E9E6E; }
.roster-shift-unconfirmed { color: #E0A100; }
.roster-shift-vacant {
    color: #D64545;
    border-style: dashed;
    border-width: 1px 1px 1px 4px;
}
.roster-shift-draft { opacity: 0.55; }

/* The badge on a supervisory role or shift. Amber rather than the block's own status colour, so "somebody
   is in charge here" is legible whether the shift is filled, short or vacant. */
.roster-supervisor {
    color: #E0A100;
    margin-right: 2px;
}

/* A slot nobody is on yet. Deliberately faint and hollow, so a row of ghosts reads as a gap to fill rather
   than as people already rostered. */
.roster-shift-ghost {
    background: transparent;
    box-shadow: none;
    opacity: 0.75;
}

.roster-shift-ghost:hover {
    background: var(--mud-palette-surface, #fff);
    opacity: 1;
}

/* Highlighted while a person is dragged over it, so the drop target is never ambiguous. */
.roster-shift-droptarget {
    outline: 2px solid var(--mud-palette-primary, #0E9E6E);
    outline-offset: 1px;
}

/* The two edges that change the start and the finish. Kept narrow so the rest of the block is still a
   comfortable target for moving the shift, and given a visible grip on hover so the handles are
   discoverable without cluttering a dense week. */
.roster-shift-resize-start,
.roster-shift-resize-end {
    position: absolute;
    left: 0;
    right: 0;
    height: 6px;
    cursor: ns-resize;
}

.roster-shift-resize-start { top: 0; }
.roster-shift-resize-end { bottom: 0; }

.roster-shift:hover .roster-shift-resize-start,
.roster-shift:hover .roster-shift-resize-end {
    background: currentColor;
    opacity: 0.45;
}

.roster-ghost {
    position: absolute;
    left: 2px;
    right: 2px;
    border: 2px dashed var(--mud-palette-primary, #0E9E6E);
    border-radius: 6px;
    background: rgba(14, 158, 110, 0.12);
    font-size: 0.7rem;
    padding: 2px 4px;
    pointer-events: none;
}

/* The draggable people list beside the calendar. */
/* A person as a pill - the group editor picks members this way. */
.roster-person-chip {
    cursor: pointer;
    border-radius: 999px;
    padding: 2px 10px;
    font-size: 0.78rem;
    border: 1px solid var(--mud-palette-lines-default, rgba(146, 172, 214, 0.35));
}

/* The list beside the calendar, dragged from far more often than it is read. Each person is a card with
   their initials, name and what they are, so a name is picked out at a glance rather than read off a
   run of identical lines. */
.roster-person {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: grab;
    border-radius: 8px;
    padding: 5px 8px;
    border: 1px solid var(--mud-palette-lines-default, rgba(146, 172, 214, 0.35));
    background: var(--mud-palette-surface, #fff);
}

.roster-person:hover {
    border-color: var(--mud-palette-primary, #0E9E6E);
}

.roster-person:active { cursor: grabbing; }

.roster-person-avatar {
    flex: none;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 999px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--mud-palette-primary, #0E9E6E);
    background: rgba(14, 158, 110, 0.12);
}

.roster-person-name {
    display: block;
    font-size: 0.8rem;
    line-height: 1.2;
}

.roster-person-type {
    display: block;
    font-size: 0.66rem;
    line-height: 1.2;
    opacity: 0.6;
}

/* The handle is the affordance that says this row is meant to be dragged. Quiet until hovered. */
.roster-person-grip {
    flex: none;
    font-size: 0.7rem;
    opacity: 0.25;
}

.roster-person:hover .roster-person-grip { opacity: 0.6; }

.roster-month-day {
    min-height: 7rem;
    border: 1px solid var(--mud-palette-lines-default, rgba(146, 172, 214, 0.25));
    border-radius: 6px;
    padding: 4px;
}

/* Controls that belong on screen but not on the printed sheet. */
@media print {
    .no-print { display: none !important; }
}

/* Hours the market is shut. Shaded rather than blocked - setup, pack-down and deliveries are all rostered
   outside trading hours, so these bands are informational and stay schedulable. */
.roster-closed {
    position: absolute;
    left: 0;
    right: 0;
    background: repeating-linear-gradient(
        45deg,
        rgba(100, 116, 139, 0.10),
        rgba(100, 116, 139, 0.10) 6px,
        rgba(100, 116, 139, 0.04) 6px,
        rgba(100, 116, 139, 0.04) 12px);
    pointer-events: none;
}

.dark .roster-closed {
    background: repeating-linear-gradient(
        45deg,
        rgba(146, 172, 214, 0.10),
        rgba(146, 172, 214, 0.10) 6px,
        rgba(146, 172, 214, 0.03) 6px,
        rgba(146, 172, 214, 0.03) 12px);
}

/* Pickers ---------------------------------------------------------------------------------------
   Date and time pickers are sized down to sit inline in a row (max-width on the picker root), and the
   popover takes its width from that anchor while clipping at overflow-x:hidden - which cuts the clock
   face and calendar off at the width of the textbox. The popover is positioned out of flow, so it can
   safely be wider than the input it hangs off. 310px is MudBlazor's own .mud-picker-content min-width. */
.mud-picker-popover-paper {
    width: auto;
    min-width: 310px;
    max-width: none;
}

/* Assistant -------------------------------------------------------------------------------------
   The chat bubble sits above everything on every signed-in page. z-index is above MudBlazor's drawer
   (1100) but below its dialogs and snackbars (1400+), so a dialog opened from an assistant row still
   covers the panel rather than fighting it. Colours come from the Mud palette so the panel follows the
   market's theme and dark mode without a second set of rules. */
.assistant-launcher {
    position: fixed;
    right: 24px;
    /* --tabbar-height is 0px unless app.css's .nav-tabbar rules set it for the width and screen-size
       setting currently in play, so this lifts the launcher clear of the bar wherever it is drawn and
       falls back to the plain corner position everywhere else, in one rule instead of three. */
    bottom: calc(24px + var(--tabbar-height));
    z-index: 1200;
    width: 56px;
    height: 56px;
    border-radius: 9999px;
    border: none;
    color: var(--mud-palette-primary-text);
    font-size: 20px;
    background-color: var(--mud-palette-primary);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
    cursor: pointer;
    transition: transform 120ms ease;
}

.assistant-launcher:hover {
    transform: scale(1.06);
}

.assistant-panel {
    position: fixed;
    right: 24px;
    bottom: calc(92px + var(--tabbar-height));
    z-index: 1200;
    display: flex;
    flex-direction: column;
    width: 400px;
    /* Capped against the viewport rather than given a fixed height, so the panel still fits on a laptop
       screen with the browser chrome taking half of it. --tabbar-height is subtracted too, so a short
       viewport with the bar on screen still leaves room above it. */
    max-height: min(640px, calc(100vh - 140px - var(--tabbar-height)));
    border-radius: 14px;
    overflow: hidden;
    background-color: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
}

@media (max-width: 640px) {
    /* On a phone the panel is the screen - a 400px card floating over a 375px viewport is unusable. */
    .assistant-panel {
        right: 8px;
        left: 8px;
        bottom: calc(84px + var(--tabbar-height));
        width: auto;
    }
}

/* The launcher is fixed over the bottom-right corner and, unlike the tab bar, nothing reserved space
   for it. On a phone the content column reaches that corner, so the bubble sat on top of whatever the
   page ended with - on every list screen that is the pager's next-page button.

   Space is reserved the same way the tab bar reserves its own (see the .nav-tabbar rules in app.css)
   rather than by moving the bubble, so the corner it lives in stays the corner people have learned.
   Keyed off :has so a market whose assistant is unavailable - the bubble is not rendered at all then -
   does not get a strip of dead space at the foot of every page.

   88px = the 24px corner offset + the 56px bubble + an 8px gap; --tabbar-height on top of that clears
   the bar too, wherever it is drawn, instead of only at the width the bar happens to share with the
   mobile column. Repeated per width band, rather than once unconditionally, because the sidebar goes
   permanent at 768px and the content column stops reaching this corner at all. */
@media (max-width: 767px) {
    /* !important because MudBlazor.min.css ships `.pb-8, .py-8 { padding-bottom: 32px !important }`,
       and #bodyContainer carries pb-8. Specificity cannot answer an !important, so this has to match it. */
    body:has(.assistant-launcher) #bodyContainer {
        padding-bottom: calc(88px + var(--tabbar-height)) !important;
    }
}

@media (min-width: 768px) and (max-width: 1023px) {
    body:has(.nav-tabbar[data-screen-tablet="true"]):has(.assistant-launcher) #bodyContainer {
        padding-bottom: calc(88px + var(--tabbar-height)) !important;
    }
}

@media (min-width: 1024px) {
    body:has(.nav-tabbar[data-screen-desktop="true"]):has(.assistant-launcher) #bodyContainer {
        padding-bottom: calc(88px + var(--tabbar-height)) !important;
    }
}

.assistant-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 12px 12px 16px;
    color: var(--mud-palette-primary-text);
    background-color: var(--mud-palette-primary);
}

.assistant-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.assistant-footer {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px;
    border-top: 1px solid var(--mud-palette-lines-default);
}

.assistant-intro {
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 13px;
    color: var(--mud-palette-text-secondary);
}

.assistant-suggestion {
    text-align: left;
    padding: 8px 10px;
    border-radius: 10px;
    border: 1px solid var(--mud-palette-lines-default);
    background: transparent;
    color: var(--mud-palette-text-primary);
    font-size: 13px;
    cursor: pointer;
}

.assistant-suggestion:hover {
    background-color: var(--mud-palette-action-default-hover);
}

.assistant-turn-user,
.assistant-turn-bot {
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-width: 92%;
    font-size: 13.5px;
}

.assistant-turn-user {
    align-self: flex-end;
    align-items: flex-end;
}

.assistant-turn-bot {
    align-self: flex-start;
}

.assistant-turn-user .assistant-bubble-text {
    background-color: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
    border-radius: 12px 12px 2px 12px;
    padding: 8px 12px;
}

.assistant-turn-bot .assistant-bubble-text {
    background-color: var(--mud-palette-background-gray);
    color: var(--mud-palette-text-primary);
    border-radius: 12px 12px 12px 2px;
    padding: 8px 12px;
    /* Model replies contain their own line breaks - a setup email especially - and collapsing them would
       run the whole thing into one paragraph. */
    white-space: pre-wrap;
}

/* The model writes markdown whatever it is asked, so the emphasis it reaches for is given somewhere to
   land. Stated rather than left to the browser: the app's resets flatten strong and em otherwise. */
.assistant-turn-bot .assistant-bubble-text strong {
    font-weight: 600;
}

.assistant-turn-bot .assistant-bubble-text em {
    font-style: italic;
}

.assistant-turn-bot .assistant-bubble-text code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.9em;
    padding: 1px 4px;
    border-radius: 4px;
    background-color: var(--mud-palette-action-disabled-background);
    /* A long product code or URL would otherwise push the bubble past the panel. */
    overflow-wrap: anywhere;
}

.assistant-turn-bot .assistant-bubble-text a {
    color: var(--mud-palette-primary);
    text-decoration: underline;
    overflow-wrap: anywhere;
}

/* Three dots rather than a spinner: a spinner reads as "the page is loading", where the point here is
   that the other side of the conversation is composing a reply. */
.assistant-typing {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 12px;
}

.assistant-typing span {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background-color: var(--mud-palette-text-secondary);
    animation: assistant-typing-dot 1.2s ease-in-out infinite;
}

.assistant-typing span:nth-child(2) {
    animation-delay: 0.15s;
}

.assistant-typing span:nth-child(3) {
    animation-delay: 0.3s;
}

@keyframes assistant-typing-dot {
    0%, 60%, 100% {
        opacity: 0.25;
        transform: translateY(0);
    }

    30% {
        opacity: 1;
        transform: translateY(-3px);
    }
}

@media (prefers-reduced-motion: reduce) {
    .assistant-typing span {
        animation: none;
        opacity: 0.6;
    }
}

.assistant-note {
    font-size: 11px;
    font-style: italic;
    color: var(--mud-palette-text-secondary);
}

/* Was this helpful? Two quiet buttons under a finished answer. Deliberately understated - the answer is what
   the reader came for, and a prominent pair of thumbs under every reply would read as the assistant asking
   for reassurance rather than as a way to report a bad one. */
.assistant-rating {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 2px;
    opacity: 0.55;
    transition: opacity 120ms ease-in-out;
}

.assistant-rating:hover,
.assistant-rating:focus-within {
    opacity: 1;
}

.assistant-rating-label,
.assistant-rating-thanks {
    font-size: 11px;
    color: var(--mud-palette-text-secondary);
}

.assistant-rating-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--mud-palette-text-secondary);
    cursor: pointer;
    font-size: 12px;
}

.assistant-rating-button:hover {
    background-color: var(--mud-palette-background-gray);
    color: var(--mud-palette-text-primary);
}

@media (prefers-reduced-motion: reduce) {
    .assistant-rating {
        transition: none;
    }
}

/* The offer of a walkthrough. Drawn as a card rather than a bare button because it is asking for consent to
   take the screen over: the title says which walkthrough, the note says what will happen, and the button is
   the only thing that starts it. */
.assistant-walkthrough {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    padding: 10px;
    border: 1px solid var(--mud-palette-lines-default);
    border-left: 3px solid var(--mud-palette-primary);
    border-radius: 10px;
    background-color: var(--mud-palette-background-gray);
}

.assistant-walkthrough-title {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    font-weight: 600;
}

.assistant-walkthrough-title i {
    color: var(--mud-palette-primary);
}

.assistant-walkthrough-note {
    font-size: 12px;
    color: var(--mud-palette-text-secondary);
}

.assistant-rows {
    width: 100%;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 10px;
    overflow: hidden;
}

/*
 * The heading over a list of rows. Not uppercased: what lands here is the sentence the report builder wrote
 * to describe the dataset - "Sales from the previous month, most recent first." - and a sentence in capitals
 * is a sentence being shouted. The styling stays quiet so the rows under it stay the loudest thing.
 */
.assistant-rows-heading {
    padding: 6px 10px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--mud-palette-text-secondary);
    background-color: var(--mud-palette-background-gray);
}

.assistant-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 7px 10px;
    border-top: 1px solid var(--mud-palette-lines-default);
}

.assistant-row:hover {
    background-color: var(--mud-palette-action-default-hover);
}

.assistant-row-warning {
    border-left: 3px solid var(--mud-palette-warning);
}

.assistant-row-title {
    font-size: 13px;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.assistant-row-subtitle {
    font-size: 11px;
    color: var(--mud-palette-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.assistant-row-value {
    font-size: 12px;
    white-space: nowrap;
    color: var(--mud-palette-text-secondary);
}

.assistant-more {
    padding: 6px;
    border: none;
    border-top: 1px solid var(--mud-palette-lines-default);
    background: transparent;
    color: var(--mud-palette-primary);
    font-size: 12px;
    cursor: pointer;
}

/* ---------------------------------------------------------------------------
   Guided tours (Components/Knowledgebase/TourHost.razor)

   Colours come from the Mud palette rather than being stated literally, so the
   overlay follows dark mode with the rest of the app.
   --------------------------------------------------------------------------- */

.tour-overlay {
    position: fixed;
    inset: 0;
    /* Transparent, not dimmed. The dimming is the spotlight's spread shadow, which is what leaves a
       hole around the highlighted control; a tint here as well would darken that hole too. */
    background: transparent;
    /* The overlay is what catches stray clicks, so it has to keep hit testing - but the target's
       rectangle is clipped out of it inline (see TourHost.OverlayStyle) so the one control the step is
       about stays clickable underneath. Matches the spotlight's timing so the hole and the ring move
       together. */
    transition: clip-path 180ms ease;
}

.tour-spotlight {
    position: fixed;
    border-radius: 8px;
    /* The dimming and the cut-out in one element: a shadow spread far enough to cover any viewport,
       with nothing painted inside the box itself. Four panels around a hole leave seams at
       fractional device pixels and need repositioning on every scroll frame. */
    box-shadow: 0 0 0 9999px rgba(7, 12, 22, 0.62);
    border: 2px solid var(--mud-palette-primary);
    /* The spotlight only marks the target - clicks have to reach the control underneath, which is the
       whole point of a step that waits for one. */
    pointer-events: none;
    transition: top 180ms ease, left 180ms ease, width 180ms ease, height 180ms ease;
}

.tour-card {
    position: fixed;
    width: 340px;
    max-width: calc(100vw - 32px);
    /* The card is positioned from an assumed height, since its real one depends on the step's wording.
       Capping it here is what makes that assumption safe: a long step scrolls inside the card instead of
       running off the top or bottom of the screen. */
    max-height: calc(100vh - 32px);
    overflow-y: auto;
    padding: 16px;
    border-radius: 14px;
    background-color: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    color: var(--mud-palette-text-primary);
}

/* The offer to resume an interrupted walkthrough. Same card, parked in a corner and positioned entirely
   from CSS - a running step's card is placed inline from the measured target, and this one has none. Below
   the overlay's own z-index, which costs nothing: it only ever shows when no tour is running. */
.tour-resume {
    right: 16px;
    bottom: 16px;
    z-index: 1999;
}

.tour-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 4px;
}

.tour-card-progress {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

.tour-card-title {
    margin: 0 0 6px;
    font-size: 1.05rem;
    font-weight: 700;
}

.tour-card-body {
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--mud-palette-text-primary);
}

.tour-card-body p {
    margin: 0 0 8px;
}

.tour-card-body strong {
    font-weight: 700;
}

.tour-card-body code {
    padding: 1px 5px;
    border-radius: 4px;
    font-size: 0.85em;
    background-color: var(--mud-palette-background-grey);
}

.tour-card-hint {
    margin-top: 10px;
    padding: 8px 10px;
    border-radius: 8px;
    font-size: 0.8rem;
    color: var(--mud-palette-primary-text);
    background-color: var(--mud-palette-primary);
}

.tour-card-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 14px;
}

.tour-card-actions-right {
    display: flex;
    align-items: center;
    gap: 8px;
}

@media (max-width: 640px) {
    /* On a phone there is no room beside anything, so the card stops trying to point and sits at the
       bottom of the screen. The spotlight still shows which control the step is about. */
    .tour-card {
        top: auto !important;
        bottom: 16px;
        left: 16px !important;
        right: 16px;
        width: auto;
        transform: none !important;
    }
}


/* Help articles ------------------------------------------------------------------------------------
   One set of rules for both the reading view and the editing surface, which is what makes the editor
   what-you-see-is-what-you-get in more than name: the article an author is typing into is laid out by the
   same declarations as the article a stallholder opens. */

.kb-article {
    font-size: 0.95rem;
    line-height: 1.6;
    color: var(--mud-palette-text-primary);
}

.kb-article h3,
.kb-article h4,
.kb-article h5 {
    margin: 20px 0 8px;
    font-weight: 700;
    line-height: 1.3;
}

.kb-article h3 { font-size: 1.15rem; }
.kb-article h4 { font-size: 1.02rem; }
.kb-article h5 { font-size: 0.95rem; }

.kb-article > :first-child {
    margin-top: 0;
}

.kb-article p {
    margin: 0 0 12px;
}

.kb-article ul,
.kb-article ol {
    margin: 0 0 12px;
    padding-left: 22px;
}

.kb-article ul { list-style: disc; }
.kb-article ol { list-style: decimal; }

.kb-article li {
    margin-bottom: 4px;
}

.kb-article a {
    color: var(--mud-palette-primary);
    text-decoration: underline;
}

.kb-article code {
    padding: 1px 5px;
    border-radius: 4px;
    font-size: 0.85em;
    background-color: var(--mud-palette-background-grey);
}

.kb-figure {
    margin: 12px 0;
}

.kb-figure img {
    display: block;
    max-width: 100%;
    height: auto;
    border-radius: 8px;
}

/* The editing surface ------------------------------------------------------------------------------- */

.kb-editor {
    border: 1px solid var(--mud-palette-lines-inputs);
    border-radius: 8px;
    background-color: var(--mud-palette-surface);
}

.kb-editor-toolbar {
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

.kb-editor-surface {
    min-height: 220px;
    padding: 14px 16px;
    outline: none;
    overflow-wrap: anywhere;
}

.kb-editor-surface:focus-visible {
    outline: none;
}

/* A caret sitting in an empty article with nothing to say for itself reads as a broken control. */
.kb-editor-surface:empty::before {
    content: "Write the topic here.";
    opacity: 0.5;
}

/* ---- HTML template editor (MP-361) -------------------------------------------------------------------

   The two token colours are the reason this editor exists. A field code that resolves is one colour; one
   that does not is another, because a mistyped code is otherwise completely silent - the renderer leaves
   the braces alone and the label prints "{Stallholder Nunber}" as text.

   Told apart by weight and by a wavy underline as well as by hue, so the distinction survives a red-green
   colour vision deficiency and a bad monitor.

   One variable per state, shared by the token and by its legend swatch. They were two sets of literals and
   they drifted - the legend promised a colour the editor did not draw, which is worse than no legend at
   all, since the legend is the only thing telling an author what the colour means. */

:root {
    --mp-template-known: #0E9E6E;
    --mp-template-known-tint: rgba(14, 158, 110, 0.14);
    --mp-template-unknown: #D92D20;
    --mp-template-unknown-tint: rgba(217, 45, 32, 0.14);
    --mp-template-block: #4F5FE0;
    --mp-template-block-tint: rgba(79, 95, 224, 0.14);
}

.mp-html-editor {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    overflow: hidden;
}

.mp-html-editor .CodeMirror {
    height: 100%;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
}

.mp-wysiwyg-editor-wrap {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    overflow: hidden;
}

.mp-wysiwyg-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.125rem;
    padding: 0.375rem;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-background-grey);
}

.mp-wysiwyg-toolbar-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.75rem;
    height: 1.75rem;
    padding: 0 0.375rem;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    font-size: 0.75rem;
    font-weight: 600;
    cursor: pointer;
    color: inherit;
}

.mp-wysiwyg-toolbar-btn:hover {
    background: var(--mud-palette-action-default-hover);
}

.mp-wysiwyg-toolbar-btn.active {
    background: var(--mp-template-known-tint);
    border-color: var(--mp-template-known);
    color: var(--mp-template-known);
}

.mp-wysiwyg-toolbar-select {
    height: 1.75rem;
    padding: 0 0.25rem;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 4px;
    background: var(--mud-palette-surface);
    color: inherit;
    font-size: 0.75rem;
    max-width: 8rem;
}

.mp-wysiwyg-swatch-btn {
    position: relative;
    overflow: hidden;
}

.mp-wysiwyg-swatch-btn input[type="color"] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
    border: none;
    padding: 0;
}

.mp-wysiwyg-highlight-swatch {
    width: 0.875rem;
    height: 0.875rem;
    border-radius: 2px;
    background: #fef08a;
    border: 1px solid var(--mud-palette-lines-default);
}

.mp-wysiwyg-editor .tiptap mark {
    border-radius: 2px;
    padding: 0 0.05rem;
}

.mp-wysiwyg-image-error {
    padding: 0.375rem 0.5rem;
    font-size: 0.75rem;
    color: var(--mp-template-unknown);
    background: var(--mp-template-unknown-tint);
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

.mp-wysiwyg-toolbar-sep {
    width: 1px;
    height: 1.25rem;
    margin: 0 0.25rem;
    background: var(--mud-palette-lines-default);
}

.mp-wysiwyg-editor {
    overflow-y: auto;
    padding: 0.75rem;
}

.mp-wysiwyg-editor .tiptap {
    outline: none;
    font-size: 0.875rem;
    line-height: 1.6;
}

.mp-wysiwyg-editor .tiptap img {
    max-width: 100%;
    height: auto;
    border-radius: 4px;
}

.mp-wysiwyg-editor .tiptap img.ProseMirror-selectednode {
    outline: 2px solid var(--mp-template-known);
}

.mp-wysiwyg-editor .mp-template-code {
    color: var(--mp-template-known);
    background: var(--mp-template-known-tint);
    font-weight: 600;
    border-radius: 3px;
}

.mp-wysiwyg-editor .mp-template-code-unknown {
    color: var(--mp-template-unknown);
    background: var(--mp-template-unknown-tint);
    font-weight: 600;
    border-radius: 3px;
    text-decoration: underline wavy currentColor;
}

.mp-wysiwyg-editor .mp-template-block {
    color: var(--mp-template-block);
    background: var(--mp-template-block-tint);
    font-weight: 600;
    border-radius: 3px;
}

/* Qualified by .CodeMirror so these outrank the default theme's own .cm-s-default .cm-<token> rules, which
   are two classes deep and would otherwise win wherever a code sits somewhere the base mode has already
   coloured - inside an attribute value, say. */
.CodeMirror .cm-mp-template-code {
    color: var(--mp-template-known);
    background: var(--mp-template-known-tint);
    font-weight: 600;
    border-radius: 3px;
}

.CodeMirror .cm-mp-template-code-unknown {
    color: var(--mp-template-unknown);
    background: var(--mp-template-unknown-tint);
    font-weight: 600;
    border-radius: 3px;
    text-decoration: underline wavy currentColor;
}

/* Structure rather than a value: {#Lines} opens a repeat, it does not substitute anything. */
.CodeMirror .cm-mp-template-block {
    color: var(--mp-template-block);
    background: var(--mp-template-block-tint);
    font-weight: 600;
    border-radius: 3px;
}

/* The swatch is the token's own colour, not an approximation of it. */
.mp-template-code-swatch,
.mp-template-code-unknown-swatch {
    display: inline-block;
    width: 0.75rem;
    height: 0.75rem;
    border-radius: 3px;
    vertical-align: -1px;
}

.mp-template-code-swatch {
    background: var(--mp-template-known);
}

.mp-template-code-unknown-swatch {
    background: var(--mp-template-unknown);
}

/* A field code in the reference list is a button that inserts it, so it has to look like one without
   becoming a row of chunky controls in a narrow column. */
.mp-field-code-insert {
    background: none;
    border: none;
    padding: 0.125rem 0.25rem;
    margin: 0 -0.25rem;
    border-radius: 4px;
    cursor: pointer;
    font: inherit;
    /* A button centres its text by default, which in a stretched flex column puts every code at a different
       x depending on how long its description is - a list you cannot scan down. */
    text-align: left;
}

/* The code sits in a column of its own so every code, and every description beside it, starts at the same
   x. Left to inline flow they step in and out with the length of the code above. */
.mp-field-code-name {
    display: inline-block;
    min-width: 11rem;
}

.mp-field-code-insert:hover,
.mp-field-code-insert:focus-visible {
    background: var(--mud-palette-action-default-hover, rgba(0, 0, 0, 0.05));
}

/* ---- Photo source buttons (MP-366) -------------------------------------------------------------------

   A file input styled as a button, for a phone held over a market table. Sized for a thumb rather than a
   cursor: 44px is the smallest target that is reliably hittable without looking, which is how this one is
   used - the operator is looking at the sheet, not at the screen. */

.mp-photo-source-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    min-height: 44px;
    padding: 0.5rem 1.25rem;
    border-radius: 999px;
    border: 1px solid var(--mud-palette-primary, #4C51BF);
    color: var(--mud-palette-primary, #4C51BF);
    background: transparent;
    font-weight: 600;
    cursor: pointer;
    user-select: none;
}

.mp-photo-source-button--primary {
    background: var(--mud-palette-primary, #4C51BF);
    color: #fff;
}

.mp-photo-source-button:hover {
    filter: brightness(0.95);
}

/* Focus has to be visible: the control the operator is actually tabbing to is the hidden input, and the
   ring belongs on the label they can see. */
.mp-photo-source-inputs input:focus-visible + * ,
.mp-photo-source-button:focus-within {
    outline: 2px solid var(--mud-palette-primary, #4C51BF);
    outline-offset: 2px;
}

/* Off-screen rather than display:none or visibility:hidden - both of those take the input out of the focus
   order, which would leave a keyboard user with two labels that nothing responds to. */
.mp-photo-source-inputs {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
}

/* Icons only, for a sheet row's photo cell, sitting beside MudIconButton-Size.Small Refund/Void controls
   (MP-653) - so this has to read as the same kind of button, not the labelled pill above. The visible
   circle matches MudBlazor's small icon button (26px, 3px padding around a 1.25rem glyph); the 44px thumb
   target survives as an invisible ::before rather than growing the circle itself. */
.mp-photo-source-button--icon {
    min-width: 0;
    min-height: 0;
    width: 26px;
    height: 26px;
    padding: 3px;
    gap: 0;
    position: relative;
    border-radius: 50%;
    border-color: transparent;
    background: transparent;
    color: var(--mud-palette-action-default, rgba(0, 0, 0, 0.54));
    font-size: 1.25rem;
}

.mp-photo-source-button--icon::before {
    content: "";
    position: absolute;
    inset: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
}

/* No filled/primary treatment for the icon-only variant - next to Refund and Void, "take a photo" is not
   the suggested action, it is one of three equal controls. */
.mp-photo-source-button--icon.mp-photo-source-button--primary {
    background: transparent;
    color: var(--mud-palette-action-default, rgba(0, 0, 0, 0.54));
}

.mp-photo-source-button--icon:hover {
    filter: none;
    background: var(--mud-palette-action-default-hover, rgba(0, 0, 0, 0.05));
}

/* ---- Sign-in / registration shell (MP-369) ------------------------------------------------------------

   Two compositions from one markup. Under 1024px the shell is the brand wash with the card floated on it,
   which is what a phone and a tablet get. From 1024px the shell splits: the photograph takes the left half
   as a brand panel and the form moves onto a plain white column, so the fields sit against a flat surface
   instead of over a busy image.

   100dvh rather than 100vh: a mobile browser changes its viewport height as the URL bar shows and hides, so
   a vh-sized page moves underneath somebody who is part way through typing their password. dvh tracks the
   visible viewport instead. vh is kept as the fallback for anything that does not know dvh. */
.login-shell {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    background: linear-gradient(160deg, #0E9E6E 0%, #0B7F58 45%, #0A2E4A 100%);

    /* Room for a notch, and for the home indicator under the version line. */
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
}

/* The photograph starts at 1024px, and the panel that carries it does not exist below that. A browser does
   not fetch a background-image for an element that is display:none, nor one whose media query does not
   apply, so a phone never downloads it - and a phone on market mobile signal is who paid for it. */
.login-hero {
    display: none;
}

.login-panel {
    flex: 1 1 100%;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    padding: 1.5rem 1rem;
}

/* A card while it is floating on the wash. From 1024px the column behind it is already white, so the card
   drops its own surface entirely rather than sitting as a white box on white. */
.login-card {
    width: 100%;
    background: #FFFFFF;
    border-radius: 20px;
    padding: 1.5rem 1.25rem;
    box-shadow: 0 18px 40px -12px rgb(7 12 22 / 0.45);
}

.login-footnote {
    color: rgb(255 255 255 / 0.75);
}

@media (min-width: 640px) {
    .login-panel { padding: 2rem 1.5rem; }
    .login-card { padding: 2rem 1.75rem; }
}

@media (min-width: 1024px) {
    .login-hero {
        position: relative;
        flex: 1 1 0%;
        min-width: 0;
        display: flex;
        flex-direction: column;
        justify-content: space-between;
        overflow: hidden;
        padding: 3rem;
        color: #FFFFFF;
        background-image: url('../img/login_bg.png');
        background-size: cover;
        background-position: center;
    }

    /* The photograph is a market in daylight, so the brand wash goes over the top of it rather than under:
       white text on the bare photo is unreadable wherever a stall awning is pale. */
    .login-hero::after {
        content: "";
        position: absolute;
        inset: 0;
        background: linear-gradient(160deg, rgb(14 158 110 / 0.74) 0%, rgb(11 127 88 / 0.72) 45%, rgb(10 46 74 / 0.92) 100%);
    }

    .login-hero > * {
        position: relative;
        z-index: 1;
    }

    .login-panel {
        flex: 0 0 clamp(28rem, 38%, 38rem);
        background: #FFFFFF;
        padding: 3rem 2.5rem;
    }

    .login-card {
        background: none;
        border-radius: 0;
        padding: 0;
        box-shadow: none;
    }

    .login-footnote { color: #63748F; }
}

/* A field on the sign-in and registration screens. One rule rather than the same eight utilities repeated
   on every input, and it is what carries the focus ring - the inputs here no longer take `form-control`,
   whose only surviving rule paints a blue ring that is nothing to do with the brand. */
.login-input {
    display: block;
    width: 100%;
    min-height: 3rem;
    padding: 0.75rem 0.875rem 0.75rem 2.75rem;
    /* 16px, not smaller: iOS zooms the whole page in on a focused input under 16px and then leaves it
       zoomed, which puts the sign-in button off the side of the screen. */
    font-size: 1rem;
    line-height: 1.5;
    color: #111827;
    background-color: #FFFFFF;
    border: 1px solid #CBD5E1;
    border-radius: 12px;
    transition: border-color 150ms ease-in-out, box-shadow 150ms ease-in-out;
}

.login-input::placeholder { color: #94A3B8; }

.login-input:focus,
.login-input:focus-visible {
    outline: none;
    border-color: #0E9E6E;
    box-shadow: 0 0 0 3px rgb(14 158 110 / 0.18);
}

/* The eye toggle and the icon sit inside the box, so the text has to stop short of them. */
.login-input--with-action { padding-right: 3rem; }

/* A field with no leading icon takes the space back. */
.login-input--plain { padding-left: 0.875rem; }

.login-field { position: relative; }

/* The box the leading icon and the eye button are centred on. It holds the input and nothing else, so a
   Caps Lock notice underneath grows the field's wrapper without moving either of them. */
.login-field__control { position: relative; }

/* Label, "Forgot your password?" and the field itself. The link is placed on the label's row but comes
   after the field in the markup, so tabbing out of the email address lands on the password rather than
   on the link. */
.login-password-block {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    column-gap: 0.75rem;
}

.login-password-block__label { grid-area: 1 / 1; margin-bottom: 0.375rem; }
.login-password-block__link { grid-area: 1 / 2; justify-self: end; }
.login-password-block__field { grid-area: 2 / 1 / auto / -1; }

.login-field__icon {
    position: absolute;
    left: 1rem;
    top: 50%;
    transform: translateY(-50%);
    color: #94A3B8;
    pointer-events: none;
}

/* 44px, because it is hit with a thumb. */
.login-field__action {
    position: absolute;
    right: 0.25rem;
    top: 50%;
    transform: translateY(-50%);
    width: 2.75rem;
    height: 2.75rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: none;
    color: #64748B;
    border-radius: 10px;
}

.login-field__action:hover { color: #0F172A; }

.login-field__action:focus-visible {
    outline: 2px solid #0E9E6E;
    outline-offset: 2px;
}

/* Both buttons are the same height as a field so the column reads as one stack, and 3rem clears the 44px
   minimum for a target that is pressed on a phone. */
.login-button {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    min-height: 3rem;
    padding: 0.75rem 1.25rem;
    font-size: 0.9375rem;
    font-weight: 600;
    border-radius: 12px;
    transition: opacity 150ms ease-in-out, background-color 150ms ease-in-out, box-shadow 150ms ease-in-out;
}

.login-button:focus-visible {
    outline: 2px solid #0E9E6E;
    outline-offset: 2px;
}

.login-button:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

.login-button--primary {
    color: #FFFFFF;
    border: 0;
    background: linear-gradient(90deg, #007096 0%, #9B26B6 100%);
    box-shadow: 0 8px 20px -8px rgb(7 12 22 / 0.5);
}

.login-button--primary:hover:not(:disabled) { opacity: 0.92; }

.login-button--secondary {
    color: #334155;
    background: #FFFFFF;
    border: 1px solid #CBD5E1;
}

.login-button--secondary:hover:not(:disabled) { background: #F8FAFC; }

/* The provider's own logo on a sign-in button. Sized to sit where the icon it replaced sat, and given a
   white chip on the gradient button - a brand mark is drawn for a light background, and several of them
   (Xero's blue, Auth0's orange) disappear into that gradient without one. */
.login-button__logo {
    width: 1.25rem;
    height: 1.25rem;
    object-fit: contain;
    flex-shrink: 0;
}

.login-button--primary .login-button__logo {
    width: 1.5rem;
    height: 1.5rem;
    padding: 0.1875rem;
    background: #FFFFFF;
    border-radius: 9999px;
}

/* ---- Paired phone capture page (MP-369) ---------------------------------------------------------------

   The whole page is one column of cards on a brand wash, sized for a phone held in one hand over a market
   table. Nothing here has a desktop breakpoint: this route is only ever opened from a QR code on a phone,
   so the layout is the phone's and the shell simply centres it if a laptop ever loads it.

   dvh again, for the same reason as the sign-in backdrop: the URL bar on a phone changes the viewport
   height, and a page sized in vh moves while somebody is using it. */
.phone-capture-shell {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    background-color: var(--mud-palette-background-gray, #F5F7FB);
    /* A wash rather than an image. It gives the cards something to sit on and costs no request, which is
       the whole reason this layout dropped the sign-in photograph in the first place. */
    background-image: radial-gradient(120% 55% at 50% 0%,
        rgba(52, 229, 161, 0.20) 0%,
        rgba(124, 140, 255, 0.12) 45%,
        rgba(245, 247, 251, 0) 100%);
    background-repeat: no-repeat;
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
}

/* Sticky: the wordmark is what tells somebody which app they just pointed their camera at, and scrolling
   a list of photos should not take it away. */
.phone-capture-header {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.75rem 1rem;
    background: rgb(255 255 255 / 0.92);
    backdrop-filter: blur(10px);
}

/* The heat ramp, as the header's own edge. Replaces the flat hairline border - it reads as branding at a
   glance and still does the job of separating the bar from what scrolls under it. */
.phone-capture-header::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    background: linear-gradient(90deg, #34E5A1 0%, #FFB833 52%, #FF6B57 100%);
}

/* The mark is stacked - the icon over the wordmark - so it needs height to be readable at all. 44px is
   where "MarketPoS" stops being a smudge; below that the bar carries a logo nobody can identify, which is
   the same as carrying none. */
.phone-capture-logo {
    height: 2.75rem;
    width: auto;
}

.phone-capture-main {
    flex: 1;
    width: 100%;
    max-width: 30rem;
    margin: 0 auto;
    padding: 1.25rem 1rem 1.5rem;
}

.phone-capture {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.phone-capture-card {
    background: #FFFFFF;
    border: 1px solid rgb(221 228 240 / 0.9);
    border-radius: 1.25rem;
    padding: 1.125rem;
    box-shadow: 0 14px 34px -22px rgb(7 12 22 / 0.45);
}

.phone-capture-intro {
    display: flex;
    flex-direction: column;
    gap: 0.625rem;
    align-items: flex-start;
    padding: 0 0.25rem;
}

.phone-capture-heading {
    font-size: 1.5rem;
    line-height: 1.2;
    font-weight: 700;
    color: var(--mud-palette-text-primary, #070C16);
}

.phone-capture-subheading {
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary, #63748F);
    margin-bottom: 0.75rem;
}

/* The expiry, as a chip beside the heading rather than a full-width bar. It is a fact about the screen,
   not a warning - until it is, and then it takes the amber and the weight. */
.phone-capture-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.375rem 0.75rem;
    border-radius: 999px;
    font-size: 0.8125rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.phone-capture-chip--calm {
    background: rgb(14 158 110 / 0.10);
    color: #0B7F58;
}

.phone-capture-chip--hurry {
    background: rgb(183 116 0 / 0.14);
    color: #8A5700;
}

/* Numbered because the order matters - flat, then square on, then the light - and because three short
   lines with a number against each is what somebody reads while already holding the phone up. */
.phone-capture-steps {
    counter-reset: phone-capture-step;
    display: flex;
    flex-direction: column;
    gap: 0.625rem;
    margin: 0 0 1rem;
    padding: 0;
    list-style: none;
    font-size: 0.9375rem;
    line-height: 1.4;
    color: var(--mud-palette-text-primary, #070C16);
}

.phone-capture-steps li {
    position: relative;
    padding-left: 2.25rem;
    min-height: 1.625rem;
}

.phone-capture-steps li::before {
    counter-increment: phone-capture-step;
    content: counter(phone-capture-step);
    position: absolute;
    left: 0;
    top: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.625rem;
    height: 1.625rem;
    border-radius: 999px;
    background: rgb(14 158 110 / 0.12);
    color: #0B7F58;
    font-size: 0.75rem;
    font-weight: 700;
}

.phone-capture-alert {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin-bottom: 1rem;
    padding: 0.75rem;
    border-radius: 0.75rem;
    background: rgb(216 62 39 / 0.08);
    color: #9C2B18;
    font-size: 0.875rem;
    line-height: 1.4;
}

/* The two photo buttons, widened for a thumb. The component is sized for the desktop panel it is also
   used in, so the sizing that only this page needs lives with this page. */
.phone-capture-actions .mp-photo-source-button {
    flex: 1 1 9rem;
    min-height: 3rem;
}

.phone-capture-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.phone-capture-item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.5rem;
    border: 1px solid var(--mud-palette-lines-default, #DDE4F0);
    border-radius: 0.875rem;
    background: var(--mud-palette-background-gray, #F5F7FB);
}

.phone-capture-thumb {
    width: 3rem;
    height: 3rem;
    flex: none;
    object-fit: cover;
    border-radius: 0.625rem;
}

.phone-capture-name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.875rem;
    color: var(--mud-palette-text-primary, #070C16);
}

/* 44px, like the point of sale quantity buttons: the smallest target that is reliably hit by somebody
   who is looking at a sheet rather than at the screen. */
.phone-capture-remove {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: #9C2B18;
    font-size: 1rem;
}

.phone-capture-remove:hover {
    background: rgb(216 62 39 / 0.10);
}

/* Sticky rather than in the flow. Ten photos is the cap, and at ten the Send button was below the fold on
   the one screen the operator has to reach it from. */
.phone-capture-send {
    position: sticky;
    bottom: 0;
    z-index: 10;
    padding: 0.75rem 0 calc(0.25rem + env(safe-area-inset-bottom));
    background: linear-gradient(to top, var(--mud-palette-background-gray, #F5F7FB) 55%, rgb(245 247 251 / 0));
}

.phone-capture-primary {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    min-height: 3.25rem;
    border: 0;
    border-radius: 999px;
    background: linear-gradient(135deg, #0E9E6E 0%, #0B7F58 100%);
    color: #FFFFFF;
    font-size: 1rem;
    font-weight: 700;
    box-shadow: 0 12px 26px -12px rgb(14 158 110 / 0.85);
}

.phone-capture-primary:disabled {
    opacity: 0.55;
    box-shadow: none;
}

/* The three endings - checking, sent, expired - share a shape, so whichever one a stallholder lands on
   reads the same way: a mark, a line, and what to do about it. */
.phone-capture-status {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.75rem;
    padding: 2rem 1.25rem;
}

.phone-capture-status-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 3.5rem;
    height: 3.5rem;
    border-radius: 999px;
    font-size: 1.5rem;
}

.phone-capture-status-icon--good {
    background: rgb(14 158 110 / 0.12);
    color: #0B7F58;
}

.phone-capture-status-icon--warn {
    background: rgb(183 116 0 / 0.14);
    color: #8A5700;
}

.phone-capture-status-icon--calm {
    background: rgb(99 116 143 / 0.12);
    color: var(--mud-palette-text-secondary, #63748F);
}

.phone-capture-status-title {
    font-size: 1.25rem;
    line-height: 1.3;
    font-weight: 700;
    color: var(--mud-palette-text-primary, #070C16);
}

/* The expired panel puts the server's own sentence here rather than a label of our own, so it is a
   sentence and has to be set as one - a two-clause message at display size reads as shouting. */
.phone-capture-status-title--message {
    font-size: 1.0625rem;
    font-weight: 600;
    line-height: 1.4;
}

.phone-capture-status-body {
    font-size: 0.9375rem;
    line-height: 1.5;
    color: var(--mud-palette-text-secondary, #63748F);
}

/* ---- Point of sale line quantity (MP-372) -------------------------------------------------------------

   Sized for a thumb on a counter rather than a mouse. 44px is the smallest target that is reliably hit
   without looking straight at it, which is how this control is actually used - the operator is watching the
   customer, the scanner or the scale, not the screen. */
.pos-qty-button {
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 0.5rem;
    border: 1px solid var(--mud-palette-lines-default, #cbd5e1);
    background: none;
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
    user-select: none;
}

.pos-qty-button:hover,
.pos-qty-button:focus-visible {
    background: var(--mud-palette-action-default-hover, rgba(0, 0, 0, 0.05));
}

/* Wide enough for three digits without the buttons moving as the count changes - a control that shifts
   under a thumb between presses is one that gets pressed twice. */
.pos-qty-value {
    min-width: 2.25rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}
