/* ============================================================
   FIXLOSOPHY – Booking & Admin Styles
   ============================================================ */

/* ---------- Step Indicator ---------- */
.steps-indicator {
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: var(--space-10);
    flex-wrap: nowrap;
    gap: 0;
}

.step-indicator {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
}

.step-indicator__dot {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--dark-3);
    border: 2px solid var(--border-dark);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--fs-xs);
    font-weight: 700;
    color: rgba(255,255,255,0.4);
    transition: all 0.3s;
}

.step-indicator.active .step-indicator__dot {
    background: var(--green);
    border-color: var(--green);
    color: var(--white);
    box-shadow: 0 0 0 4px var(--green-glow);
}

.step-indicator.done .step-indicator__dot {
    background: var(--green-dark);
    border-color: var(--green-dark);
    color: var(--white);
}

.step-indicator__label {
    font-size: var(--fs-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: rgba(255,255,255,0.35);
}

.step-indicator.active .step-indicator__label,
.step-indicator.done .step-indicator__label {
    color: var(--green);
}

.step-indicator__line {
    flex: 1;
    height: 2px;
    background: var(--border-dark);
    margin: 0 var(--space-1);
    margin-bottom: var(--space-5);
    min-width: 24px;
    max-width: 80px;
    transition: background 0.3s;
}

.step-indicator__line.done { background: var(--green-dark); }

/* ---------- Booking Card ---------- */
.booking-card {
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    padding: var(--space-8);
    max-width: 860px;
    margin: 0 auto;
}

.booking-card__title {
    margin-bottom: var(--space-1);
}

.booking-card__subtitle {
    color: rgba(255,255,255,0.5);
    margin-bottom: var(--space-8);
    font-size: var(--fs-body);
}

.booking-error {
    background: rgba(220,38,38,0.12);
    border: 1px solid rgba(220,38,38,0.3);
    color: #f87171;
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-5);
    font-size: var(--fs-sm);
}

/* ---------- Account page ---------- */
.account-section { padding-top: var(--space-8); }
.account-section .booking-card { margin-bottom: var(--space-6); }

/* The saved-bike list sits directly above the add-a-bike field. .photo-picker-list
   carries no bottom margin of its own because the booking wizard's copy of it takes
   its spacing from the .form-group around it — which this row isn't in, so without
   this the last bike sat flush against the input. Scoped to the account page for the
   same reason: unscoped it would double the wizard's spacing. */
.account-section .photo-picker-list { margin-bottom: var(--space-5); }

.account-booking-list { display: flex; flex-direction: column; gap: var(--space-3); }

.account-booking-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-4);
    padding: var(--space-3) var(--space-4);
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
}

.account-booking-row__info {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
    font-size: var(--fs-xs);
}

.account-booking-row__info span { color: rgba(255,255,255,0.5); }

/* Status badge plus the cancel controls. Wraps under the booking details on a
   narrow screen rather than squeezing the service name. */
.account-booking-row__actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    justify-content: flex-end;
}

/* Why the Cancel button isn't there — work already started, or the two-hour cutoff
   passed. Its own full-width line, like the job notes below it, and order 3 so it
   still lands under the actions once the row reorders on a phone. Said on the page
   rather than in a title attribute: the reason is the whole point of the change. */
.account-booking-row__blocked {
    flex: 1 1 100%;
    order: 3;
    margin: 0;
    font-size: var(--fs-xs);
    color: rgba(255,255,255,0.5);
}

@media (max-width: 479px) {
    .account-booking-row {
        flex-wrap: wrap;
        align-items: flex-start;
    }
    .account-booking-row__info { flex: 1 1 100%; order: 2; }
    .account-booking-row__actions { flex: 1 1 100%; order: 3; justify-content: flex-start; }
}

/* An input with a button beside it. Not .form-row: that's a two-column grid whose
   cells stretch to the row height, which left the button taller than the field and
   misaligned against the field's bottom margin. */
.inline-add-row {
    display: flex;
    gap: var(--space-3);
    align-items: flex-start;
    flex-wrap: wrap;
}
.inline-add-row .form-control { flex: 1 1 12rem; }
.inline-add-row .btn { flex: 0 0 auto; }

/* Account → Your Data */
.account-data-actions {
    display: flex;
    gap: var(--space-3);
    flex-wrap: wrap;
}

.account-danger-zone {
    margin-top: var(--space-4);
    padding: var(--space-4) var(--space-5);
    background: rgba(220,38,38,0.06);
    border: 1px solid rgba(220,38,38,0.25);
    border-radius: var(--radius);
}

.account-danger-zone p {
    color: rgba(255,255,255,0.7);
    font-size: var(--fs-sm);
    line-height: 1.6;
    margin-bottom: var(--space-4);
}

.account-danger-zone__warning { color: #f87171 !important; }

@media (max-width: 479px) {
    .account-data-actions { flex-direction: column; }
    .account-data-actions .btn { width: 100%; justify-content: center; }
}

.booking-nav {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: var(--space-8);
    padding-top: var(--space-6);
    border-top: 1px solid var(--border-dark);
    gap: var(--space-4);
    flex-wrap: wrap;
}

/* Back and Start over travel together, so the primary action keeps the right-hand
   end of the row to itself and "Start over" never reads as the way forward. */
.booking-nav__left {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
}

/* Says the booking cap has been reached before any of the wizard is filled in, so
   it can't be discovered at the Confirm click. Amber rather than red: nothing has
   gone wrong, there's just a limit and a way past it. */
.booking-limit-notice {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
    margin-bottom: var(--space-6);
    padding: var(--space-5);
    background: rgba(234,179,8,0.08);
    border: 1px solid rgba(234,179,8,0.28);
    border-radius: var(--radius);
}

.booking-limit-notice h2 {
    margin: 0;
    font-size: var(--fs-lg);
    color: #fbbf24;
}

.booking-limit-notice p {
    margin: 0;
    color: rgba(255,255,255,0.72);
    line-height: 1.55;
}

/* ---------- Customer Auth Strip ---------- */
.customer-auth-strip {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: .5rem;
    padding: var(--space-3) var(--space-4);
    margin-bottom: var(--space-6);
    font-size: var(--fs-body-sm);
    color: rgba(255,255,255,0.65);
}
.customer-auth-strip strong { color: var(--white); }
.customer-auth-strip__link {
    background: none;
    border: none;
    color: var(--green);
    font-size: var(--fs-body-sm);
    font-weight: 600;
    cursor: pointer;
    padding: 0;
}
@media (hover: hover) and (pointer: fine) {
    .customer-auth-strip__link:hover { color: var(--green-dark); text-decoration: underline; }
}

/* ---------- Service Picker ---------- */
.service-category-group {
    margin-bottom: var(--space-8);
}

.service-category-label {
    font-size: var(--fs-caption);
    text-transform: uppercase;
    letter-spacing: 2px;
    color: var(--green);
    font-weight: 700;
    margin-bottom: var(--space-3);
}

.service-pick-grid {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

/* A <button> since the a11y pass — width/text-align/color are reset explicitly
   because a button doesn't inherit them the way the old <div> did. */
.service-pick-card {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    width: 100%;
    text-align: left;
    color: inherit;
    font-family: inherit;
    padding: var(--space-4) var(--space-5);
    background: var(--dark-3);
    border: 1.5px solid var(--border-dark);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: all 0.2s;
    position: relative;
}

.service-pick-card:focus-visible,
.cal-day:focus-visible,
.timeslot:focus-visible {
    outline: 2px solid var(--green);
    outline-offset: 2px;
}

/* An unavailable day is a disabled <button> now, so the cursor and pointer-events
   follow from the attribute rather than from a handler that quietly did nothing. */
.cal-day:disabled {
    cursor: default;
}

@media (hover: hover) and (pointer: fine) {
    .service-pick-card:hover {
        border-color: var(--green);
        background: var(--dark-4);
    }
}

.service-pick-card.selected {
    border-color: var(--green);
    background: rgba(61,187,72,0.08);
}

.service-pick-card__icon {
    font-size: 1.3rem;
    flex-shrink: 0;
    width: 36px;
    text-align: center;
}

.service-pick-card__info {
    flex: 1;
    min-width: 0;
}

.service-pick-card__info strong {
    display: block;
    font-size: var(--fs-body);
    color: var(--white);
}

.service-pick-card__info span {
    font-size: var(--fs-caption);
    color: rgba(255,255,255,0.45);
}

.service-pick-card__price {
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--green);
    white-space: nowrap;
}

.service-pick-card__check {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--green);
    color: var(--white);
    font-size: var(--fs-tiny);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

/* ---------- Calendar ---------- */
.schedule-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-8);
}

.calendar-wrapper {
    flex: 1;
}

.cal-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--space-4);
}

.cal-nav__title {
    font-weight: 700;
    font-size: 1rem;
    color: var(--white);
}

/* line-height: 1 matters — flex centres the line box, not the glyph inside it,
   so the UA's default `normal` line-height left the arrows sitting visibly high
   in the square. The icons are inline SVG rather than text arrows for the same
   reason: a symmetric viewBox centres exactly, where U+2190/2192 are drawn on
   the font's math axis and shift with whatever fallback face resolves them. */
.cal-nav__btn {
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    color: var(--white);
    width: 34px;
    height: 34px;
    border-radius: 8px;
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
    transition: background-color 0.2s, border-color 0.2s, color 0.2s;
    display: flex;
    align-items: center;
    justify-content: center;
}

.cal-nav__btn svg {
    width: 16px;
    height: 16px;
    display: block;
}

@media (hover: hover) and (pointer: fine) {
    .cal-nav__btn:hover:not(:disabled) { background: var(--green); border-color: var(--green); }
}
.cal-nav__btn:disabled { opacity: 0.3; cursor: not-allowed; }

.cal-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: var(--space-1);
}

.cal-head {
    text-align: center;
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    color: rgba(255,255,255,0.35);
    padding: var(--space-2) 0;
}

.cal-day {
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    font-size: var(--fs-xs);
    font-weight: 500;
    transition: all 0.15s;
    cursor: default;
    /* Button resets — these are <button> elements since the a11y pass. */
    width: 100%;
    padding: 0;
    border: none;
    background: transparent;
    font-family: inherit;
    color: inherit;
}

.cal-day--empty { background: transparent; }

.cal-day--disabled {
    color: rgba(255,255,255,0.2);
    background: transparent;
}

/* Shut, as opposed to merely full. A struck-through number distinguishes the two at
   a glance — "come back tomorrow" versus "try another time today" — without adding
   colour the calendar doesn't otherwise use. The reason itself is on the title and
   the aria-label, so it reaches a screen reader too. */
.cal-day--closed {
    text-decoration: line-through;
    text-decoration-thickness: 1px;
    color: rgba(255,255,255,0.28);
    cursor: not-allowed;
}

.cal-day--available {
    color: var(--white);
    background: var(--dark-3);
    cursor: pointer;
}

@media (hover: hover) and (pointer: fine) {
    .cal-day--available:hover { background: var(--green-glow); color: var(--green); }
}

.cal-day--today {
    border: 1.5px solid rgba(255,255,255,0.2);
}

.cal-day--selected {
    background: var(--green) !important;
    color: var(--dark) !important;
    font-weight: 700;
}

.cal-legend {
    display: flex;
    gap: var(--space-4);
    margin-top: var(--space-3);
    font-size: var(--fs-tiny);
    color: rgba(255,255,255,0.4);
    align-items: center;
    flex-wrap: wrap;
}

.cal-legend__dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    display: inline-block;
    margin-right: var(--space-1);
}

.cal-legend__dot--available { background: var(--dark-4); border: 1px solid var(--border-dark); }
.cal-legend__dot--selected { background: var(--green); }
.cal-legend__dot--disabled { background: transparent; border: 1px solid var(--border-dark); opacity: 0.3; }

/* The legend's "closed" marker mirrors the day itself rather than being a dot — a
   struck-through swatch is what the calendar actually shows. */
.cal-legend__closed {
    text-decoration: line-through;
    text-decoration-thickness: 1px;
    color: rgba(255,255,255,0.4);
    margin-right: var(--space-1);
}

/* ---------- Time Slots ---------- */
.timeslot-wrapper h4 {
    margin-bottom: var(--space-4);
    font-size: 1rem;
    color: var(--white);
}

.timeslot-prompt {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    padding: var(--space-12) var(--space-4);
    color: rgba(255,255,255,0.3);
    text-align: center;
    background: var(--dark-3);
    border-radius: var(--radius);
    border: 1px dashed var(--border-dark);
}

.timeslot-prompt span { font-size: 2rem; }
.timeslot-prompt p { font-size: var(--fs-sm); margin: 0; }

.timeslot-none {
    color: rgba(255,255,255,0.4);
    font-size: var(--fs-sm);
    padding: var(--space-6);
    text-align: center;
    background: var(--dark-3);
    border-radius: var(--radius-sm);
}

.timeslot-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-3);
}

.timeslot {
    padding: var(--space-3) var(--space-2);
    text-align: center;
    background: var(--dark-3);
    border: 1.5px solid var(--border-dark);
    border-radius: var(--radius-sm);
    font-size: var(--fs-body-sm);
    font-weight: 600;
    color: var(--white);
    cursor: pointer;
    transition: all 0.15s;
    /* Button reset — a <button> since the a11y pass. */
    width: 100%;
    font-family: inherit;
}

@media (hover: hover) and (pointer: fine) {
    .timeslot:hover { border-color: var(--green); color: var(--green); }
}

.timeslot--selected {
    background: var(--green) !important;
    border-color: var(--green) !important;
    color: var(--dark) !important;
}

/* ---------- Details Form ---------- */
.details-form { display: flex; flex-direction: column; gap: 0; }

.bike-chip-row { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-3); }

.bike-chip {
    padding: var(--space-2) var(--space-3);
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: 50px;
    color: rgba(255,255,255,0.65);
    font-size: var(--fs-caption);
    font-weight: 500;
    cursor: pointer;
    transition: all 0.15s;
}

@media (hover: hover) and (pointer: fine) {
    .bike-chip:hover { border-color: var(--green); color: var(--white); }
}
.bike-chip--selected { background: var(--green); border-color: var(--green); color: var(--dark); font-weight: 700; }

.photo-picker-list {
    list-style: none;
    margin: var(--space-3) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.photo-picker-list li {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    font-size: var(--fs-xs);
}

.photo-picker-remove {
    background: none;
    border: none;
    color: rgba(255,255,255,0.5);
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    padding: 0 var(--space-1);
}

@media (hover: hover) and (pointer: fine) {
    .photo-picker-remove:hover { color: var(--white); }
}

/* ---------- Review ---------- */
.review-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0;
    background: var(--dark-3);
    border-radius: var(--radius);
    border: 1px solid var(--border-dark);
    overflow: hidden;
    margin-bottom: var(--space-5);
}

.review-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--space-3) var(--space-5);
    border-bottom: 1px solid var(--border-dark);
    gap: var(--space-4);
}

.review-item:last-child { border-bottom: none; }

.review-label {
    font-size: var(--fs-caption);
    color: rgba(255,255,255,0.45);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    flex-shrink: 0;
}

.review-value {
    font-weight: 600;
    font-size: var(--fs-body-sm);
    text-align: right;
}

.review-value--green { color: var(--green); }

.review-note {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: rgba(61,187,72,0.07);
    border: 1px solid var(--green-glow);
    border-radius: var(--radius-sm);
    font-size: var(--fs-caption);
    color: rgba(255,255,255,0.55);
    margin-bottom: var(--space-2);
}

/* ---------- Confirmed ---------- */
.booking-confirmed {
    text-align: center;
    max-width: 560px;
    margin: 0 auto;
    padding: var(--space-4) 0 var(--space-8);
}

.confirmed-icon {
    width: 72px;
    height: 72px;
    border-radius: 50%;
    background: var(--green);
    color: var(--dark);
    font-size: 2rem;
    font-weight: 900;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto var(--space-6);
    box-shadow: 0 0 0 8px rgba(61,187,72,0.15);
}

.confirmed-subtitle {
    color: rgba(255,255,255,0.55);
    margin-bottom: var(--space-8);
}

.confirmed-ref {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-1);
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    padding: var(--space-4) var(--space-8);
    margin-bottom: var(--space-8);
}

.confirmed-ref span { font-size: var(--fs-tiny); color: rgba(255,255,255,0.4); text-transform: uppercase; letter-spacing: 1px; }
.confirmed-ref strong { font-size: 1.3rem; color: var(--green); letter-spacing: 1px; }

.confirmed-summary {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    padding: var(--space-5);
    margin-bottom: var(--space-8);
    text-align: left;
}

.confirmed-row {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    font-size: 1.2rem;
}

.confirmed-row > span { flex-shrink: 0; }

.confirmed-row div {
    display: flex;
    flex-direction: column;
}

.confirmed-row div strong { font-size: var(--fs-body-sm); color: var(--white); }
.confirmed-row div span { font-size: var(--fs-caption); color: rgba(255,255,255,0.45); }

.confirmed-actions {
    display: flex;
    justify-content: center;
    gap: var(--space-4);
    flex-wrap: wrap;
}

/* ---------- Admin Login ---------- */
.admin-login {
    min-height: calc(100svh - var(--nav-h));
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-8) var(--space-5);
    background: var(--dark);
}

.admin-login__card {
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    padding: var(--space-10);
    width: 100%;
    max-width: 420px;
    text-align: center;
}

.admin-login__logo {
    display: flex;
    justify-content: center;
    margin-bottom: var(--space-6);
}
.admin-logo-img { height: 64px; width: auto; display: block; margin: 0 auto; mix-blend-mode: screen; }

.admin-login__card h2 { margin-bottom: var(--space-2); }
.admin-login__card p { color: rgba(255,255,255,0.5); margin-bottom: var(--space-6); font-size: var(--fs-body-sm); }
.admin-login__card .form-group { text-align: left; margin-bottom: var(--space-5); }

.remember-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    text-align: left;
    margin-bottom: var(--space-5);
    color: rgba(255,255,255,0.7);
    font-size: var(--fs-body-sm);
    cursor: pointer;
}
.remember-row input { width: auto; margin: 0; }

.auth-alt { margin-top: var(--space-5); margin-bottom: 0; font-size: var(--fs-body-sm); color: rgba(255,255,255,0.5); }
.auth-alt a { color: var(--green); font-weight: 600; }

/* ---------- Admin Dashboard ---------- */
/* Tighter than the public page heroes so the dashboard header takes ~50px less
   vertical space (and stays compact on phones). */
.page-hero--admin { padding-top: var(--space-8); padding-bottom: var(--space-6); }
.admin-section { padding-top: var(--space-8); }

@media (max-width: 479px) {
    .page-hero--admin { padding-top: var(--space-6); padding-bottom: var(--space-5); }
    .admin-section { padding-top: var(--space-5); }

    /* Booking card: reduce padding */
    .booking-card { padding: var(--space-5); }

    /* Step indicator: shrink dots and hide labels on tiny screens */
    .step-indicator__dot { width: 28px; height: 28px; font-size: var(--fs-tiny); }
    .step-indicator__label { display: none; }
    .step-indicator__line { margin-bottom: 0; min-width: 16px; }
    .steps-indicator { margin-bottom: var(--space-8); }

    /* Booking nav buttons: stack on tiny screens */
    .booking-nav { flex-direction: column-reverse; }
    .booking-nav .btn { width: 100%; justify-content: center; }
    .booking-nav > span { display: none; }
    /* The grouped Back/Start over pair stacks too, rather than trying to sit two
       full-width buttons side by side. */
    .booking-nav__left { width: 100%; flex-direction: column; gap: var(--space-2); }

    /* Calendar: tighten up */
    .cal-day { font-size: var(--fs-caption); border-radius: var(--radius-sm); }
    /* Was 0.6rem — the smallest text anywhere on the site, for the one row that
       has to stay readable at arm's length while you pick a date. The weekday
       initials are two characters, so --fs-2xs still fits the cell at 320px. */
    .cal-head { font-size: var(--fs-2xs); }

    /* Service picker: reduce padding */
    .service-pick-card { padding: var(--space-3); }

    /* Review items: allow wrapping */
    .review-item { flex-wrap: wrap; gap: var(--space-1); }
    .review-value { text-align: left; }

    /* Confirmed page */
    .confirmed-ref { padding: var(--space-3) var(--space-5); }
    .confirmed-ref strong { font-size: 1.1rem; }
    .confirmed-actions { flex-direction: column; align-items: center; }
    .confirmed-actions .btn { width: 100%; max-width: 320px; justify-content: center; }
}

/* Small utilities for the admin dashboard markup — replace repeated inline
   styles in Admin.razor rather than hand-writing the same declarations per
   element. */
.text-center { text-align: center; }
.text-muted-sm { font-size: var(--fs-xs); color: rgba(255,255,255,0.45); }
.text-faint-sm { font-size: var(--fs-xs); color: rgba(255,255,255,0.35); }

.admin-header-actions {
    display: flex;
    align-items: center;
    gap: var(--space-4);
}
.admin-header-actions__name {
    color: rgba(255,255,255,0.7);
    font-size: var(--fs-sm);
}
.admin-header-actions__name .status-badge { font-size: var(--fs-tiny); }

.form-control--compact { font-size: var(--fs-xs); padding: var(--space-1) var(--space-2); }
.form-control--price   { font-size: var(--fs-sm); padding: var(--space-1) var(--space-2); width: 100px; }
.form-control--assign  { min-width: 130px; }
.admin-subheading      { font-size: var(--fs-h3-compact); margin: var(--space-8) 0 var(--space-4); }

/* Replaces repeated inline styles in Admin.razor. */
.admin-inline-error    { margin-bottom: var(--space-3); }
.admin-checkbox-label  { margin-left: var(--space-1); }
.price-current         { font-weight: 700; color: var(--green); }

.admin-table .col-actions     { width: 1%; white-space: nowrap; }

.tab-bar-wrapper { margin-bottom: var(--space-6); }
.tab-bar-wrapper .admin-filter-tabs { gap: var(--space-2); }

/* ============================================================
   ADMIN PRICING
   ------------------------------------------------------------
   Two layouts over the same rows: cards below 1024px, the table from 1024px. The
   switch is later than the dashboard's usual 768px because 1024px is the narrowest
   the table fits its container — and a price list that scrolls sideways hides its
   Save and Delete buttons off the edge.
   ============================================================ */
.pricing-tab-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--space-4);
    flex-wrap: wrap;
    gap: var(--space-3);
}
.pricing-tab-header h3 { margin: 0; }
.pricing-tab-header__actions { display: flex; gap: var(--space-3); }

/* Below 1024px the sticky bar at the foot of the list provides both buttons, so these
   would be a second pair doing the same job. */
@media (max-width: 1023px) {
    .pricing-tab-header__actions { display: none; }
}

.pricing-home-count {
    margin-bottom: var(--space-4);
    font-size: var(--fs-sm);
    color: rgba(255,255,255,0.5);
}

.action-btn:disabled { opacity: 0.3; cursor: default; pointer-events: none; }

/* ── Cards (phones and tablets) ── */
.pricing-cards {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.pricing-group {
    margin: var(--space-4) 0 var(--space-1);
    font-size: var(--fs-tiny);
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: rgba(255,255,255,0.45);
}
.pricing-group:first-child { margin-top: 0; }

.pricing-card {
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    overflow: hidden;
    transition: border-color 0.2s;
}
.pricing-card--open { border-color: rgba(61,187,72,0.4); }

/* The whole line is the button: a service's name, how long it takes and its price,
   which is everything most visits to this tab are here to check. */
.pricing-card__summary {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    min-height: 4rem;
    padding: var(--space-3) var(--space-4);
    background: none;
    border: 0;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.pricing-card__title {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
}
.pricing-card__name {
    font-size: var(--fs-body);
    font-weight: 700;
    color: var(--white);
    overflow-wrap: anywhere;
}
.pricing-card__meta { font-size: var(--fs-xs); color: rgba(255,255,255,0.45); }

.pricing-card__price {
    flex: 0 0 auto;
    font-weight: 700;
    color: var(--green);
    font-variant-numeric: tabular-nums;
}

.pricing-card__chevron {
    flex: 0 0 auto;
    color: rgba(255,255,255,0.4);
    transition: transform 0.2s;
}
[aria-expanded="true"] > .pricing-card__chevron { transform: rotate(180deg); }

.pricing-chip {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-05) var(--space-2);
    border-radius: 50px;
    font-size: var(--fs-tiny);
    font-weight: 700;
    white-space: nowrap;
    background: rgba(234,179,8,0.12);
    color: #eab308;
    border: 1px solid rgba(234,179,8,0.3);
}
.pricing-chip--saved { background: rgba(61,187,72,0.12); color: var(--green); border-color: rgba(61,187,72,0.3); }
.pricing-chip--error { background: rgba(220,38,38,0.12); color: #f87171; border-color: rgba(220,38,38,0.3); }

.pricing-card__body {
    padding: var(--space-4);
    border-top: 1px solid var(--border-dark);
}

/* 16px, not the denser admin size: anything smaller makes iOS zoom the page on focus
   and never zoom back. Applies at tablet widths too, where the site-wide floor stops. */
.pricing-card .form-control { font-size: 16px; }
.pricing-card .form-group { margin-bottom: 0; }

.pricing-card__fields {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4) var(--space-3);
    margin-bottom: var(--space-4);
}
.pricing-card__field--wide { grid-column: 1 / -1; }

/* One height for every field in the row. A <select> otherwise sizes itself a few
   pixels shorter than the text inputs beside it. */
.pricing-card__fields .form-control { height: 3rem; }

/* What the price is now, under the box it is being changed in. */
.pricing-card__hint {
    display: block;
    margin-top: var(--space-1);
    font-size: var(--fs-xs);
    color: rgba(255,255,255,0.45);
}

.pricing-card__more {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    min-height: var(--touch-min);
    padding: var(--space-2) 0;
    background: none;
    border: 0;
    border-top: 1px solid var(--border-dark);
    color: rgba(255,255,255,0.85);
    font: inherit;
    font-size: var(--fs-sm);
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}

.pricing-card .pricing-details {
    padding-top: var(--space-2);
    gap: var(--space-4);
}
.pricing-card .pricing-details .form-group { margin-bottom: var(--space-4); }
.pricing-card .pricing-details__text .form-group:last-child { margin-bottom: 0; }

.pricing-card__actions,
.pricing-card .pricing-delete-confirm {
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border-dark);
}

.pricing-card__actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}
.pricing-card__actions .btn { justify-content: center; }

.pricing-card__secondary {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    gap: var(--space-2);
}
.pricing-card__secondary .action-btn,
.pricing-card .booking-danger__actions .action-btn {
    min-height: var(--touch-min);
    padding-inline: var(--space-2);
}
.pricing-card .booking-danger__actions .action-btn { flex: 1; }
.pricing-card .booking-danger__text { margin-top: 0; }

.pricing-row-error { margin: var(--space-4) 0 0; }

/* Room to breathe on a tablet: the four fields on one line, and Save beside the rest
   instead of stretched across a 900px card. */
@media (min-width: 640px) {
    .pricing-card__fields { grid-template-columns: minmax(0, 2fr) repeat(3, minmax(0, 1fr)); }
    .pricing-card__field--wide { grid-column: auto; }
    .pricing-card__actions { flex-direction: row-reverse; justify-content: space-between; align-items: center; }
    .pricing-card__actions .btn { min-width: 10rem; }
    .pricing-card__secondary { grid-auto-columns: auto; }
    .pricing-card__secondary .action-btn { padding-inline: var(--space-4); }
}

/* Add service and Save All follow the list down the page instead of scrolling off the
   top. In the flow after the list, so the last card always scrolls clear of it. */
.pricing-sticky-actions {
    position: sticky;
    bottom: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-2);
    padding: var(--space-5) 0 max(var(--space-3), env(safe-area-inset-bottom));
    background: linear-gradient(to top, var(--dark) 70%, transparent);
}
.pricing-sticky-actions .btn { flex: 1; justify-content: center; }

/* ── Table (desktop) ── */
.pricing-table-wrap { display: none; }

@media (min-width: 1024px) {
    .pricing-cards,
    .pricing-sticky-actions { display: none; }
    .pricing-table-wrap { display: block; }
}

/* Fixed layout, so the columns are these widths and the service name takes what is
   left — the table is exactly as wide as its container at every width, which is what
   keeps it from scrolling sideways. */
.pricing-table-admin { table-layout: fixed; }

/* Tight at the narrow end, so the longest service name is readable in full at 1024px. */
.admin-table.pricing-table-admin th,
.admin-table.pricing-table-admin td { padding-inline: var(--space-2); }

.pricing-table-admin .col-order       { width: 5rem; }
.pricing-table-admin .col-type        { width: 8.25rem; }
.pricing-table-admin .col-duration    { width: 6.5rem; }
.pricing-table-admin .col-price       { width: 7.25rem; }
.pricing-table-admin .col-price-input { width: 7.25rem; }
.pricing-table-admin .col-row-actions { width: 12rem; }

.pricing-table-admin .pricing-input { width: 100%; }

.pricing-order,
.pricing-row-actions { display: inline-flex; align-items: center; gap: var(--space-1); }
.pricing-row-actions { gap: var(--space-2); }
.pricing-order .action-btn { padding: var(--space-1) var(--space-2); }

/* A row with something to save says so on the button that saves it. */
.action-btn.pricing-save--unsaved { background: var(--green); color: var(--dark); border-color: var(--green); }

/* Under 1200px Delete is its icon alone; the button keeps its name for a screen reader. */
.pricing-delete-label { display: none; }

@media (min-width: 1200px) {
    .admin-table.pricing-table-admin th,
    .admin-table.pricing-table-admin td { padding-inline: var(--space-3); }

    .pricing-table-admin .col-order       { width: 5.5rem; }
    .pricing-table-admin .col-type        { width: 8.75rem; }
    .pricing-table-admin .col-duration    { width: 7.5rem; }
    .pricing-table-admin .col-price       { width: 7.5rem; }
    .pricing-table-admin .col-price-input { width: 7.75rem; }
    .pricing-table-admin .col-row-actions { width: 15rem; }
    .pricing-delete-label { display: inline; }
}

/* The Details panel and the delete confirmation each open as a full-width row directly
   under their service, so it is clear which one they belong to. */
.admin-table tbody tr.pricing-subrow { background: var(--dark-3); }
.admin-table.pricing-table-admin tbody tr.pricing-subrow td { padding: var(--space-5) var(--space-6); }
.pricing-subrow .pricing-row-error { margin: 0; }
.pricing-subrow .booking-danger__text { margin-top: 0; }

/* ── What customers see: the fields behind Details ── */
.pricing-details {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-2) var(--space-8);
}
.pricing-details .form-group { margin-bottom: var(--space-4); }
/* The site-wide textarea floor is sized for the contact form's message box. */
.pricing-details textarea.form-control { min-height: 0; }
.pricing-details__icon { display: flex; align-items: center; gap: var(--space-3); }
.pricing-details__icon .form-control { width: auto; min-width: 10rem; }
.pricing-details__icon-preview { font-size: 1.5rem; line-height: 1; color: var(--green); }
.pricing-details__options .admin-toggle:first-child { border-top: none; }

@media (min-width: 900px) {
    .pricing-details { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
}

.admin-stats {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-4);
    margin-bottom: var(--space-6);
}

.admin-stat {
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    padding: var(--space-5);
    text-align: center;
}

.admin-stat--highlight {
    border-color: var(--green);
    background: rgba(61,187,72,0.05);
}

.admin-stat__number {
    font-size: 2.2rem;
    font-weight: 900;
    color: var(--green);
    line-height: 1;
    margin-bottom: var(--space-1);
}

.admin-stat__label {
    font-size: var(--fs-tiny);
    text-transform: uppercase;
    letter-spacing: 1px;
    color: rgba(255,255,255,0.4);
}

/* Filters */
.admin-filters {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin-bottom: var(--space-6);
}

/* The sort picker sits beside the search box. Fixed width so it doesn't stretch to
   fill the row on desktop and dwarf the thing being sorted. */
.admin-sort { flex-shrink: 0; }
.admin-sort .form-control { width: 100%; }

@media (min-width: 640px) {
    .admin-sort .form-control { width: auto; min-width: 11rem; }
}

/* Chips wrap onto a second line rather than scrolling sideways: a strip that scrolls
   hides whatever is past the edge behind a gesture nothing on screen hints at. This
   was a scroller — the class name still says so — and on a phone "Completed" and
   "Cancelled" were simply not there until you happened to drag the row. */
.admin-filter-scroll { min-width: 0; }

.admin-filter-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-1);
}

.filter-tab {
    padding: var(--space-2) var(--space-3);
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: 50px;
    color: rgba(255,255,255,0.6);
    font-size: var(--fs-caption);
    font-weight: 500;
    cursor: pointer;
    /* Named properties, not `all`. The active state used to also change font-weight,
       and `all` happily animated the resulting width change — so selecting a tab made
       it grow by ~3px over 150ms and shunt every tab after it sideways. Listing the
       properties means a future style that affects layout can't quietly become an
       animation. */
    transition: background-color 0.15s, border-color 0.15s, color 0.15s;
    white-space: nowrap;
}

@media (hover: hover) and (pointer: fine) {
    .filter-tab:hover { border-color: var(--green); color: var(--white); }
}

/* Deliberately no font-weight change. Bold text is wider, so it resized the button
   and shifted its neighbours every time the selection moved. The weight was
   redundant anyway: a solid green pill with near-black text on a dark bar is already
   about as loud as an active state gets. */
.filter-tab.active { background: var(--green); border-color: var(--green); color: var(--dark); }

.admin-search { width: 100%; }
.admin-search .form-control { font-size: var(--fs-sm); }

/* Today schedule */
.today-schedule {
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    padding: var(--space-5);
    margin-bottom: var(--space-6);
}

.today-schedule h3 { font-size: var(--fs-h3-compact); margin-bottom: var(--space-4); }

.today-slots { display: flex; flex-direction: column; gap: var(--space-2); }

.today-slot {
    display: grid;
    grid-template-columns: 48px 1fr auto;
    align-items: center;
    gap: var(--space-1) var(--space-3);
    padding: var(--space-3);
    background: var(--dark-3);
    border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
}

.today-slot__time { font-weight: 700; color: var(--green); }
.today-slot__name { font-weight: 600; }
.today-slot__service {
    color: rgba(255,255,255,0.55);
    font-size: var(--fs-caption);
    grid-column: 2;
}

/* Status, and "Not arrived" under it when the slot has passed. One grid item spanning
   both rows: as two loose items the second one fell into the next free cell, which is
   the 48px time column — a badge three times too wide for it, drawn over the row below. */
.today-slot__badges {
    grid-column: 3;
    grid-row: 1 / span 2;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--space-1);
}

/* Table */
.admin-table-wrapper { overflow-x: auto; border-radius: var(--radius); border: 1px solid var(--border-dark); }

.admin-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fs-sm);
}

.admin-table thead tr {
    background: var(--dark-3);
    border-bottom: 1px solid var(--border-dark);
}

.admin-table th {
    padding: var(--space-3) var(--space-4);
    text-align: left;
    font-size: var(--fs-2xs);
    text-transform: uppercase;
    letter-spacing: 1px;
    color: rgba(255,255,255,0.45);
    white-space: nowrap;
}

.admin-table tbody tr {
    background: var(--dark-2);
    border-bottom: 1px solid var(--border-dark);
    cursor: pointer;
    transition: background 0.15s;
}

.admin-table tbody tr:last-child { border-bottom: none; }
/* An expanded row stays highlighted on touch too — it marks which row is open,
   which is state rather than hover feedback. Only the hover half is gated. */
.admin-table tbody tr.row-expanded { background: var(--dark-3); }

@media (hover: hover) and (pointer: fine) {
    .admin-table tbody tr:hover { background: var(--dark-3); }
}

.admin-table td { padding: var(--space-3) var(--space-4); vertical-align: middle; }

/* A row with a panel open under its buttons is as tall as the panel. Centred, the
   booking it belongs to floats halfway down a row 500px high; at the top it stays
   beside the buttons that opened the panel. */
.admin-table tbody tr:has(.complete-note, .booking-confirm) > td { vertical-align: top; }

.row-detail td { background: rgba(61,187,72,0.04); padding: 0; }

.booking-detail {
    padding: var(--space-4);
    border-top: 1px solid var(--border-dark);
}

.booking-detail__grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-3) var(--space-8);
}

.booking-detail__grid > div {
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
}

.booking-detail__grid strong { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: 0.5px; color: rgba(255,255,255,0.35); }
.booking-detail__grid span { font-size: var(--fs-sm); color: rgba(255,255,255,0.75); }

.booking-detail__photos { margin-top: var(--space-3); }
.booking-detail__photos strong {
    display: block;
    margin-bottom: var(--space-1);
    font-size: var(--fs-2xs);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: rgba(255,255,255,0.35);
}

.admin-photo-thumbs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-1);
}

.admin-photo-thumb {
    width: 56px;
    height: 56px;
    object-fit: cover;
    border-radius: var(--radius);
    border: 1px solid var(--border-dark);
}

/* Table cells */
.ref-badge {
    font-size: var(--fs-tiny);
    font-family: monospace;
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    padding: var(--space-1) var(--space-2);
    border-radius: 4px;
    white-space: nowrap;
    color: var(--green);
}

.customer-cell, .service-cell, .date-cell {
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
}

.customer-cell strong, .service-cell strong, .date-cell strong { color: var(--white); font-size: var(--fs-sm); }
.customer-cell span, .service-cell span, .date-cell span { font-size: var(--fs-tiny); color: rgba(255,255,255,0.4); }

/* The two columns whose width comes from what a customer typed. Left to size
   themselves, one long email address widened the Customer column for every row and
   pushed the Actions column out of the frame — on a desktop. So the customer is
   capped and its email truncates (the whole address is in the title, and in the
   row's detail), and the service keeps enough width not to be broken one word a line. */
.customer-cell { min-width: 9rem; max-width: 13rem; }
.customer-cell strong { overflow-wrap: anywhere; }
/* width: 0 with min-width: 100% takes the address out of the column's sizing
   altogether — it fills whatever width the name was given and truncates there. A
   plain nowrap + ellipsis still asks for its full width when the table is measuring
   itself, which is how one address came to set the width for every row. */
.customer-cell span {
    width: 0;
    min-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.service-cell { min-width: 7.5rem; }
.date-cell { white-space: nowrap; }

/* Any other cell holding an address or a name somebody typed: it may break anywhere
   rather than hold its column open. */
.admin-table .cell-break { overflow-wrap: anywhere; }

/* And the cells that must not: a date or a phone number split over two lines reads as
   two things. The address beside them is what gives way. */
.admin-table .cell-nowrap { white-space: nowrap; }

/* Seven and nine columns at 16px either side is over 200px of padding. These two
   tables give some back, which is what lets them fit the 1160px they have. */
.admin-table--dense th,
.admin-table--dense td { padding-inline: var(--space-3); }

/* Nine columns, five of them a tick box under a three-word heading. The headings
   wrap; the columns are then as wide as a word rather than as wide as a sentence. */
.admin-table--staff th { white-space: normal; line-height: 1.3; vertical-align: bottom; }
.admin-table--staff th.col-tick { max-width: 6.5rem; text-align: center; }

/* Status badges */
.status-badge {
    display: inline-block;
    padding: var(--space-1) var(--space-3);
    border-radius: 50px;
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    white-space: nowrap;
}

.status-badge--pending    { background: rgba(234,179,8,0.15);  color: #eab308; border: 1px solid rgba(234,179,8,0.3);  }
.status-badge--confirmed  { background: rgba(61,187,72,0.15);  color: var(--green); border: 1px solid rgba(61,187,72,0.3); }
.status-badge--inprogress { background: rgba(59,130,246,0.15); color: #60a5fa; border: 1px solid rgba(59,130,246,0.3); }
.status-badge--completed  { background: rgba(107,114,128,0.15);color: #9ca3af; border: 1px solid rgba(107,114,128,0.3);}
.status-badge--cancelled  { background: rgba(220,38,38,0.12);  color: #f87171; border: 1px solid rgba(220,38,38,0.25); }
/* Not a booking status — a derived "the slot has passed and the bike isn't in".
   Amber rather than red on purpose: most late arrivals turn up, so this is a prompt
   to ring them, not a verdict. Distinct from --pending, which is a real status. */
.status-badge--late       { background: rgba(249,115,22,0.15); color: #fb923c; border: 1px solid rgba(249,115,22,0.35); }
/* A finished job whose bike is still at the shop. Solid where the others are tinted:
   on the customer's page it is the one state that asks something of them, and on the
   dashboard it is the bike somebody has to chase. */
.status-badge--ready      { background: var(--green); color: var(--dark); border: 1px solid var(--green); }

/* The Bookings table's status cell. A wrapping row rather than loose inline badges, so
   two of them — or a badge and the wait beside it — have a gap between them on one
   line and between the lines when they take two. */
.status-cell {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-2);
}

/* How long a finished bike has been waiting, beside its "To collect" badge. Not part
   of the pill: a table column is as wide as the widest thing in it that cannot break,
   and with the count inside, a bike left for six weeks widened the Bookings table past
   a desktop screen. On its own it drops to the next line instead. */
.collect-wait {
    font-size: var(--fs-2xs);
    font-weight: 700;
    white-space: nowrap;
    color: rgba(255,255,255,0.6);
}
/* Past the collection window: the same orange the badge beside it has turned. */
.collect-wait--overdue { color: #fb923c; }

/* Action buttons */
.action-buttons { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.action-btn {
    /* inline-flex + inherited font + fixed line-height so <button> and <a>
       action buttons render at an identical height everywhere (the calendar
       row centres them, so any intrinsic size difference would show). */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-1) var(--space-3);
    min-height: 28px;
    border-radius: 6px;
    font-family: inherit;
    font-size: var(--fs-tiny);
    font-weight: 600;
    line-height: 1.35;
    cursor: pointer;
    /* Every colour below comes from a --variant class, so a button wearing none of
       them used to fall through to the user agent's button chrome — a white pill on
       the dark card. These two make the base neutral instead of undefined. */
    background: rgba(255,255,255,0.06);
    color: rgba(255,255,255,0.75);
    border: 1px solid transparent;
    transition: all 0.15s;
    white-space: nowrap;
    gap: var(--space-1);
}

/* The one primary action on a booking. Confirm / Start work / Mark complete were
   three separately-coloured buttons for what is one control — they were already
   mutually exclusive, so the colour change only ever signalled "the button moved". */
.action-btn--primary { background: rgba(61,187,72,0.15); color: var(--green); border-color: rgba(61,187,72,0.3); }
@media (hover: hover) and (pointer: fine) {
    .action-btn--primary:hover { background: var(--green); color: var(--dark); }
}

/* Confirming a destructive step, at the point of confirming it — never as the
   resting state of a button that merely opens the confirmation. */
.action-btn--danger { background: rgba(220,38,38,0.12); color: #f87171; border-color: rgba(220,38,38,0.3); }
@media (hover: hover) and (pointer: fine) {
    .action-btn--danger:hover { background: #dc2626; color: var(--white); }
}

/* Opening the cancel confirmation — quiet red, so it reads as destructive without
   claiming to be the destruction itself. --danger stays reserved for the button that
   actually fires inside the confirmation, and the two sit next to each other often
   enough that the difference in weight is the whole point. */
.action-btn--cancel {
    background: rgba(220,38,38,0.08);
    color: #f87171;
    border-color: rgba(220,38,38,0.22);
}
@media (hover: hover) and (pointer: fine) {
    .action-btn--cancel:hover { background: rgba(220,38,38,0.2); border-color: rgba(220,38,38,0.45); }
}

/* Icons inside action buttons sit beside a label, so they take a touch more than the
   1em the base .icon rule gives them or they read as a smudge next to 600-weight text. */
.action-btn .icon { width: 1.05em; height: 1.05em; }


/* An empty result inside a table that stays on the page, rather than in place of it.
   Matches .admin-empty so the phone and desktop views read the same. */
.admin-table__empty-row td {
    padding: var(--space-12) var(--space-4);
    text-align: center;
    color: rgba(255,255,255,0.35);
    font-size: var(--fs-body-sm);
}

.admin-table__empty-row:hover td { background: none; }

.admin-empty {
    padding: var(--space-12);
    text-align: center;
    color: rgba(255,255,255,0.35);
    font-size: var(--fs-body-sm);
}

/* Staff management / Pricing tab */
.staff-mgmt { margin-top: var(--space-2); }
.staff-add-card {
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    padding: var(--space-6);
    margin-bottom: var(--space-6);
}
.staff-add-card h3 { margin: 0 0 var(--space-4); font-size: var(--fs-h3-compact); color: var(--white); }

/* ── Availability tab ────────────────────────────────────────────────────────
   Closures and staff absence, side by side above 900px. They're read together —
   "are we shut, and who's in" is one question — but each is self-contained, so on a
   phone they stack rather than interleave. */
.availability-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-6);
}

@media (min-width: 900px) {
    .availability-grid { grid-template-columns: 1fr 1fr; align-items: start; }
}

.availability-panel {
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    padding: var(--space-6);
}

.availability-panel h3 { margin: 0 0 var(--space-2); font-size: var(--fs-h3-compact); color: var(--white); }
.availability-panel > .text-muted-sm { display: block; margin-bottom: var(--space-4); }

/* The add form, separated from the list below it by a rule rather than only whitespace —
   they're both lists of fields otherwise, and ran together. */
.availability-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding-bottom: var(--space-5);
    margin-bottom: var(--space-5);
    border-bottom: 1px solid var(--border-dark);
}

.availability-form .form-group { margin: 0; }
.availability-form .btn { align-self: flex-start; }

.availability-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.availability-list__item {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius-sm);
    padding: var(--space-3) var(--space-4);
}

.availability-list__item strong { color: var(--white); }

/* Bookings a closure has just stranded. Amber, above both panels, and deliberately
   loud: this is the screen's real job — the shop said "close that week", and the
   thing that matters next is the people who had booked into it. */
.availability-affected {
    background: rgba(234,179,8,0.08);
    border: 1px solid rgba(234,179,8,0.35);
    border-radius: var(--radius);
    padding: var(--space-5);
    margin-bottom: var(--space-6);
}

.availability-affected h3 {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0 0 var(--space-2);
    font-size: var(--fs-h3-compact);
    color: #eab308;
}

.availability-affected > .text-muted-sm { display: block; margin-bottom: var(--space-4); }

.availability-affected__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius-sm);
    padding: var(--space-3) var(--space-4);
}

.availability-affected__row + .availability-affected__row { margin-top: var(--space-2); }

.availability-affected__info {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
}

/* Wraps rather than squeezing the inputs to nothing on a narrow window — the date
   and time pickers have a floor below which they stop being usable. */
.availability-affected__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

.availability-affected__actions .form-control { width: auto; min-width: 8.5rem; }

/* Admin utility: success banner & info banner */
.admin-success {
    background: rgba(61,187,72,0.10);
    border: 1px solid rgba(61,187,72,0.3);
    color: var(--green);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-6);
    font-size: var(--fs-sm);
}

/* Amber counterpart to .admin-success — something that needs attention but isn't an
   error, like nobody being marked as a mechanic. */
.admin-inline-warning {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    background: rgba(234,179,8,0.10);
    border: 1px solid rgba(234,179,8,0.3);
    color: #eab308;
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-6);
    font-size: var(--fs-sm);
}

.admin-inline-warning strong { color: inherit; }

.pricing-info-banner {
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius-sm);
    padding: var(--space-3) var(--space-4);
    margin-bottom: var(--space-6);
    font-size: var(--fs-sm);
    color: rgba(255,255,255,0.5);
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-5);
}
.pricing-info-banner strong { color: var(--white); }

/* ============================================================
   ADMIN CARD VIEW (mobile only)
   ============================================================ */

/* Mobile: show cards, hide the table.
   The hide is on the --desktop-only modifier, not on .admin-table-wrapper itself.
   It used to apply to every wrapper, but only the bookings table had a card
   fallback — so the Pricing and Staff tabs, which reuse the wrapper, rendered a
   completely blank page on every phone. All three now have card layouts, and a
   wrapper only opts into being hidden when one exists. */
.admin-cards {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.admin-table-wrapper--desktop-only { display: none; }

.admin-card {
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    overflow: hidden;
    cursor: pointer;
    transition: border-color 0.2s;
}

.admin-card--open { border-color: rgba(61,187,72,0.4); }
@media (hover: hover) and (pointer: fine) {
    .admin-card:hover { border-color: var(--border-dark); }
    .admin-card--open:hover { border-color: rgba(61,187,72,0.4); }
}

.admin-card__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-3) var(--space-4) var(--space-2);
    gap: var(--space-2);
}

.admin-card__header-right {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    /* May be narrower than what it holds, which is what lets the badges wrap. */
    min-width: 0;
}

/* A booking's badges. Two of them and the reference are wider than a phone's card, and
   as loose items in the row above they could not wrap: the second ran under the
   chevron and off the card's edge, where overflow: hidden cut it off without a
   scrollbar to say so. As a group they stack, right-aligned, and the chevron keeps
   its place. */
.admin-card__badges {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-1) var(--space-2);
    min-width: 0;
}

.admin-card__chevron {
    flex-shrink: 0;
    font-size: 1.3rem;
    color: rgba(255,255,255,0.3);
    line-height: 1;
    transition: transform 0.2s;
    display: inline-block;
}

.admin-card__chevron.open { transform: rotate(90deg); color: var(--green); }

/* An enquiry card has nothing to expand — everything it holds is already on it —
   so it doesn't offer the pointer the bookings cards do. */
.admin-card--static { cursor: default; }

/* Handled ones stay readable but stop competing with the ones still outstanding. */
.admin-card--handled { opacity: 0.6; }

.admin-card__name {
    padding: 0 var(--space-4) var(--space-1);
    font-weight: 700;
    font-size: var(--fs-body);
    color: var(--white);
}

.admin-card__meta {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 var(--space-4) var(--space-3);
    gap: var(--space-2);
    flex-wrap: wrap;
}

.admin-card__meta span:first-child { font-size: var(--fs-xs); color: rgba(255,255,255,0.6); }
.admin-card__meta span:last-child  { font-size: var(--fs-caption); color: rgba(255,255,255,0.4); }

.admin-card__detail {
    border-top: 1px solid var(--border-dark);
    padding: var(--space-3) var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    background: rgba(61,187,72,0.03);
}

.admin-card__detail-row {
    display: flex;
    gap: var(--space-3);
    font-size: var(--fs-caption);
}

/* Direct child, not any descendant: as `span:first-child` this also matched the
   first <span> inside any nested markup in the value column, which uppercased and
   shrank the first job note on every expanded booking. */
.admin-card__detail-row > span:first-child {
    color: rgba(255,255,255,0.35);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-size: var(--fs-2xs);
    min-width: 48px;
    flex-shrink: 0;
    padding-top: var(--space-05);
}

/* Direct child for the same reason as the rule above. */
.admin-card__detail-row > span:last-child {
    color: rgba(255,255,255,0.75);
    word-break: break-word;
}

.admin-card__actions {
    display: flex;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--border-dark);
    background: var(--dark-3);
    flex-wrap: wrap;
}

/* Admin hero row */
.admin-hero-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-4);
}

/* ============================================================
   RESPONSIVE
   ============================================================ */

@media (min-width: 480px) {
    .timeslot-grid { grid-template-columns: repeat(3, 1fr); }
    .admin-stats { grid-template-columns: repeat(4, 1fr); }
}

@media (min-width: 640px) {
    .service-pick-grid { display: grid; grid-template-columns: repeat(2, 1fr); }
    .schedule-grid { grid-template-columns: 1fr 1fr; }
    .booking-detail__grid { grid-template-columns: repeat(4, 1fr); }
}

@media (min-width: 768px) {
    .timeslot-grid { grid-template-columns: repeat(4, 1fr); }
    .booking-card { padding: var(--space-10); }

    /* Filters: the chips keep a line to themselves, and search and sort share the
       next. Seven chips, a search box and a sort picker are about 1,000px side by
       side, which a tablet does not have. */
    .admin-filters {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
    }
    .admin-filter-scroll { flex: 1 1 100%; }
    .admin-search { width: auto; min-width: 220px; flex: 1 1 260px; }
}

@media (min-width: 1200px) {
    /* One line, once there is room for all three. */
    .admin-filter-scroll { flex: 1 1 0; }
    .admin-search { flex: 0 1 260px; }
}

/* ============================================================
   ADMIN CUSTOMERS, ENQUIRIES & STAFF — card layouts
   ------------------------------------------------------------
   What each list shows until its table fits: one column on a phone, a grid on a
   tablet.
   ============================================================ */
.admin-mobile-cards {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

/* The switch has to sit here, after both card lists are defined, rather than with
   the other breakpoint rules further up: at equal specificity the later declaration
   wins whatever media query it is in, and placed above .admin-mobile-cards these
   rules lost to it — cards and table both rendering, every row twice. */
/* ── Cards or a table: each list shows its table from the width the table fits ──
   This switched at 768px for every list, which is where a phone ends rather than
   where any of these tables fits: on a tablet the bookings table was 490px wider
   than its frame, and the staff table was still 176px too wide on a desktop. A
   table that scrolls sideways hides its last columns — here, the actions.

   So the table waits until it fits, and until then the list is cards: two or three
   across on a tablet rather than one very wide one. The container stops growing at
   1200px, so a table that fits at 1200 fits at every width above it. */
@media (min-width: 720px) {
    .admin-cards,
    .admin-mobile-cards {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
        /* Each card is its own height: opening one must not stretch its neighbours. */
        align-items: start;
    }

    .admin-cards > .admin-empty,
    .admin-mobile-cards > .admin-empty { grid-column: 1 / -1; }
}

/* Customers: six short columns. */
@media (min-width: 1024px) {
    .admin-mobile-cards--to-1024 { display: none; }
    .admin-table-wrapper--from-1024 { display: block; }
}

/* Bookings, enquiries, staff: wide tables with a column of controls. */
@media (min-width: 1200px) {
    .admin-cards,
    .admin-mobile-cards--to-1200 { display: none; }
    .admin-table-wrapper--from-1200 { display: block; }
}

.admin-edit-card {
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    padding: var(--space-5);
}

.admin-edit-card__title {
    font-weight: 700;
    font-size: var(--fs-body);
    color: var(--white);
    margin-bottom: var(--space-05);
}

.admin-edit-card__meta {
    font-size: var(--fs-xs);
    color: rgba(255,255,255,0.45);
    margin-bottom: var(--space-3);
}

/* What somebody typed — a name, an address — may break anywhere rather than hold a
   card wider than the screen it is on. A 58-character address did exactly that to
   the customer list on a 360px phone, by three pixels. */
.admin-edit-card__title,
.admin-edit-card__meta,
.admin-card__name,
.admin-card__meta span {
    max-width: 100%;
    overflow-wrap: anywhere;
}

/* Label and control on one line, wrapping to two when the label is long. */
.admin-edit-card__field {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
    padding: var(--space-2) 0;
    border-top: 1px solid var(--border-dark);
}

.admin-edit-card__field:first-of-type { border-top: none; }

.admin-edit-card__field > span {
    font-size: var(--fs-sm);
    color: rgba(255,255,255,0.6);
}

.admin-edit-card__field .form-control { width: auto; min-width: 7rem; }

.admin-edit-card__actions {
    display: flex;
    gap: var(--space-2);
    margin-top: var(--space-3);
}

.admin-edit-card__actions .btn,
.admin-edit-card__actions .action-btn { flex: 1; justify-content: center; }

/* A checkbox big enough to hit. The bare 13px checkboxes in the staff table were
   unusable on touch. */
.admin-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) 0;
    border-top: 1px solid var(--border-dark);
    font-size: var(--fs-sm);
    color: rgba(255,255,255,0.7);
    cursor: pointer;
    min-height: var(--touch-min);
}

.admin-toggle input[type="checkbox"] {
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    accent-color: var(--green);
    cursor: pointer;
}

/* ============================================================
   ADMIN CALENDAR — toolbar and motion
   ------------------------------------------------------------
   One toolbar, the way calendar apps lay it out: arrows and where you are on the
   left, Today / date / view on the right. On a phone the arrows flank the title on
   the first line — a single wrapping row used to split the two arrows across lines —
   and the tools sit beneath.
   ============================================================ */
.cal-toolbar {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
}

.cal-toolbar__nav {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    min-width: 0;
}

.cal-toolbar__prev  { order: 1; }
.cal-toolbar__title { order: 2; }
.cal-toolbar__next  { order: 3; }

.cal-toolbar__title {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    overflow: hidden;
    color: var(--white);
    font-size: var(--fs-h3-compact);
    font-weight: 700;
    line-height: 1.2;
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Only one of the two lengths is ever shown. */
.cal-toolbar__title--full  { display: none; }
.cal-toolbar__title--short { display: inline; }

/* Round, quiet icon buttons — the green fill the booking page gives its arrows is
   too loud for something clicked this often. */
.cal-toolbar .cal-nav__btn {
    width: 36px;
    height: 36px;
    border-color: transparent;
    border-radius: 50%;
    background: transparent;
    transition: background-color 0.15s, transform 0.1s;
}

.cal-toolbar .cal-nav__btn:active { transform: scale(0.92); }

@media (hover: hover) and (pointer: fine) {
    .cal-toolbar .cal-nav__btn:hover:not(:disabled) { background: var(--dark-4); border-color: transparent; }
}

.cal-toolbar__tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

.cal-toolbar__today {
    height: 36px;
    padding: 0 var(--space-4);
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: 8px;
    color: var(--white);
    font-family: inherit;
    font-size: var(--fs-caption);
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, opacity 0.15s;
}

.cal-toolbar__today:disabled { opacity: 0.4; cursor: default; }

@media (hover: hover) and (pointer: fine) {
    .cal-toolbar__today:hover:not(:disabled) { border-color: var(--green); color: var(--green); }
}

.day-cal__picker {
    width: auto;
    height: 36px;
    padding: var(--space-1) var(--space-3);
    font-size: var(--fs-xs);
    color-scheme: dark;
}

/* Day · Week · Month as one segmented control. The thumb is a single element that
   slides to the chosen option — the options are equal columns, so moving it by its
   own width per step lands it exactly — rather than three pills that each switch
   their colour on and off. */
.cal-seg {
    position: relative;
    isolation: isolate;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    margin-left: auto;
    padding: 3px;
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: 10px;
}

.cal-seg__thumb {
    position: absolute;
    z-index: -1;
    top: 3px;
    bottom: 3px;
    left: 3px;
    width: calc((100% - 6px) / 3);
    background: var(--green);
    border-radius: 7px;
    box-shadow: 0 2px 10px rgba(61,187,72,0.3);
    transform: translateX(calc(var(--seg-i, 0) * 100%));
    transition: transform 0.3s cubic-bezier(0.3, 0.8, 0.2, 1);
}

.cal-seg__btn {
    min-width: 64px;
    height: 30px;
    padding: 0 var(--space-3);
    background: transparent;
    border: none;
    border-radius: 7px;
    color: rgba(255,255,255,0.7);
    font-family: inherit;
    font-size: var(--fs-caption);
    font-weight: 600;
    cursor: pointer;
    transition: color 0.2s;
}

.cal-seg__btn.active { color: var(--dark); }
.cal-seg__btn:focus-visible { outline: 2px solid var(--white); outline-offset: -2px; }

@media (hover: hover) and (pointer: fine) {
    .cal-seg__btn:not(.active):hover { color: var(--white); }
}

@media (min-width: 900px) {
    .cal-toolbar {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
    }

    .cal-toolbar__prev  { order: 1; }
    .cal-toolbar__next  { order: 2; }
    .cal-toolbar__title {
        order: 3;
        margin-left: var(--space-2);
        text-align: left;
    }

    .cal-toolbar__title--full  { display: inline; }
    .cal-toolbar__title--short { display: none; }

    .cal-toolbar__tools { flex-wrap: nowrap; }
}

/* Finger-sized on a phone; see TOUCH TARGETS further down. */
@media (max-width: 767px) {
    .cal-toolbar .cal-nav__btn {
        width: var(--touch-min);
        height: var(--touch-min);
    }

    .cal-toolbar__today,
    .day-cal__picker { height: var(--touch-min); }

    /* The view switch spans the width as its own row, above Today and the date,
       rather than wrapping onto a line of its own pushed to the right. */
    .cal-seg {
        order: -1;
        flex: 1 1 100%;
        margin-left: 0;
    }

    .cal-seg__btn { height: 38px; }
    .day-cal__picker { flex: 1 1 auto; }
}

.day-cal__summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
    color: rgba(255,255,255,0.6);
    font-size: var(--fs-body-sm);
}

.day-cal__closed {
    color: #f87171;
    font-size: var(--fs-xs);
    font-weight: 600;
}

/* ── Moving between periods ──────────────────────────────────────────────
   The stage is re-keyed whenever what it shows changes, so each new week or view
   enters with a short slide from the direction you travelled (or a settle, for a
   change of view). Entrance only: the outgoing view is simply replaced, which at
   this length reads as one motion rather than two. */
.cal-stage-wrap {
    position: relative;
    /* The entering slide starts 18px to one side; clip it rather than let it widen
       the page for a quarter of a second. Clip, not hidden, so nothing inside becomes
       a scroll container and the grid's sticky headers keep working. */
    overflow-x: clip;
}

.cal-stage { transition: opacity 0.2s; }

.cal-stage--next { animation: cal-in-next 0.26s cubic-bezier(0.2, 0.8, 0.2, 1) both; }
.cal-stage--prev { animation: cal-in-prev 0.26s cubic-bezier(0.2, 0.8, 0.2, 1) both; }
.cal-stage--swap { animation: cal-in-swap 0.24s cubic-bezier(0.2, 0.8, 0.2, 1) both; }

@keyframes cal-in-next { from { opacity: 0; transform: translateX(18px); } }
@keyframes cal-in-prev { from { opacity: 0; transform: translateX(-18px); } }
@keyframes cal-in-swap { from { opacity: 0; transform: translateY(6px) scale(0.995); } }

/* Loading: nothing at all for the first 120ms — most answers arrive inside that, and
   a bar that flashes on and straight off reads as a glitch — then a thin sweeping bar
   and the old view dimmed, so a slow click still visibly did something. The delay
   lives on the loading state only, so the view brightens again the moment it lands. */
.cal-progress {
    position: absolute;
    top: -8px;
    left: 0;
    right: 0;
    height: 3px;
    overflow: hidden;
    border-radius: 3px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s;
}

.cal-progress::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: -35%;
    width: 35%;
    border-radius: 3px;
    background: var(--green);
    animation: cal-progress 1s ease-in-out infinite;
}

@keyframes cal-progress { to { left: 100%; } }

.cal-stage-wrap.is-loading .cal-progress { opacity: 1; transition-delay: 0.12s; }
.cal-stage-wrap.is-loading .cal-stage    { opacity: 0.55; transition-delay: 0.12s; }

@media (prefers-reduced-motion: reduce) {
    .cal-stage--next,
    .cal-stage--prev,
    .cal-stage--swap { animation: none; }
    .cal-seg__thumb { transition: none; }
    .cal-progress::after { animation-duration: 2.5s; }
}


/* ── Time grid (Day and Week) ────────────────────────────────────────────── */
.tgrid-scroll {
    --tgrid-gutter: 56px;
    --tgrid-head-h: 68px;
    --tgrid-hour-h: 64px;
    --tgrid-col-min: 0px;
    position: relative;
    overflow-x: auto;
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    scrollbar-width: thin;
}

/* On a wide screen the grid scrolls inside itself once it's taller than the window,
   with the day headers and the all-day row pinned above the hours — so the dates stay
   in sight at 6pm. 96px leaves room for the sticky site header. On a phone the page
   scrolls instead: a second vertical scroller inside a swiping one is a fight. */
@media (min-width: 768px) {
    .tgrid-scroll {
        overflow: auto;
        max-height: calc(100vh - 96px);
    }

    .tgrid__head,
    .tgrid__allday {
        position: sticky;
        z-index: 3;
        background: var(--dark-2);
    }

    .tgrid__head   { top: 0; }
    .tgrid__allday { top: var(--tgrid-head-h); }

    .tgrid__corner {
        top: 0;
        z-index: 4;
    }
}

.tgrid {
    display: grid;
    position: relative;
}

/* The time column stays put while the days swipe past it on a phone, so it needs
   its own background — otherwise the days scroll visibly underneath the labels. */
.tgrid__corner,
.tgrid__time {
    position: sticky;
    left: 0;
    z-index: 2;
    background: var(--dark-2);
    border-right: 1px solid var(--border-dark);
}

.tgrid__corner { border-bottom: 1px solid var(--border-dark); }

.tgrid__time {
    padding: var(--space-05) var(--space-2) 0 0;
    border-top: 1px solid var(--border-dark);
    text-align: right;
    font-size: var(--fs-2xs);
    color: rgba(255,255,255,0.45);
}

.tgrid__head {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-05);
    min-width: 0;
    padding: var(--space-2) var(--space-1);
    border: none;
    border-left: 1px solid var(--border-dark);
    background: transparent;
    color: var(--white);
    font-family: inherit;
}

button.tgrid__head { cursor: pointer; }

@media (hover: hover) and (pointer: fine) {
    button.tgrid__head:hover .tgrid__date { background: var(--dark-4); }
}

.tgrid__dow {
    font-size: var(--fs-2xs);
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: rgba(255,255,255,0.45);
}

.tgrid__date {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    font-size: 1.15rem;
    font-weight: 700;
    line-height: 1;
    transition: background-color 0.15s;
}

.tgrid__head--today .tgrid__dow  { color: var(--green); }
.tgrid__head--today .tgrid__date { background: var(--green); color: var(--dark); }

/* Closures and unreadable times, above the hours — Google's all-day row. */
.tgrid__allday {
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
    min-width: 0;
    min-height: var(--space-2);
    padding: var(--space-05);
    border-left: 1px solid var(--border-dark);
    border-bottom: 1px solid var(--border-dark);
}

.tgrid__allday .tblock { flex: 0 0 auto; }

/* Same amber as the booking page's closed days: shut, and worth noticing. The full
   reason is on the title where the column is too narrow to show it. */
.tgrid__banner {
    display: block;
    padding: var(--space-05) var(--space-2);
    background: rgba(234,179,8,0.12);
    border: 1px solid rgba(234,179,8,0.35);
    border-radius: 4px;
    color: #eab308;
    font-size: var(--fs-2xs);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tgrid__cell {
    position: relative;
    display: flex;
    gap: var(--space-05);
    min-width: 0;
    padding: var(--space-05);
    border-left: 1px solid var(--border-dark);
    border-top: 1px solid var(--border-dark);
}

/* Shading says what an hour is before anything is booked in it: hatched where the
   shop is shut, a flat tint for lunch, amber hatching for a part-day closure. */
.tgrid__cell--outsidehours {
    background: repeating-linear-gradient(135deg, rgba(255,255,255,0.025) 0 6px, transparent 6px 12px), rgba(0,0,0,0.28);
}

.tgrid__cell--lunch   { background: rgba(255,255,255,0.035); }
.tgrid__cell--blocked { background: repeating-linear-gradient(135deg, rgba(234,179,8,0.10) 0 6px, transparent 6px 12px); }

/* An overlay rather than a background, so it darkens whatever shading the hour
   already has instead of replacing it. */
.tgrid__cell--past::before {
    content: "";
    position: absolute;
    inset: 0;
    background: rgba(0,0,0,0.22);
    pointer-events: none;
}

.tgrid__cell-label {
    margin: auto;
    font-size: var(--fs-2xs);
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: rgba(255,255,255,0.3);
}

.tgrid__cell--blocked .tgrid__cell-label { color: rgba(234,179,8,0.7); }

/* The current time, across today's column. */
.tgrid__now {
    position: absolute;
    left: 0;
    right: 0;
    height: 2px;
    background: #ef4444;
    z-index: 1;
    pointer-events: none;
}

.tgrid__now::before {
    content: "";
    position: absolute;
    left: -5px;
    top: -4px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #ef4444;
}

/* ── A booking on the grid ───────────────────────────────────────────────
   Colour by status, from the same palette as the status badges. Two custom
   properties per status so the edge and the fill can't drift apart. */
.tblock {
    --tb: var(--green);
    --tb-bg: rgba(61,187,72,0.16);
    position: relative;
    z-index: 1;
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
    overflow: hidden;
    padding: var(--space-05) var(--space-2);
    background: var(--tb-bg);
    border: 1px solid transparent;
    border-left: 3px solid var(--tb);
    border-radius: 6px;
    color: var(--white);
    font-family: inherit;
    font-size: var(--fs-2xs);
    line-height: 1.25;
    text-align: left;
    cursor: pointer;
    transition: transform 0.15s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.15s, background-color 0.15s;
}

.tblock--pending   { --tb: #eab308; --tb-bg: rgba(234,179,8,0.12); border: 1px dashed rgba(234,179,8,0.6); border-left: 3px solid var(--tb); }
.tblock--confirmed  { --tb: var(--green); --tb-bg: rgba(61,187,72,0.16); }
.tblock--inprogress { --tb: #60a5fa; --tb-bg: rgba(59,130,246,0.18); }
.tblock--completed  { --tb: #9ca3af; --tb-bg: rgba(107,114,128,0.18); }
.tblock--cancelled  { --tb: #f87171; --tb-bg: rgba(220,38,38,0.08); opacity: 0.55; }
.tblock--cancelled .tblock__name { text-decoration: line-through; }

/* Lifts under the pointer and presses in under a click — transform and shadow only,
   which the browser animates without re-laying out the grid. */
@media (hover: hover) and (pointer: fine) {
    .tblock:hover {
        z-index: 2;
        transform: translateY(-1px);
        box-shadow: 0 6px 16px rgba(0,0,0,0.4);
    }
}

.tblock:active        { transform: scale(0.98); }
.tblock:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
.tblock--selected     { z-index: 2; box-shadow: 0 0 0 2px var(--white), 0 6px 16px rgba(0,0,0,0.4); }

.tblock__line {
    display: flex;
    align-items: baseline;
    gap: var(--space-1);
    min-width: 0;
}

.tblock__time { flex-shrink: 0; font-weight: 700; opacity: 0.8; }

.tblock__name,
.tblock__meta {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tblock__name { font-size: var(--fs-caption); font-weight: 600; }
.tblock__meta { color: rgba(255,255,255,0.6); }

.tblock--wide .tblock__name { font-size: var(--fs-sm); }

.tblock__late {
    font-weight: 700;
    color: #fb923c;
    text-transform: uppercase;
}

/* A phone: narrower gutter, and in Week exactly three days to a screen with the rest
   a swipe away, snapping so a day never stops half cut off. The width comes from the
   grid's own box (cqw, a container query unit) rather than the viewport: a guess at
   the page's margins left three columns a few pixels wider than the space, and a
   sliver of the previous day showed behind the time column. */
@media (max-width: 767px) {
    .tgrid-scroll {
        --tgrid-gutter: 44px;
        --tgrid-hour-h: 60px;
    }

    .tgrid-scroll--week {
        container-type: inline-size;
        --tgrid-col-min: calc((100cqw - var(--tgrid-gutter)) / 3);
        scroll-snap-type: x mandatory;
        scroll-padding-left: var(--tgrid-gutter);
    }

    .tgrid-scroll--week .tgrid__head { scroll-snap-align: start; }
}

/* ── Month grid ──────────────────────────────────────────────────────────
   The 1px gap over a border-coloured background draws every cell's border exactly
   once, with none doubled up where two cells meet. */
.mgrid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 1px;
    overflow: hidden;
    background: var(--border-dark);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
}

.mgrid__head {
    padding: var(--space-2) 0;
    background: var(--dark-2);
    text-align: center;
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    color: rgba(255,255,255,0.45);
}

.mgrid__cell {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    min-height: 112px;
    padding: var(--space-1);
    background: var(--dark-2);
}

.mgrid__cell--other { background: var(--dark); }
.mgrid__cell--other .mgrid__num { color: rgba(255,255,255,0.3); }
.mgrid__cell--shut  { background: rgba(234,179,8,0.05); }
.mgrid__cell--past .mgrid__pill { opacity: 0.6; }

.mgrid__num {
    display: flex;
    align-items: center;
    justify-content: center;
    align-self: flex-start;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: var(--white);
    font-family: inherit;
    font-size: var(--fs-caption);
    font-weight: 700;
    cursor: pointer;
    transition: background-color 0.15s, transform 0.1s;
}

.mgrid__num:active { transform: scale(0.9); }

@media (hover: hover) and (pointer: fine) {
    .mgrid__num:hover { background: var(--dark-4); }
}

.mgrid__cell--today .mgrid__num { background: var(--green); color: var(--dark); }

/* Struck through, as the booking page marks a closed day. */
.mgrid__cell--shut .mgrid__num { text-decoration: line-through; }

.mgrid__banner {
    padding: 0 var(--space-1);
    border-radius: 4px;
    background: rgba(234,179,8,0.12);
    color: #eab308;
    font-size: var(--fs-2xs);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mgrid__pill--pending,    .mgrid__bar--pending    { --pc: #eab308; }
.mgrid__pill--confirmed,  .mgrid__bar--confirmed  { --pc: var(--green); }
.mgrid__pill--inprogress, .mgrid__bar--inprogress { --pc: #60a5fa; }
.mgrid__pill--completed,  .mgrid__bar--completed  { --pc: #9ca3af; }

.mgrid__pill {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    width: 100%;
    min-width: 0;
    padding: 1px var(--space-1);
    border: none;
    border-radius: 4px;
    background: transparent;
    color: rgba(255,255,255,0.85);
    font-family: inherit;
    font-size: var(--fs-2xs);
    text-align: left;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color 0.12s;
}

@media (hover: hover) and (pointer: fine) {
    .mgrid__pill:hover { background: var(--dark-4); }
}

.mgrid__pill--selected { background: var(--dark-4); box-shadow: inset 0 0 0 1px var(--white); }

.mgrid__dot {
    flex-shrink: 0;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--pc);
}

.mgrid__pill-time { flex-shrink: 0; font-weight: 700; }

.mgrid__pill-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.mgrid__more {
    align-self: flex-start;
    padding: 0 var(--space-1);
    border: none;
    background: transparent;
    color: rgba(255,255,255,0.5);
    font-family: inherit;
    font-size: var(--fs-2xs);
    font-weight: 600;
    cursor: pointer;
}

.mgrid__bars { display: none; }

/* A phone: a cell is ~50px wide. The pills become one coloured bar per booking, and
   the day number's button is stretched over the whole cell, so the cell is one
   large target that opens the day. */
@media (max-width: 767px) {
    .mgrid__cell {
        align-items: center;
        min-height: 64px;
        padding: 2px;
    }

    .mgrid__num { align-self: center; }
    .mgrid__num::after {
        content: "";
        position: absolute;
        inset: 0;
    }

    .mgrid__pill,
    .mgrid__more,
    .mgrid__banner { display: none; }

    .mgrid__bars {
        display: flex;
        flex-direction: column;
        gap: 2px;
        width: 100%;
        padding: 0 var(--space-1);
    }

    .mgrid__bar {
        height: 4px;
        border-radius: 2px;
        background: var(--pc);
    }
}

/* ── Booking panel ───────────────────────────────────────────────────────
   A drawer from the right on a wide screen, a sheet from the bottom on a phone.
   Above the sticky navbar (1000) and the mobile menu (1100/1200). */
.cal-panel-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1300;
    background: rgba(0,0,0,0.5);
    animation: cal-fade-in 0.22s ease-out both;
}

.cal-panel-backdrop.is-closing { animation: cal-fade-out 0.16s ease-in both; }

@keyframes cal-fade-in  { from { opacity: 0; } }
@keyframes cal-fade-out { to   { opacity: 0; } }

.cal-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 1301;
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    width: min(420px, 100vw);
    overflow-y: auto;
    padding: var(--space-5);
    background: var(--dark-2);
    border-left: 1px solid var(--border-dark);
    box-shadow: -12px 0 32px rgba(0,0,0,0.45);
    outline: none;
    animation: cal-panel-in-side 0.26s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

/* Out is quicker than in, and eases the other way — it's getting out of the way, not
   arriving. 0.16s matches the pause CalendarTab.ClosePanelAsync waits before
   removing the panel. */
.cal-panel.is-closing { animation: cal-panel-out-side 0.16s ease-in both; }

@keyframes cal-panel-in-side    { from { transform: translateX(40px); opacity: 0; } }
@keyframes cal-panel-out-side   { to   { transform: translateX(40px); opacity: 0; } }
@keyframes cal-panel-in-bottom  { from { transform: translateY(100%); } }
@keyframes cal-panel-out-bottom { to   { transform: translateY(100%); } }

@media (prefers-reduced-motion: reduce) {
    .cal-panel,
    .cal-panel.is-closing,
    .cal-panel-backdrop,
    .cal-panel-backdrop.is-closing { animation: none; }
}

.cal-panel__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
}

.cal-panel__title { min-width: 0; }

.cal-panel__title h2 {
    margin: 0 0 var(--space-2);
    font-size: var(--fs-h3-compact);
    overflow-wrap: anywhere;
}

.cal-panel__badges {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

.cal-panel__close {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: 8px;
    color: var(--white);
    cursor: pointer;
}

.cal-panel__close svg { width: 16px; height: 16px; display: block; }

.cal-panel__facts {
    display: grid;
    gap: var(--space-3);
    margin: 0;
}

.cal-panel__facts > div {
    display: grid;
    grid-template-columns: 88px minmax(0, 1fr);
    gap: var(--space-3);
}

.cal-panel__facts dt {
    padding-top: 2px;
    font-size: var(--fs-2xs);
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: rgba(255,255,255,0.45);
}

.cal-panel__facts dd {
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--white);
    overflow-wrap: anywhere;
}

.cal-panel__facts .cal-panel__notes {
    font-style: italic;
    color: rgba(255,255,255,0.7);
    white-space: pre-wrap;
}

.cal-panel__actions {
    display: flex;
    flex-wrap: wrap;
    padding-top: var(--space-4);
    border-top: 1px solid var(--border-dark);
}

/* BookingActions' desktop rules (further down) squeeze it onto one line with icon-only
   contact buttons, because the bookings table has no room. The panel has the room at
   every width, so it keeps the card layout: the primary action across the top, and the
   three alternatives labelled beneath it. Four classes deep so it outranks those rules
   whichever order the two blocks end up in. */
.cal-panel__actions .booking-actions {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
}

.cal-panel__actions .booking-actions .booking-actions__primary { min-height: 40px; }

.cal-panel__actions .booking-actions .booking-actions__row {
    flex: 1 1 auto;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.cal-panel__actions .booking-actions__row .action-btn {
    flex: 1 1 0;
    min-height: 40px;
}

.cal-panel__actions .booking-actions__row .action-btn--whatsapp,
.cal-panel__actions .booking-actions__row .action-btn--call {
    width: auto;
    min-width: 0;
    padding-inline: var(--space-3);
}

.cal-panel__actions .booking-actions__row .action-btn--cancel { flex-grow: 0.62; }

.cal-panel__actions .booking-actions__row .action-btn .action-btn__label {
    position: static;
    width: auto;
    height: auto;
    overflow: visible;
    clip-path: none;
}

@media (max-width: 767px) {
    .cal-panel {
        top: auto;
        left: 0;
        width: auto;
        max-height: 85vh;
        padding: var(--space-4);
        border-left: none;
        border-top: 1px solid var(--border-dark);
        border-radius: 16px 16px 0 0;
        box-shadow: 0 -12px 32px rgba(0,0,0,0.45);
        animation-name: cal-panel-in-bottom;
    }

    .cal-panel.is-closing { animation-name: cal-panel-out-bottom; }

    .cal-panel__close {
        width: var(--touch-min);
        height: var(--touch-min);
    }
}

a.action-btn { text-decoration: none; }

/* ── One booking's controls, in two rows ─────────────────────────────────────
   Was a single flex-wrap row holding up to nine pills of equal weight; then one
   primary action with contact demoted to bare icon squares and Cancel hidden behind
   a "…" menu, which cost three taps to reach the second most common thing done to a
   booking. Now: the primary action across the top, and under it the three things you
   might do instead, all labelled. The menu is gone — Reopen was the only other thing
   in it, and it has the primary slot to itself on a cancelled or completed booking. */
.booking-actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    /* Its parents (.admin-card__actions, .action-buttons, .cal-panel__actions)
       are themselves flex, so without this it sizes to its content and the primary
       button never actually reaches full width. */
    flex: 1 1 100%;
    min-width: 0;
}

/* The thing tapped twenty times a day gets the full width and a full touch target.
   Column direction stretches it; the width is not set here. */
.booking-actions__primary { min-height: var(--touch-min); }

.booking-actions__row {
    display: flex;
    gap: var(--space-2);
    /* Wraps rather than overflowing if a translation ever outgrows a third of a
       narrow card — the automatic minimum size keeps each label readable first. */
    flex-wrap: wrap;
}

/* flex-basis 0 makes the grow values a straight ratio of the row, rather than a
   share of whatever is left over after the labels. */
.booking-actions__row .action-btn {
    flex: 1 1 0;
    min-height: var(--touch-min);
}

/* Cancel is the least used of the three by a wide margin, so it takes the smallest
   share. Not so small that the word crowds its own padding: flex's automatic minimum
   size stops it shrinking under the label, so on a very narrow card the ratio gives
   out before the text does. */
.booking-actions__row .action-btn--cancel {
    flex-grow: 0.62;
    padding-inline: var(--space-2);
}

/* In the table the whole set goes back on one line — the Actions cell has no room
   for a stacked block, and a mouse does not need 44px targets.

   Keyed on being in the table, not on a width. It used to be a min-width query set to
   wherever the cards gave way to the table, and every time that width moved the cards
   on the wrong side of it got mouse-sized buttons on what is most often a tablet. */
.admin-table .booking-actions {
    flex-direction: row;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: nowrap;
}
.admin-table .booking-actions__primary { min-height: 28px; }
.admin-table .booking-actions__row { flex: 0 0 auto; flex-wrap: nowrap; }

/* Four gaps at --space-2 is 32px of the Actions column spent on air. Half that is
   still legible at 28px tall and buys back headroom the table needs to sit inside
   its wrapper without a scrollbar. */
.admin-table .booking-actions,
.admin-table .booking-actions__row { gap: var(--space-1); }
.admin-table .booking-actions__row .action-btn {
    flex: 0 0 auto;
    min-height: 28px;
}
.admin-table .booking-actions__row .action-btn--cancel { padding-inline: var(--space-2); }

/* Two labelled contact pills pushed the table 110px past its wrapper, so in the table
   they are icon squares with their title tooltips. The labels earn their width on a
   card, where the target has to be finger-sized and a bare glyph is a guess; a mouse
   gets the tooltip instead. Cancel keeps its word everywhere — it is the destructive
   one, and a symbol is not enough warning. */
.admin-table .booking-actions__row .action-btn--whatsapp,
.admin-table .booking-actions__row .action-btn--call {
    /* Contains the absolutely-positioned label below. Without a positioned ancestor
       it answers to the page instead, so the table wrapper's overflow-x no longer
       clips it — and two 1px labels out past the right edge were enough to put a
       sideways scrollbar under the whole dashboard. */
    position: relative;
    width: 32px;
    min-width: 32px;
    padding-inline: 0;
}

/* Hidden, not removed: the label is the button's accessible name, and dropping it
   would leave a link announced as nothing at all. */
.admin-table .booking-actions__row .action-btn--whatsapp .action-btn__label,
.admin-table .booking-actions__row .action-btn--call .action-btn__label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ── An enquiry's controls ───────────────────────────────────────────────────
   Their own row rather than .booking-actions__row, which above 768px squeezes
   .action-btn--call to a 32px icon square and visually hides the label inside it.
   A booking survives that: the label is the word "Call" and the number is already
   in the row. An enquiry doesn't — the sender's address appears nowhere else, so
   there is nothing to hide it in favour of, and unhidden text in a 32px box with
   zero inline padding simply overprinted the button beside it. */
.enquiry-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.enquiry-actions .action-btn {
    min-height: var(--touch-min);
    gap: var(--space-1);
}

/* An address can be arbitrarily long, and the Actions cell sits inside the table's
   overflow-x wrapper — so it truncates here rather than pushing a scrollbar under
   the whole dashboard. The full value stays in the title and in the mailto:. */
.enquiry-actions__contact { min-width: 0; }

.enquiry-actions__text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* In the table: a mouse doesn't need 44px, and the cell has less room than a card.
   Keyed on the table rather than on a width, for the reason .booking-actions is.

   The four controls wrap into two rows inside a column of fixed width. On one line
   they took 520px of the row and left the message — the thing the row is about —
   a column one or two words wide. */
.admin-table .enquiry-actions {
    gap: var(--space-1);
    justify-content: flex-start;
    width: 17rem;
}
.admin-table .enquiry-actions .action-btn { min-height: 28px; }
/* The contact pills give up width first; WhatsApp and Mark handled keep theirs,
   because a truncated verb is useless where a truncated address still reads. */
.admin-table .enquiry-actions__contact { flex: 0 1 auto; max-width: 100%; }
.admin-table .enquiry-actions .action-btn--whatsapp,
.admin-table .enquiry-actions .action-btn--primary,
.admin-table .enquiry-actions .action-btn--keep { flex: 0 0 auto; }

.admin-table .enquiry-message { min-width: 14rem; }

/* The confirm step for Cancel. It used to fire on a single click, despite being
   irreversible and now emailing the customer. */
.booking-confirm {
    flex: 1 1 100%;
    margin-top: var(--space-3);
    padding: var(--space-3);
    background: rgba(220,38,38,0.07);
    border: 1px solid rgba(220,38,38,0.25);
    border-radius: var(--radius-sm);
}

.booking-confirm__text {
    margin-bottom: var(--space-3);
    color: rgba(255,255,255,0.8);
    font-size: var(--fs-sm);
    line-height: 1.5;
}

.booking-confirm__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }

/* Delete's new home, at the foot of the expanded detail. Separated by a rule so it
   reads as a different category of thing, not the next button along. */
.booking-danger {
    margin-top: var(--space-4);
    padding-top: var(--space-3);
    border-top: 1px solid var(--border-dark);
}

.booking-danger__text {
    margin-bottom: var(--space-3);
    color: rgba(255,255,255,0.6);
    font-size: var(--fs-sm);
    line-height: 1.5;
}

.booking-danger__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.action-btn--whatsapp { background: rgba(37,211,102,0.12); color: #25d366; border-color: rgba(37,211,102,0.3); }
@media (hover: hover) and (pointer: fine) {
    .action-btn--whatsapp:hover { background: #25d366; color: var(--dark); }
}
.action-btn--call { background: rgba(59,130,246,0.12); color: #60a5fa; border-color: rgba(59,130,246,0.3); }
@media (hover: hover) and (pointer: fine) {
    .action-btn--call:hover { background: #3b82f6; color: var(--white); }
}
.action-btn--keep { background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.7); border-color: rgba(255,255,255,0.15); }
@media (hover: hover) and (pointer: fine) {
    .action-btn--keep:hover { background: rgba(255,255,255,0.15); color: var(--white); }
}
/* Delete is permanent (two-step), so it stays muted until hover — distinct from
   the always-red "Cancel" which only changes a booking's status. */
.action-btn--delete { background: rgba(255,255,255,0.04); color: rgba(255,255,255,0.5); border-color: rgba(255,255,255,0.12); }
@media (hover: hover) and (pointer: fine) {
    .action-btn--delete:hover { background: #dc2626; color: var(--white); border-color: #dc2626; }
}

/* ============================================================
   iOS INPUT ZOOM — admin & booking controls
   ------------------------------------------------------------
   Companion to the block of the same name in app.css. It has to live here rather
   than there because this file is loaded second: the selectors below are defined
   in this file at 13.6–14px, media queries contribute no specificity, and at equal
   specificity the later stylesheet wins. Putting the override in app.css alone left
   every admin field still under the 16px threshold, so the page kept zooming on
   focus exactly where it was worst — the dashboard, on a phone.
   ============================================================ */
@media (max-width: 767px) {
    .form-control--compact,
    .form-control--price,
    .day-cal__picker,
    .admin-search .form-control {
        font-size: 16px;
    }

    /* 16px needs more room than the 13–14px these were spaced for. */
    .form-control--compact,
    .form-control--price {
        padding: var(--space-2) var(--space-3);
    }

    /* Fixed at 100px for a 14px font; too tight once the digits are 16px. */
    .form-control--price { width: 7.5rem; }
}

/* ============================================================
   TOUCH TARGETS (mobile)
   ------------------------------------------------------------
   Every control a finger has to hit gets at least --touch-min (44px) in the
   vertical direction, and as close to it horizontally as the layout allows. The
   visual sizes are unchanged on desktop, where a mouse makes small targets fine.

   The calendar day is the one compromise: it's a 7-column grid inside the booking
   card, so at a 390px viewport each cell can only be ~41px wide however the padding
   is tuned. Dropping aspect-ratio and setting an explicit min-height gives a
   41×44 target — short on one axis, but the axis that matters least, and far better
   than the ~31px square it collapsed to at 320px.
   ============================================================ */
@media (max-width: 767px) {
    .cal-nav__btn {
        width: var(--touch-min);
        height: var(--touch-min);
    }

    .cal-day {
        aspect-ratio: auto;
        min-height: var(--touch-min);
    }

    .cal-grid { gap: var(--space-05); }

    .timeslot { min-height: var(--touch-min); }

    .action-btn {
        min-height: var(--touch-min);
        padding: var(--space-2) var(--space-3);
    }

    .filter-tab {
        min-height: var(--touch-min);
        display: inline-flex;
        align-items: center;
        padding: var(--space-2) var(--space-4);
    }

    .bike-chip {
        min-height: var(--touch-min);
        display: inline-flex;
        align-items: center;
        padding: var(--space-2) var(--space-4);
    }

    /* The ✕ was an ~18px glyph with almost no padding around it. */
    .photo-picker-remove {
        min-width: var(--touch-min);
        min-height: var(--touch-min);
        display: inline-flex;
        align-items: center;
        justify-content: center;
        font-size: 1.35rem;
    }

    .service-pick-card { min-height: var(--touch-min); }

    .customer-auth-strip__link {
        min-height: var(--touch-min);
        display: inline-flex;
        align-items: center;
    }
}

/* ============================================================
   NOTIFICATION BELL (admin header)
   ------------------------------------------------------------
   Fed by NotificationService and pushed live over the circuit the dashboard already
   holds — see NotificationHub. The panel is absolutely positioned against .notif,
   so it needs the header row to allow overflow.
   ============================================================ */
.notif { position: relative; }

.notif__bell {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--touch-min);
    min-height: var(--touch-min);
    padding: var(--space-2);
    font-size: 1.25rem;
    background: transparent;
    border: 1px solid var(--border-dark);
    border-radius: var(--radius-sm);
    color: rgba(255,255,255,0.75);
    cursor: pointer;
    transition: border-color 0.2s, color 0.2s;
}

.notif__bell--open { border-color: var(--green); color: var(--green); }

@media (hover: hover) and (pointer: fine) {
    .notif__bell:hover { border-color: var(--green); color: var(--green); }
}

/* The unread count. Sits on the bell's top-right corner. */
.notif__badge {
    position: absolute;
    top: 2px;
    right: 2px;
    min-width: 18px;
    height: 18px;
    padding: 0 var(--space-1);
    border-radius: 9px;
    background: var(--green);
    color: var(--dark);
    font-size: 0.65rem;
    font-weight: 800;
    line-height: 18px;
    text-align: center;
}

.notif__panel {
    position: absolute;
    top: calc(100% + 0.5rem);
    right: 0;
    z-index: 1200;          /* above the sticky navbar (1000) */
    /* The panel is rendered inside .page-hero, which is text-align: center — so
       without this every notification came out centred like a heading. */
    text-align: left;
    width: min(22rem, calc(100vw - 2rem));
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

.notif__panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border-dark);
    font-size: var(--fs-sm);
    color: var(--white);
}

.notif__mark {
    background: none;
    border: none;
    color: var(--green);
    font-size: var(--fs-xs);
    font-weight: 600;
    cursor: pointer;
    padding: var(--space-1);
}

.notif__empty {
    padding: var(--space-6) var(--space-4);
    text-align: center;
    color: rgba(255,255,255,0.35);
    font-size: var(--fs-sm);
}

.notif__list {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 60vh;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.notif__item {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 0 var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border-dark);
}

.notif__item:last-child { border-bottom: none; }

/* A left bar rather than a background wash, so an unread item still reads as part
   of the same list. */
.notif__item--unread { box-shadow: inset var(--rule-accent) 0 0 var(--green); }

.notif__item-title {
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--white);
}

.notif__item-body {
    grid-column: 1;
    font-size: var(--fs-xs);
    color: rgba(255,255,255,0.55);
}

.notif__item-time {
    grid-row: 1;
    grid-column: 2;
    font-size: var(--fs-2xs);
    color: rgba(255,255,255,0.35);
    white-space: nowrap;
}

/* The panel is anchored to the bell, which sits in a right-aligned header row; on a
   phone that row wraps, so pin the panel to the viewport edge instead of the bell. */
@media (max-width: 479px) {
    /* A 22rem panel anchored to the bell would run off the left edge on a narrow
       screen, because the bell sits mid-row rather than at the edge. So the panel is
       anchored to the header row instead — still directly beneath the bell, but
       right-aligned to the container, where there is room for it.

       Note what this must NOT be: `position: static` on .notif alone. That removes
       the positioning context entirely, so the panel's `top: calc(100% + 0.5rem)`
       resolves against a far taller ancestor and it lands halfway down the page,
       nowhere near the bell that opened it. */
    .admin-header-actions { position: relative; }
    .notif { position: static; }
    .notif__panel {
        right: 0;
        left: auto;
        width: min(22rem, calc(100vw - 2rem));
    }
}

/* ---------- Pager (admin bookings list) ---------- */
/* Explicit prev/next rather than infinite scroll — on a phone, infinite scroll makes
   anything below the list unreachable. */
.pager {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-top: var(--space-5);
}

.pager__status {
    font-size: var(--fs-sm);
    color: rgba(255,255,255,0.5);
    text-align: center;
    flex: 1 1 auto;
}

.pager__btn { flex: 0 0 auto; }

@media (max-width: 479px) {
    .pager__status { order: 3; flex-basis: 100%; }
    .pager__btn { flex: 1 1 0; justify-content: center; }
}

/* The toggle between "unread only" and "read too". Sits under the list so the
   notifications themselves stay the first thing you read. */
.notif__panel-foot {
    padding: var(--space-2) var(--space-4) var(--space-3);
    border-top: 1px solid var(--border-dark);
    text-align: center;
}

/* ---------- Completion note (admin) ---------- */
/* Opens inline under the action buttons when a job is marked complete. flex-basis
   100% so it drops onto its own line rather than squeezing in beside the buttons. */
.complete-note {
    flex: 1 1 100%;
    margin-top: var(--space-3);
    padding: var(--space-3);
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius-sm);
    text-align: left;
}

.complete-note__label {
    display: block;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--white);
    margin-bottom: var(--space-2);
}

.complete-note__optional {
    font-weight: 400;
    color: rgba(255,255,255,0.4);
}

.complete-note__share {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-3);
    font-size: var(--fs-sm);
    color: rgba(255,255,255,0.75);
    cursor: pointer;
    min-height: var(--touch-min);
}

.complete-note__share input[type="checkbox"] {
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    accent-color: var(--green);
    cursor: pointer;
}

.complete-note__hint {
    font-size: var(--fs-xs);
    color: rgba(255,255,255,0.4);
    margin-bottom: var(--space-3);
}

.complete-note__actions {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
}

/* The mechanic's report on this job. Its own full-width line rather than another
   entry in the info column, so the reference and status badges stay level with the
   service name — the same shape .customer-booking__notes takes in the dashboard.
   order puts it last on a phone too, where the row reorders its other children. */
.account-booking-row__notes {
    flex: 1 1 100%;
    order: 4;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

/* Tinted green to read as something from the shop rather than more booking
   metadata, and headed so it is unmistakably the report on *this* job. */
.account-booking-row__note {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-3);
    background: rgba(61,187,72,0.07);
    border-left: var(--rule-accent) solid var(--green);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    color: rgba(255,255,255,0.75);
    line-height: 1.5;
}

.account-booking-row__note-head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--fs-2xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--green);
}

/* ============================================================
   CUSTOMERS TAB (admin)
   ============================================================ */
.admin-search--wide { flex: 1 1 100%; }

/* A list row that's a button — it opens the detail view. Inherits .admin-edit-card's
   box, so only the button resets and the stats row are needed here. */
.customer-card {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-05);
    width: 100%;
    text-align: left;
    font-family: inherit;
    color: inherit;
    cursor: pointer;
}

.customer-card__stats {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
    width: 100%;
    margin-top: var(--space-2);
    font-size: var(--fs-xs);
    color: rgba(255,255,255,0.45);
}

.customer-card:focus-visible {
    outline: 2px solid var(--green);
    outline-offset: 2px;
}

@media (hover: hover) and (pointer: fine) {
    .customer-card:hover { border-color: var(--green); }
}

/* ── Bulk customer import ────────────────────────────────────────────────────
   Three states in one panel — pick a file, review what it will do, see what it did.
   The review step is the point of the whole thing, so it gets the room. */
.import-panel { max-width: 46rem; }

.import-panel__title { margin-bottom: var(--space-3); font-size: var(--fs-h3-compact); color: var(--white); }

.import-panel__intro {
    margin-bottom: var(--space-6);
    color: rgba(255,255,255,0.65);
    font-size: var(--fs-sm);
    line-height: 1.6;
}

.import-panel__intro code {
    padding: 0 var(--space-1);
    background: var(--dark-3);
    border-radius: var(--radius-sm);
    font-size: var(--fs-xs);
}

.import-panel__note {
    margin-top: var(--space-2);
    color: rgba(255,255,255,0.45);
    font-size: var(--fs-xs);
    line-height: 1.6;
}

.import-panel__error {
    margin: var(--space-4) 0;
    padding: var(--space-3) var(--space-4);
    background: rgba(220,38,38,0.1);
    border: 1px solid rgba(220,38,38,0.3);
    border-radius: var(--radius-sm);
    color: #f87171;
    font-size: var(--fs-sm);
}

.import-summary {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin: var(--space-6) 0;
    padding: var(--space-5);
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
}

.import-summary__row {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    color: rgba(255,255,255,0.7);
    font-size: var(--fs-sm);
}

/* Tabular figures so the counts line up in a column rather than jittering. */
.import-summary__count {
    min-width: 3ch;
    color: var(--white);
    font-size: var(--fs-h3-compact);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.import-summary__count--create { color: var(--green); }
.import-summary__count--bad { color: #f87171; }

.import-problems { margin-top: var(--space-6); margin-bottom: var(--space-6); }
.import-problems h4 { margin-bottom: var(--space-3); font-size: var(--fs-sm); color: var(--white); }

.import-panel__opt {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin-top: var(--space-4);
    color: rgba(255,255,255,0.8);
    font-size: var(--fs-sm);
    line-height: 1.5;
    cursor: pointer;
}

.import-panel__actions {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    margin-top: var(--space-6);
}

/* ---------- Detail ---------- */
.customer-detail__back { margin-bottom: var(--space-5); }

.customer-detail__head { margin-bottom: var(--space-5); }
.customer-detail__head h3 { margin-bottom: var(--space-1); }

.customer-detail__contact {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    margin-top: var(--space-3);
}

.customer-detail__stats { margin-bottom: var(--space-5); }
.customer-detail__empty { margin-bottom: var(--space-5); }
.customer-detail__add-note { margin-bottom: var(--space-5); }

/* ---------- Enquiries tab ---------- */
/* The message as it was typed, newlines and all. Capped so one long message can't
   push the actions on every other row off the bottom of the screen. */
.enquiry-message {
    margin: 0;
    font-size: var(--fs-sm);
    line-height: 1.6;
    color: rgba(255,255,255,0.8);
    white-space: pre-wrap;
    max-height: 12rem;
    overflow-y: auto;
}

.admin-table__row--handled { opacity: 0.6; }

/* The outstanding count, on the tab itself — the reason to open it. */
.filter-tab__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    margin-left: var(--space-2);
    padding: 0 var(--space-1);
    border-radius: 50px;
    background: var(--green);
    color: var(--dark);
    font-size: var(--fs-2xs);
    font-weight: 700;
}

/* ---------- Notes ---------- */
.customer-notes,
.customer-bookings,
.customer-bikes {
    list-style: none;
    margin: 0 0 var(--space-6);
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.customer-note {
    padding: var(--space-3);
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius-sm);
}

/* Shared notes get the same green edge the customer sees on their own booking, so
   staff can tell at a glance which ones the customer is reading. */
.customer-note--shared { border-left: var(--rule-accent) solid var(--green); }

.customer-note__head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    margin-bottom: var(--space-2);
}

.customer-note__delete { margin-left: auto; }

.customer-note__body {
    font-size: var(--fs-sm);
    color: rgba(255,255,255,0.8);
    line-height: 1.6;
    white-space: pre-wrap;
}

/* ---------- Bookings & bikes ---------- */
.customer-booking {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    padding: var(--space-3);
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius-sm);
}

/* Never linked to the account — shown so a guest booking doesn't make a returning
   customer look like a stranger. */
.customer-booking--unlinked { opacity: 0.75; border-style: dashed; }

/* A job's notes, on the job. Full width so they wrap onto their own line under the
   row rather than squeezing the service name, and one step lighter than the row that
   holds them — the same relationship .account-booking-row has with its card. */
.customer-booking__notes {
    flex: 1 1 100%;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}
.customer-booking__notes .customer-note { background: var(--dark-3); }

.customer-booking__main {
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
    flex: 1 1 10rem;
    min-width: 0;
}

.customer-bikes li {
    padding: var(--space-3);
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
}

/* Staff notes shown on a booking's expanded row. Also the only place a note on a
   guest booking is visible, since a guest has no customer page to put it on. */
.booking-notes {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.booking-note {
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
    padding: var(--space-3);
    background: var(--dark-2);
    border-left: var(--rule-accent) solid var(--border-dark);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    line-height: 1.5;
    white-space: pre-wrap;
}

/* Green edge = the customer can see this one, matching how it looks on their own
   booking. */
.booking-note--shared { border-left-color: var(--green); }

/* ============================================================
   Insights tab
   ============================================================
   Charts are plain elements rather than SVG or a charting library: every
   figure here is a single magnitude against a maximum, which a div with a
   width already expresses, and the dashboard has no other client-side
   dependency to keep it company.

   The funnel uses one green ramp, light to dark, because its stages are
   ordered rather than distinct — a categorical palette there would imply the
   steps are five unrelated things. The steps are spaced so adjacent pairs stay
   apart for a colour-blind reader and the lightest still clears the dashboard's
   surface. */

.insights-stats { grid-template-columns: repeat(2, 1fr); }

.insights-delta {
    margin-top: var(--space-2);
    font-size: var(--fs-tiny);
    color: rgba(255,255,255,0.4);
}

.insights-delta--up   { color: var(--green); }
.insights-delta--down { color: #E29B4B; }

.insights-notice {
    background: rgba(226,155,75,0.08);
    border: 1px solid rgba(226,155,75,0.35);
    border-radius: var(--radius);
    padding: var(--space-4);
    margin-bottom: var(--space-5);
    font-size: var(--fs-sm);
    color: rgba(255,255,255,0.75);
}

.insights-notice strong { display: block; color: #E29B4B; margin-bottom: var(--space-1); }
.insights-notice code   { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

.insights-panel {
    background: var(--dark-2);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius);
    padding: var(--space-5);
    margin-bottom: var(--space-5);
}

.insights-panel__title {
    margin: 0 0 var(--space-3);
    font-size: var(--fs-body);
    font-weight: 600;
}

.insights-lede { margin: 0 0 var(--space-4); }

.insights-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-5);
}

.insights-footnote {
    font-size: var(--fs-tiny);
    color: rgba(255,255,255,0.35);
    margin-top: var(--space-6);
    line-height: 1.6;
}

/* Funnel */

.funnel-stage {
    display: grid;
    grid-template-columns: 7.5rem 1fr;
    gap: var(--space-3);
    align-items: center;
}

.funnel-stage__name {
    font-size: var(--fs-xs);
    color: rgba(255,255,255,0.75);
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    min-width: 0;
}

.funnel-stage__num {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: var(--fs-tiny);
    color: rgba(255,255,255,0.35);
    flex: none;
}

.funnel-stage__track { display: flex; align-items: center; height: 2.25rem; }

.funnel-stage__bar {
    height: 1.9rem;
    min-width: 3rem;
    border-radius: 0 4px 4px 0;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding-right: var(--space-2);
}

.funnel-stage__count {
    font-size: var(--fs-tiny);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    /* Dark ink on every step: the ramp's darkest rung still sits well above the
       surface, and one text colour beats four that each need checking. */
    color: #0A2A10;
}

.funnel-stage__bar--1 { background: #C6EFCA; }
.funnel-stage__bar--2 { background: #96E0A0; }
.funnel-stage__bar--3 { background: #63CB72; }
.funnel-stage__bar--4 { background: #34A845; }
.funnel-stage__bar--5 { background: #1F7A2E; }
.funnel-stage__bar--5 .funnel-stage__count { color: #EAF7EC; }

.funnel-drop {
    grid-column: 2;
    margin-left: 7.5rem;
    padding-left: var(--space-3);
    border-left: 1px solid var(--border-dark);
    font-size: var(--fs-tiny);
    color: rgba(255,255,255,0.4);
    font-variant-numeric: tabular-nums;
    line-height: 1.6;
}

.funnel-drop strong { color: rgba(255,255,255,0.7); font-weight: 600; }
.funnel-drop--worst strong { color: #E29B4B; }

/* Horizontal magnitude bars */

.insights-bars { display: flex; flex-direction: column; gap: var(--space-3); }
.insights-bars--tight { gap: var(--space-2); margin-top: var(--space-4); }

.insights-bar--weak { opacity: 0.45; }

.insights-bar__meta {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-3);
    margin-bottom: var(--space-1);
    font-size: var(--fs-xs);
}

.insights-bar__name {
    color: rgba(255,255,255,0.8);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.insights-bar__value {
    flex: none;
    font-weight: 600;
    color: var(--white);
    font-variant-numeric: tabular-nums;
}

.insights-bar__sub {
    margin-left: var(--space-2);
    font-weight: 400;
    color: rgba(255,255,255,0.35);
}

.insights-bar__track {
    height: 6px;
    background: var(--dark-4);
    border-radius: 3px;
    overflow: hidden;
}

.insights-bar__fill { height: 100%; background: var(--green); border-radius: 3px; }

/* Weekly columns */

.insights-columns {
    display: flex;
    align-items: flex-end;
    gap: var(--space-2);
    overflow-x: auto;
    padding-bottom: var(--space-1);
}

.insights-column {
    flex: 1 1 0;
    min-width: 2.25rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-1);
}

.insights-column__value {
    font-size: var(--fs-tiny);
    font-variant-numeric: tabular-nums;
    color: rgba(255,255,255,0.55);
}

/* The plot is a fixed-height box so the bar's percentage height has something to
   resolve against — a percentage height against an auto-height parent collapses to
   nothing, which is the classic way a bar chart renders as a row of slivers. */
.insights-column__plot {
    width: 100%;
    height: 7rem;
    display: flex;
    align-items: flex-end;
}

.insights-column__bar {
    width: 100%;
    min-height: 2px;
    background: var(--green);
    border-radius: 3px 3px 0 0;
}

.insights-column__label {
    font-size: var(--fs-tiny);
    color: rgba(255,255,255,0.3);
    white-space: nowrap;
}

.insights-pair {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-3);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--border-dark);
    font-size: var(--fs-xs);
}

.insights-pair__label { color: rgba(255,255,255,0.6); }
.insights-pair__value { font-weight: 600; font-variant-numeric: tabular-nums; }

@media (min-width: 768px) {
    .insights-stats { grid-template-columns: repeat(4, 1fr); }
    .insights-grid  { grid-template-columns: 1fr 1fr; }
    .funnel-stage   { grid-template-columns: 10rem 1fr; }
    .funnel-drop    { margin-left: 10rem; }
}

/* ── The guest manage-booking page ───────────────────────────────────────────
   NOTE: the closing brace above went missing in a merge, which left every rule from
   here to the end of the file inside that min-width: 768px block — so on a phone,
   where a link from an email is nearly always opened, this page had none of its
   layout. Anything appended to this file was silently desktop-only too.

   Built on .admin-login__card, which is already the shape for a single-purpose
   page reached from a link rather than from the site's own navigation. What it
   adds is the details list and the two action panels.

   The card centres its text; the details read as a list of facts, so they are
   pulled back to the left here rather than fighting it everywhere else. */
.manage-booking { max-width: 480px; }

.manage-booking__details {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-2) var(--space-4);
    margin: var(--space-6) 0;
    text-align: left;
}

.manage-booking__details dt {
    color: rgba(255,255,255,0.45);
    font-size: var(--fs-sm);
    align-self: center;
}

.manage-booking__details dd {
    margin: 0;
    color: rgba(255,255,255,0.9);
    font-size: var(--fs-body-sm);
    align-self: center;
}

/* Stacked rather than side by side: on a phone, two buttons sharing a row put
   "Cancel this booking" a thumb's width from the one they meant to press. */
.manage-booking__actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

/* The shared .booking-confirm__actions is a wrapping row, which is right inside a
   dashboard table cell and wrong here: at phone width the two buttons wrap onto
   separate lines at whatever width their labels happen to need, and two stacked
   buttons of different widths read as a layout accident. Full width, stacked. */
.manage-booking .booking-confirm__actions {
    margin-top: var(--space-4);
    flex-direction: column;
    gap: var(--space-3);
}

.manage-booking .booking-confirm__actions .btn { width: 100%; }

@media (min-width: 480px) {
    .manage-booking .booking-confirm__actions { flex-direction: row; }
    .manage-booking .booking-confirm__actions .btn { width: auto; flex: 1; }
}

/* Not an error — the shop explaining why this one needs a person. Coloured as
   information rather than as a failure, because the customer has done nothing
   wrong and the next step is a phone call, not a retry. */
.manage-booking__blocked {
    margin: var(--space-4) 0;
    padding: var(--space-3);
    background: rgba(255,255,255,0.04);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius-sm);
    color: rgba(255,255,255,0.7);
    font-size: var(--fs-sm);
    line-height: 1.5;
    text-align: left;
}

/* The card's own rule dims every <p> inside it, which is right for a form's
   helper text and wrong for the one line confirming what just happened. */
.manage-booking .success-message p {
    color: rgba(255,255,255,0.85);
    margin-bottom: 0;
}

/* ── Picking a new day and time (admin) ──────────────────────────────────────
   ReschedulePicker: a date and, once the day has been read, its free slots. Drawn
   in the bookings table, in a phone card and in the calendar drawer, so it sizes
   itself to whatever it has been put in rather than to the viewport — one column
   where it is narrow, two side by side where there is room for both. */
.reschedule-picker {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
    gap: var(--space-4);
    max-width: 520px;
    margin-bottom: var(--space-4);
    text-align: left;
}

.reschedule-picker .form-group { margin-bottom: 0; }

/* Why the day has nothing to offer. Takes the slot list's place beside the date, and
   lines up with the bottom of the field it sits next to. */
.reschedule-picker__note {
    align-self: end;
    margin: 0;
    padding: var(--space-3);
    background: rgba(255,255,255,0.04);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius-sm);
    color: rgba(255,255,255,0.7);
    font-size: var(--fs-sm);
    line-height: 1.5;
}

/* ── The account page: what a customer does about a booking ──────────────────
   Cancelling is a button on the row; moving it is a phone call, and this is the
   line under the list that says so. */
.account-booking-list__help {
    margin: var(--space-4) 0 0;
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: rgba(255,255,255,0.55);
}

.account-booking-list__help a { color: var(--green); white-space: nowrap; }

/* The row's own controls were mouse-sized at every width. On a phone they are how a
   customer cancels, so they get a finger-sized target there. */
@media (max-width: 767px) {
    .account-booking-row__actions .action-btn {
        min-height: var(--touch-min);
        padding-inline: var(--space-4);
    }
}

/* ── Move / Edit on a booking (admin) ────────────────────────────────────────
   BookingAmend. Sits above the danger zone in the expanded detail and under the
   actions in the calendar panel, ruled off the same way the danger zone is. */
.booking-amend {
    margin-top: var(--space-4);
    padding-top: var(--space-3);
    border-top: 1px solid var(--border-dark);
}

/* The calendar panel is a flex column with its own gap and already rules its
   sections off, so the margin would double up there. */
.cal-panel > .booking-amend { margin-top: 0; }

/* In the bookings table the detail row is as wide as the table, and on a tablet the
   table is wider than the wrapper it scrolls in — so a form laid out across the row
   put half its fields past the right edge. Capped to what is on screen and pinned to
   the wrapper's left edge, it is all there whichever way the table is scrolled. The
   cap also stops a bike description running 1,100px wide on a desktop. */
.booking-detail .booking-amend {
    position: sticky;
    left: var(--space-4);
    max-width: min(720px, calc(100vw - 5rem));
}

.booking-amend__buttons {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.booking-amend__panel { margin-top: 0; }

.booking-amend__notify { margin-top: 0; margin-bottom: var(--space-3); }

/* One column in a phone card or the drawer, two wherever the panel is wide enough —
   sized from the panel, not the viewport, because the same component is drawn in a
   380px drawer on a 1440px screen. */
.booking-amend__fields {
    display: grid;
    /* 280px, so the four short fields pair up two by two in the table's detail row
       rather than going three and one. */
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
    gap: var(--space-3) var(--space-4);
    margin-bottom: var(--space-3);
}

.booking-amend__fields .form-group { margin-bottom: 0; }
.booking-amend__field--wide { grid-column: 1 / -1; }

/* The site-wide textarea floor is sized for the contact form's message box. */
.booking-amend__fields textarea.form-control { min-height: 0; }

.booking-amend__hint {
    display: block;
    margin-top: var(--space-1);
    font-size: var(--fs-xs);
    color: rgba(255,255,255,0.45);
}

/* Finger-sized wherever it is drawn as a card or a sheet — which is now every width a
   list is still cards at, tablets included — and mouse-sized only in the table. */
.booking-amend .action-btn {
    min-height: var(--touch-min);
    padding-inline: var(--space-4);
}
.booking-amend__buttons .action-btn,
.booking-amend .complete-note__actions .action-btn { flex: 1 1 0; }

.admin-table .booking-amend .action-btn {
    min-height: 28px;
    padding-inline: var(--space-3);
    flex: 0 0 auto;
}

/* The confirm steps and the completion note open inside a card as well, and their
   buttons were mouse-sized there: "Yes, cancel it" at 28px, on a phone. */
.admin-card .booking-confirm__actions .action-btn,
.admin-card .complete-note__actions .action-btn,
.admin-card .booking-danger .action-btn { min-height: var(--touch-min); }

/* In the calendar panel every other control is 40px on a wide screen, where the
   panel is a drawer worked with a mouse; Move and Edit match them. Below 768px it is
   a sheet under a thumb and they keep the full 44px. */
@media (min-width: 768px) {
    .cal-panel .booking-amend .action-btn { min-height: 40px; }
}

/* ── Completing a job: the final price ───────────────────────────────────────
   A pound sign fixed in front of the field rather than typed into it, so the value
   is a number and nothing else. */
.complete-note__price {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    max-width: 12rem;
}

.complete-note__price > span {
    font-weight: 700;
    color: rgba(255,255,255,0.6);
}

/* The panel's labels follow one another now, and the second needs air above it. */
.complete-note__hint + .complete-note__label { margin-top: var(--space-3); }

/* ── How a finished job ended ────────────────────────────────────────────────
   BookingOutcome: final price, whether the customer was told, whether the bike has
   gone. Three label/value pairs that sit side by side where there is room and stack
   where there is not — sized from the box it is in, since it is drawn in a table row,
   a phone card and the calendar drawer. */
.booking-outcome {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));
    gap: var(--space-3) var(--space-5);
    margin: var(--space-4) 0 0;
    padding-top: var(--space-3);
    border-top: 1px solid var(--border-dark);
}

.cal-panel > .booking-outcome { margin-top: 0; }

.booking-outcome > div { display: flex; flex-direction: column; gap: var(--space-05); min-width: 0; }

.booking-outcome dt {
    font-size: var(--fs-2xs);
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
    color: rgba(255,255,255,0.4);
}

.booking-outcome dd {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: 0;
    font-size: var(--fs-sm);
    color: rgba(255,255,255,0.85);
}

.booking-outcome__none { color: rgba(255,255,255,0.45); }
.booking-outcome__note { font-size: var(--fs-tiny); color: rgba(255,255,255,0.45); }
.booking-outcome__overdue { color: #fb923c; font-weight: 600; }

/* Same sticky cap as the Move / Edit block beneath it, for the same reason: in the
   table the detail row is as wide as the table, not as wide as the screen. */
.booking-detail .booking-outcome {
    position: sticky;
    left: var(--space-4);
    max-width: min(720px, calc(100vw - 5rem));
}

.admin-card .booking-outcome .action-btn { min-height: var(--touch-min); padding-inline: var(--space-4); }

/* In a card it follows a column of label-then-value rows, so it is three more of
   them rather than a block set differently underneath. */
.admin-card .booking-outcome {
    grid-template-columns: 1fr;
    gap: var(--space-2);
    margin-top: var(--space-1);
}
.admin-card .booking-outcome > div { flex-direction: row; align-items: baseline; gap: var(--space-3); }
.admin-card .booking-outcome dt { flex-shrink: 0; font-weight: 400; color: rgba(255,255,255,0.35); }
.admin-card .booking-outcome dd { font-size: var(--fs-caption); color: rgba(255,255,255,0.75); }

/* ── "Your bike is ready" on the customer's own pages ────────────────────────
   Green-edged, like the mechanic's report it often sits above: something from the
   shop, and good news rather than a reason they can't press a button. */
.account-booking-row__ready,
.manage-booking__ready {
    margin: 0;
    padding: var(--space-3);
    background: rgba(61,187,72,0.08);
    border-left: var(--rule-accent) solid var(--green);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    color: rgba(255,255,255,0.85);
    font-size: var(--fs-sm);
    line-height: 1.6;
    text-align: left;
}

.account-booking-row__ready { flex: 1 1 100%; order: 3; }
.manage-booking__ready { margin: var(--space-4) 0; }

/* ============================================================
   NEW BOOKING — a booking keyed in by staff
   ------------------------------------------------------------
   The button that opens the panel, the empty hours on the calendar that open it
   too, and the form inside the drawer the booking detail already uses.
   ============================================================ */

/* The one filled button on the dashboard's working screens: everything else there
   acts on a booking that exists, and this makes one. */
.admin-add-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    height: 36px;
    padding: 0 var(--space-4);
    background: var(--green);
    border: 1px solid var(--green);
    border-radius: 8px;
    color: var(--dark);
    font-family: inherit;
    font-size: var(--fs-caption);
    font-weight: 700;
    white-space: nowrap;
    cursor: pointer;
    transition: filter 0.15s;
}

.admin-add-btn:focus-visible { outline: 2px solid var(--white); outline-offset: 2px; }

@media (hover: hover) and (pointer: fine) {
    .admin-add-btn:hover { filter: brightness(1.1); }
}

/* On the calendar it sits at the end of the line that counts the day's bookings. */
.day-cal__new { margin-left: auto; }

.admin-new { flex-shrink: 0; }

@media (max-width: 767px) {
    .admin-add-btn { height: var(--touch-min); }

    /* Under the search and the sort, as wide as they are. */
    .admin-new,
    .admin-new .admin-add-btn { width: 100%; }
}

/* An empty hour the shop keeps: the whole cell is the button. Nothing is drawn
   until it is pointed at or tabbed to — a week is seventy hours, and a plus sign in
   every one of them is a wall of plus signs. */
.tgrid__add {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 0;
    padding: 0;
    background: transparent;
    border: 1px dashed transparent;
    border-radius: 6px;
    color: var(--green);
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.12s, background 0.12s, border-color 0.12s;
}

.tgrid__add:focus-visible {
    opacity: 1;
    border-color: var(--green);
    background: rgba(61,187,72,0.08);
    outline: none;
}

@media (hover: hover) and (pointer: fine) {
    .tgrid__add:hover {
        opacity: 1;
        border-color: rgba(61,187,72,0.5);
        background: rgba(61,187,72,0.08);
    }
}

/* A touch screen has no hover to reveal it with. The day view has room for a hint,
   so each free hour there shows a faint plus; the week's are simply tappable. */
@media (hover: none) {
    .tgrid-scroll--day .tgrid__add { opacity: 0.35; }
}

/* ── The form ── */
.new-booking__lede {
    margin: 0;
    font-size: var(--fs-xs);
    color: rgba(255,255,255,0.5);
}

/* A column, so the field can be stretched to the height of its neighbour: a date
   input and a select are not the same height by nature, and side by side that shows. */
.new-booking .form-group {
    display: flex;
    flex-direction: column;
    margin-bottom: 0;
}

.new-booking__fields select.form-control { flex: 1 1 auto; }

/* Two columns for the one short pair — the date and the time. Everything else spans
   both: an email address is the longest thing typed here, and half a drawer shows
   two thirds of one. */
.new-booking__fields {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3);
}

.new-booking__field--wide { grid-column: 1 / -1; }

/* The site-wide textarea floor is sized for the contact form's message box. */
.new-booking__fields textarea.form-control { min-height: 0; }

.new-booking__hint {
    margin: 0;
    font-size: var(--fs-xs);
    color: rgba(255,255,255,0.45);
}

.new-booking__find .new-booking__hint { margin-top: var(--space-2); }

.new-booking__day-note { margin-bottom: 0; }

.new-booking__matches {
    display: grid;
    gap: var(--space-1);
    margin: var(--space-2) 0 0;
    padding: 0;
    list-style: none;
}

.new-booking__match {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    width: 100%;
    min-height: var(--touch-min);
    padding: var(--space-2) var(--space-3);
    background: var(--dark-3);
    border: 1px solid var(--border-dark);
    border-radius: var(--radius-sm);
    color: var(--white);
    font-family: inherit;
    font-size: var(--fs-sm);
    text-align: left;
    cursor: pointer;
}

.new-booking__match strong,
.new-booking__match span { overflow-wrap: anywhere; }

.new-booking__match span {
    font-size: var(--fs-xs);
    color: rgba(255,255,255,0.5);
}

.new-booking__match:focus-visible { border-color: var(--green); outline: none; }

@media (hover: hover) and (pointer: fine) {
    .new-booking__match:hover { border-color: var(--green); }
}

.new-booking__bikes {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-2);
}

.new-booking__notify { margin-top: 0; }

.new-booking__actions .action-btn {
    flex: 1 1 0;
    min-height: 40px;
}

@media (max-width: 767px) {
    .new-booking__actions .action-btn { min-height: var(--touch-min); }
}

/* ── The service being booked, on the calendar step ──────────────────────────
   Said on the step itself: arriving from a "Book" button under a service skips the
   step that would have shown it. */
.booking-chosen {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-1) var(--space-4);
    margin: 0 0 var(--space-5);
    padding: var(--space-3) var(--space-4);
    background: rgba(61,187,72,0.08);
    border-left: var(--rule-accent) solid var(--green);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    color: rgba(255,255,255,0.85);
    font-size: var(--fs-sm);
    text-align: left;
}

.booking-chosen > span { min-width: 0; overflow-wrap: anywhere; }
.booking-chosen strong { color: var(--white); }

.booking-chosen__change {
    font-size: var(--fs-sm);
    font-weight: 600;
    text-decoration: underline;
}

@media (max-width: 767px) {
    .booking-chosen__change { min-height: var(--touch-min); }
}

/* ── "Email me when this bike is due its next service" ───────────────────────
   The one optional yes on the review step. A whole-row label, so the box is as easy
   to hit as the sentence beside it. */
.reminder-optin {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: var(--touch-min);
    margin: var(--space-4) 0 var(--space-2);
    color: rgba(255,255,255,0.85);
    font-size: var(--fs-sm);
    line-height: 1.5;
    text-align: left;
    cursor: pointer;
}

.reminder-optin input[type="checkbox"] {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    accent-color: var(--green);
    cursor: pointer;
}

/* The Pricing tab's interval is a number of months: a short field, not a full-width one. */
.pricing-details__months { max-width: 7rem; }

/* The address on the stop-reminders page can be as long as an address gets. */
.reminders-stop__email { overflow-wrap: anywhere; }
