﻿/* Inter/Nunito are loaded via <link rel="stylesheet"> (+ preconnect) in each host's <head>
   (AppRoot.razor for Web, index.html for MAUI) instead of @import here: @import is only
   discovered once the browser downloads and starts parsing this file, well after the preload
   scanner could've already kicked off the font request from a <link> tag in the initial HTML.
   That extra delay widened the window where text paints in the fallback font before swapping to
   Inter, which was most visible on glyphs (like "@") whose shape differs most between fonts. */

/* Make most text unselectable */
body, h1, h2, h3, h4, h5, h6, p, span, label, button, a {
    user-select: none;
}

/* But keep it selectable where needed */
input, textarea {
    user-select: text;
}

/* Firefox adds invisible inner padding around a <button>'s content via ::-moz-focus-inner that
   isn't part of the button's own box model: it's asymmetric, so it throws off centering on
   small icon buttons (the circular clear-x buttons) even with padding:0 and flex-centering set. */
button::-moz-focus-inner {
    border: 0;
    padding: 0;
}

/* Base styling (mobile-first) */
body {
    font-family: "Inter", system-ui, sans-serif;
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    height: 100dvh;
    overflow: hidden;
}

.main {
    display: grid;
    grid-template-rows: auto 1fr;
    justify-items: center;
    align-items: center;
    width: 100%;
    height: 100dvh;
    background-color: #f0f0f0;
    position: relative;
    z-index: 1;
}

    .main::before {
        content: "";
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-image: url('/images/background.png');
        background-size: cover;
        background-position: center;
        background-repeat: no-repeat;
        filter: blur(10px);
        z-index: -1;
    }

/* For fade-in error transitions */
.field-validation-error {
    color: red;
    font-size: 0.8rem;
    min-height: 1.2em;
    margin-top: -8px;
    opacity: 1;
    transition: opacity 0.3s ease;
    text-align: center;
    margin-bottom: -8px;
    height: 16px;
}

    .field-validation-error.hidden {
        opacity: 0;
    }

.login-link,
.register-link,
.forgot-password-link {
    margin-bottom: 0px;
    color: #007AFF;
    font-size: 0.9rem;
    cursor: pointer;
    text-align: center;
}

.loader {
    border: 3px solid #f3f3f3;
    border-top: 3px solid #007AFF;
    border-radius: 50%;
    width: 20px;
    height: 20px;
    animation: spin 1s linear infinite;
}

.input-error {
    border: 1px solid #e50000 !important;
}

.loading-wrapper {
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 2rem;
    text-align: center;
}

.spinner {
    width: 40px;
    height: 40px;
    border: 4px solid #ddd;
    border-top: 4px solid #007AFF;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
    margin-bottom: 1rem;
}

.loading-message {
    font-size: 1rem;
    font-weight: 500;
    opacity: 0.8;
}

.loading-wrapper--inline {
    height: auto;
}

@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

.input-success {
    outline: 1px solid #26b050 !important;
}

.input-warning {
    outline: 1px solid #f5a623 !important;
}

.input-neutral {
    outline: none !important;
}


/* Default */

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

/* Shared Layout and Utility Styles */

.page-header {
    position: sticky;
    top: 0;
    background: #f5f5f5;
    z-index: 1;
    padding: 0 20px 20px;
    text-align: center;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}

/* Pins an icon/button to the top-right of a .page-header, aligned with .page-title, the same
   way .top-left-nav already pins the back button to the top-left. Component-specific visuals
   (background, color, hover) are layered on top via a second class in the owning component. */
.header-corner-btn {
    position: absolute;
    top: 12px;
    right: 12px;
}

/* Grid-row collapser: animates height from natural size → 0 without layout jumps */
.header-collapser {
    display: grid;
    grid-template-rows: 1fr;
    transition: grid-template-rows 0.32s ease;
}

.header-collapser.collapsed {
    grid-template-rows: 0fr;
}

/* The page-header must have overflow:hidden and min-height:0 so the grid can crush it */
.header-collapser > .page-header {
    overflow: hidden;
    min-height: 0;
    transition: opacity 0.22s ease;
}

.header-collapser.collapsed > .page-header {
    opacity: 0;
    pointer-events: none;
}

.page-title {
    font-size: 2rem;
    font-weight: 600;
    margin-bottom: 20px;
    text-align: center;
    color: #222;
}

/* Same three-slot centering trick as .form-page-title (Login/Register): an SVG on one side of the
   title needs an equal-width invisible spacer on the other side, or the text drifts off the
   header's true center line instead of staying centered. Used by pages that replace an emoji title
   icon (Book, GetBooked) with an SVG: emoji render inconsistently across devices/fonts, this
   doesn't. Plain .page-title usages elsewhere (no icon) are untouched by this. */
.page-title-row {
    display: flex;
    align-items: center;
    width: 100%;
    /* .page-title's own margin is zeroed below (it's a flex child now, not a standalone block), so
       this replaces both that margin-bottom AND the default h1 margin-top the emoji used to get for
       free. Top raised from 10px to 30px; confirmed on iPhone SE (375x667) that 10px still let
       BackHomeButton's icon-only mode (bottom edge ~36px from the header's top) clip the title's
       first letter. Bottom trimmed from 20px to 8px to compensate, since that gap to the search box
       below had more room than it needed. */
    margin: 30px 0 8px;
}

    .page-title-row .page-title {
        flex: 1 1 auto;
        min-width: 0;
        margin: 0;
    }

.page-title-spacer,
.page-title-icon-slot {
    flex: 0 0 auto;
    /* Both slots must stay identical to each other: that's what keeps the title text centered.
       Widened alongside the icon size increase below so the bigger icon isn't cramped. */
    width: 1.5em;
    display: flex;
    align-items: center;
    justify-content: center;
}

.page-title-icon {
    width: 1.3em;
    height: 1.3em;
    flex-shrink: 0;
    /* Geometrically centered against the h1's full line box (confirmed: identical vertical
       midpoint), but that box reserves descender space (for g/j/p/q/y) below the baseline that a
       title without descenders never uses; so the icon reads as sitting low against the visible
       letters. Nudged up to align with the text's cap-height instead of its line-box center. */
    transform: translateY(-3px);
}

.search-box {
    width: 100%;
    max-width: 400px;
    min-width: 200px;
    padding: 12px 16px;
    font-size: 1rem;
    border-radius: 6px;
    border: 1px solid #ccc;
    box-sizing: border-box;
    flex: 1 1 250px;
    max-width: 300px;
}

    .search-box:focus {
        border-color: #2b90d9;
        box-shadow: 0 0 5px rgba(43, 144, 217, 0.5);
        outline: none;
    }

/* Back to CSS Grid (a flex-wrap version of this was tried and rejected; see below), because only
   Grid can keep cards aligned into actual columns when one row has fewer cards than the row above
   it: every row shares the same track definitions, so card N is always in the same column
   regardless of how many cards are in that particular row. flex-wrap can't do this: each wrapped
   line centers independently, so a shorter trailing row just centers under the middle of the row
   above rather than lining up with its columns.
   The column track itself is capped at a fixed max (not 1fr) so cards get real room to grow
   for their title/description but never balloon to fill an ultrawide monitor: auto-fit means a
   window too narrow for even one card at the minimum still gets exactly one (correctly sized) track
   rather than none. justify-content:center on the CONTAINER (not per-card) is what makes the whole
   block of columns center as a single unit once they've hit their max and can't grow further;
   extra width becomes a margin on both sides of the whole group, not gaps between individual cards.
   (The very first version of this used auto-fit/auto-fill with a fixed max per column and
   justify-content:start, which produced a "dead zone" during resize AND dumped all the leftover
   space onto the right edge instead of centering it: looked broken rather than intentional. A
   later attempt used 1fr as the max instead of a fixed cap, which centers/stretches each column
   independently and drifted cards apart from each other on a wide screen with only a couple of
   results; also rejected.)

   minmax's two numbers do different jobs here: since the MAX (440px) is a definite length (not
   1fr/auto), it's what auto-fit uses to decide how many tracks fit at all: a window needs to
   clear roughly N * max + (N-1) * gap before a Nth column appears (900px for a 2nd column at
   440px); while the MIN only matters afterwards, as the floor each already-placed column can
   shrink to if the container isn't an exact multiple of the max. Narrowed the min slightly
   (280 -> 250) so cards have a bit more room to shrink before overflowing on in-between widths;
   the max is intentionally left at 440 so a single card gets real room to grow, matching the
   original design intent from before this file's minmax was retuned and reverted. */
.services-list {
    display: grid;
    justify-content: center;
    width: 100%;
    min-height: 150px; /* Optional: adds vertical space even if empty */
    grid-template-columns: repeat(auto-fit, minmax(250px, 440px));
    gap: 20px;
}

.no-results {
    color: #777;
    font-style: italic;
    margin-top: 20px;
    width: 100%; /* ensures it doesn't collapse */
    /* Only takes effect where the parent is actually a CSS Grid (e.g. .services-list). A plain
       flex/block parent ignores grid-column entirely, so this is safe everywhere .no-results is
       reused. Without it, this would be confined to a single ~280-440px column track instead of
       spanning the full row. */
    grid-column: 1 / -1;
}

.modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 1000;
    animation: modalOverlayIn 0.2s ease both;
}

.modal-content {
    background: #fff;
    padding: 20px;
    border-radius: 16px;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
    width: 90%;
    max-width: 500px;
    max-height: 95vh;
    /* .modal-content is a flex item of .modal-overlay: without min-height:0, some browsers size
       it to fit its own content's intrinsic height first and only apply max-height/overflow-y as
       an afterthought, letting tall content (e.g. BookingModal's full form) visibly spill past both
       the top and bottom of a short viewport instead of being contained and scrolled. */
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    animation: modalContentIn 0.2s ease both;
}

@keyframes modalOverlayIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Animates margin-top/opacity, not transform+scale: confirmed live (2026-07-11, via a standalone
   Playwright A/B repro) that a literal `transform: none` "to" frame held by animation-fill-mode:both
   is NOT safe in practice: the browser still treats .modal-content as the containing block for any
   position:fixed/absolute descendant for as long as this animation is attached, regardless of the
   currently-held keyframe's computed value. This bit AvailabilityEditor's duration-field hint bubble
   (rendered ~200px off from its intended position, which also made .modal-content itself scroll
   both horizontally and vertically to accommodate the misplaced child). The scale "pop" effect is
   dropped since margin can't replicate it; opacity fade + a small vertical settle is the closest
   equivalent. Same underlying anti-pattern as the page-wrapper entrance animations (Home/Book/
   GetBooked/ProviderCalendar/PublicProfile.razor.css) and login-form-container's auth-page-enter. */
@keyframes modalContentIn {
    from { opacity: 0; margin-top: 6px; }
    to   { opacity: 1; margin-top: 0; }
}

/* ── Blazor's own reconnection UI (AppRoot.razor's #components-reconnect-modal) ──
   blazor.web.js toggles these classes directly via DOM mutation, not through Blazor's render
   cycle; so instead of a Razor @if per state, all four state messages already exist in the
   markup and CSS alone decides which one (if any) is visible, matching Microsoft's documented
   customization contract (components-reconnect-show/hide/failed/rejected/retrying/paused). -show
   and -retrying are both treated as "actively reconnecting" since which one actually fires can
   vary by hosting/render mode; -paused falls back to the same text rather than showing nothing. */
#components-reconnect-modal {
    display: none;
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-paused,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: flex;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.4);
    justify-content: center;
    align-items: center;
    z-index: 10001;
    animation: modalOverlayIn 0.2s ease both;
}

.reconnect-card {
    background: var(--card-bg, #fff);
    border: 1px solid var(--card-border, #e8e8e8);
    /* Less top padding than the other three sides: the wordmark sat with too much air above it. */
    padding: 16px 32px 28px;
    border-radius: 16px;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
    text-align: center;
    max-width: 320px;
    animation: modalContentIn 0.2s ease both;
}

/* Same two-tone wordmark as AppBranding.razor's own .bogbee-bog/.bogbee-bee, duplicated here
   (rather than reused) since this markup lives in AppRoot.razor, outside AppBranding's own
   CSS-isolation scope. display:inline-block (not the default inline) so margin-bottom below
   actually takes effect: vertical margins on a plain inline element are ignored for layout. */
.reconnect-brand {
    display: inline-block;
    font-family: "Nunito", sans-serif;
    font-weight: 900;
    font-size: 1.3rem;
    margin-bottom: 10px;
}

.reconnect-brand-bog {
    color: #1a5dc8;
}

.reconnect-brand-bee {
    color: #f59500;
}

[data-theme="dark"] .reconnect-brand-bog {
    color: #5badff;
}

[data-theme="dark"] .reconnect-brand-bee {
    color: #ffb740;
}

.reconnect-spinner {
    width: 32px;
    height: 32px;
    margin: 14px auto;
    border: 4px solid var(--card-border, #ddd);
    border-top: 4px solid #1a5dc8;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

[data-theme="dark"] .reconnect-spinner {
    border-top-color: #ffb740;
}

/* Reconnection failed/rejected: the spinner would be actively misleading (nothing is in
   progress until Reload is pressed), so it's hidden rather than left spinning forever. */
#components-reconnect-modal.components-reconnect-failed .reconnect-spinner,
#components-reconnect-modal.components-reconnect-rejected .reconnect-spinner {
    display: none;
}

.reconnect-state-text {
    display: none;
    margin: 0;
    color: var(--card-text, #444);
    font-size: 0.95rem;
    line-height: 1.4;
}

#components-reconnect-modal.components-reconnect-show .reconnect-state-reconnecting,
#components-reconnect-modal.components-reconnect-retrying .reconnect-state-reconnecting,
#components-reconnect-modal.components-reconnect-paused .reconnect-state-reconnecting {
    display: block;
}

#components-reconnect-modal.components-reconnect-failed .reconnect-state-failed {
    display: block;
}

#components-reconnect-modal.components-reconnect-rejected .reconnect-state-rejected {
    display: block;
}

.reconnect-reload-btn {
    display: none;
    margin-top: 16px;
    padding: 8px 20px;
    border: none;
    border-radius: 20px;
    background: #1a5dc8;
    color: #fff;
    font-weight: 700;
    font-size: 0.85rem;
    cursor: pointer;
    transition: background-color 0.15s ease;
}

[data-theme="dark"] .reconnect-reload-btn {
    background: #ffb740;
    color: #152538;
}

.reconnect-reload-btn:hover {
    filter: brightness(1.1);
}

#components-reconnect-modal.components-reconnect-failed .reconnect-reload-btn,
#components-reconnect-modal.components-reconnect-rejected .reconnect-reload-btn {
    display: inline-block;
}

.booking-fields {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 6px 0;
}

.booking-input {
    width: 100%;
    box-sizing: border-box;
    padding: 7px 10px;
    font-size: 1rem;
    border: 1px solid #ccc;
    border-radius: 6px;
}

textarea.booking-input {
    resize: vertical;
}

.modal-buttons {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

    .modal-buttons button {
        flex-shrink: 0;
        min-width: clamp(50px, 30%, 100px);
        text-align: center;
    }

.book-btn {
    background-color: #2b90d9;
    color: #fff;
    border: none;
    padding: 10px 16px;
    border-radius: 6px;
    transition: background-color 0.25s ease, transform 0.2s ease, box-shadow 0.25s ease;
    cursor: pointer;
    font-weight: 500;
    text-transform: uppercase;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
}

    .book-btn:hover {
        background-color: #1d7dc4;
        box-shadow: 0 0 8px rgba(43, 144, 217, 0.4);
    }

    .book-btn:active {
        transform: translateY(0);
        box-shadow: 0 0 4px rgba(43, 144, 217, 0.3);
    }

.cancel-btn {
    background-color: #eee;
    color: #333;
    /* Transparent border reserves the same 1px at rest that :hover fills with red: swapping
       border:none -> border:1px solid on hover would grow the button by 2px and shift
       neighboring layout, the same resize bug already fixed on the spinner/checkmark buttons. */
    border: 1px solid transparent;
    padding: 9px 15px;
    border-radius: 6px;
    cursor: pointer;
    box-sizing: border-box;
    transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease,
                box-shadow 0.25s ease;
    font-weight: 500;
    text-transform: uppercase;
}

    /* No transform/lift on hover or active: was translateY(-1px)/translateY(0), a 1px vertical
       nudge that, combined with the shadow blooming outward at the same time, read as an odd
       diagonal shift rather than a clean lift. Color/shadow feedback alone is kept. */
    .cancel-btn:hover {
        background-color: #e0e0e0;
        border-color: #ff3b30;
        color: #ff3b30;
        box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
    }

    .cancel-btn:active {
        box-shadow: 0 0 4px rgba(0, 0, 0, 0.1);
    }

.status-message {
    margin-top: 10px;
    font-weight: 500;
}

    .status-message.success {
        color: green;
    }

    .status-message.error {
        color: red;
    }

.autofill-btn {
    align-self: flex-start;
    margin-bottom: 10px;
    padding: 6px 12px;
    font-size: 0.9rem;
    background: #f0f0f0;
    border: 1px solid #ccc;
    border-radius: 6px;
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}

    .autofill-btn:hover {
        background: #e2e2e2;
        border-color: #b8b8b8;
    }

/* Small pill-shaped filter/suggestion button: used for Book's location & service type filter
   chips, and CreateServiceModal's "did you mean this type?" suggestions. Global (not scoped to
   one component's .razor.css) since it's reused across unrelated components. */
.type-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 5px 12px;
    border-radius: 99px;
    border: 1.5px solid var(--card-border, #ddd);
    background: var(--card-bg, #fff);
    color: var(--card-text, #555);
    font-size: 0.78rem;
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s, color 0.15s;
    flex-shrink: 0;
}

    .type-chip svg {
        width: 13px;
        height: 13px;
        flex-shrink: 0;
    }

    .type-chip:hover {
        border-color: #2b90d9;
        color: #2b90d9;
    }

    .type-chip.active {
        border-color: #2b90d9;
        background: #2b90d9;
        color: #fff;
    }

.address-row {
    display: flex;
    width: 100%;
    gap: 10px;
}

.address-postal,
.address-city {
    display: flex;
    flex-direction: column;
    min-width: 0;
    gap: 8px;
}

.address-postal {
    flex: 0 1 35%; /* don’t grow, can shrink, base width = 35% */
}

.address-city {
    flex: 1 1 65%; /* can grow and shrink, base width = 65% */
}

.modal-content h2 {
    margin-top: 0;
    margin-bottom: 0.3rem;
}

.modal-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 1.3rem;
    font-weight: 700;
    color: #333;
}

.modal-plus-icon {
    width: 1.4em;
    height: 1.4em;
    color: #2b90d9;
    flex-shrink: 0;
    transition: transform 0.25s cubic-bezier(.34,1.56,.64,1), filter 0.25s ease-in-out;
}

.modal-content p {
    margin-top: 0;
    margin-bottom: 1rem;
}

.tooltip-container {
    position: relative;
}

/* Validation checklist: inline below field on mobile, speech bubble on desktop */
@media (max-width: 819px) {
    .tooltip-checklist {
        position: static;
        width: auto;
        max-width: none;
        padding: 0.5rem 0.75rem;
        background: #f5f7fa;
        border: 1px solid #d0d5dd;
        border-radius: 6px;
        font-size: 0.82rem;
        /* No margin-top: the field above it already has its own margin-bottom (.form-input,
           0.5rem) for normal field-to-field spacing; adding more here on top of that stacked
           the two, leaving a bigger gap to the field this box actually belongs to than to the
           unrelated field below it (which only gets the next field's own margin-top). Putting the
           extra space on margin-bottom instead keeps it snug against its own field and pushes it
           further from the next one, so proximity reads the right way round. */
        margin-bottom: 0.4rem;
        box-shadow: none;
        opacity: 1;
        pointer-events: auto;
        transform: none;
        transition: none;
    }

    .tooltip-checklist.hide {
        display: none;
    }

    .tooltip-checklist.show {
        display: block;
    }

    [data-theme="dark"] .tooltip-checklist {
        background: #0f1e2e;
        border-color: #2a4060;
        color: #b8cad8;
    }
}

@media (min-width: 820px) {
    .tooltip-checklist {
        /* position:fixed with only `left` (set by JS below, from the target field's own
           getBoundingClientRect); deliberately no `right` here. A fixed box with left, width,
           AND right all non-auto is over-constrained and `left` gets silently dropped per spec,
           which is exactly what pinned every bubble to the same right-edge spot regardless of
           field before this was fixed. */
        position: fixed;
        width: auto;
        max-width: max-content;
        padding: 0.6rem 0.9rem;
        background: #ffffff;
        border: 1px solid #d0d5dd;
        border-radius: 10px;
        font-size: 0.82rem;
        box-shadow: 0 8px 24px rgba(140, 149, 159, 0.2), 0 2px 8px rgba(0, 0, 0, 0.07);
        z-index: 1000;
        opacity: 0;
        pointer-events: none;
        transform: translateY(-50%) translateX(-4px);
        transition: opacity 0.15s ease,
                    transform 0.2s cubic-bezier(0.34, 1.2, 0.64, 1),
                    background-color 0.5s ease, color 0.4s ease,
                    border-color 0.4s ease, box-shadow 0.4s ease;
    }

    .tooltip-checklist.show {
        opacity: 1;
        pointer-events: auto;
        transform: translateY(-50%) translateX(0);
    }

    /* Tail: border layer */
    .tooltip-checklist::before {
        content: '';
        position: absolute;
        right: calc(100% + 1px);
        top: 50%;
        transform: translateY(-50%);
        width: 0;
        height: 0;
        border: 7px solid transparent;
        border-right: 7px solid #d0d5dd;
        border-left-width: 0;
    }

    /* Tail: fill layer */
    .tooltip-checklist::after {
        content: '';
        position: absolute;
        right: 100%;
        top: 50%;
        transform: translateY(-50%);
        width: 0;
        height: 0;
        border: 6px solid transparent;
        border-right: 6px solid #ffffff;
        border-left-width: 0;
    }
}

.tooltip-checklist p.check {
    color: #1a7f37;
    margin: 0.2rem 0;
    display: flex;
    align-items: flex-start;
    gap: 0.35rem;
}

.tooltip-checklist p.cross {
    color: #cf222e;
    margin: 0.2rem 0;
    display: flex;
    align-items: flex-start;
    gap: 0.35rem;
}

.tooltip-checklist .icon {
    flex-shrink: 0;
}

.login-form-container {
    flex-shrink: 1; /* allow shrinking */
    min-height: 0; /* required for flex children to shrink properly */
    max-height: 100%; /* contain inside the page */
    overflow: hidden;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 1.5rem 0 1.5rem 1rem;
    background-color: #ffffff;
    box-sizing: border-box;
    margin: auto;
    border-radius: 1rem;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15), 0 2px 6px rgba(0, 0, 0, 0.1);
    width: 100%;
    max-width: clamp(320px, 90vw, 500px);
    position: relative;
    outline: 1px solid rgba(255, 255, 255, 0.4);
    animation: auth-page-enter 0.32s cubic-bezier(0.4, 0, 0.2, 1) both;
}

/* Terms/Privacy: a multi-section legal document reads better wider than the narrow auth-form
   card this class was designed for: 500px meant a lot of internal scrolling for not much text
   per line. Widened here rather than raising the shared max-width above, which stays narrow on
   purpose for Login/Register/ForgotPassword.
   Unlike Login/Register/ForgotPassword, there's no .login-page/.register-page-style wrapper
   forcing 100dvh here, so .main's grid (grid-template-rows: auto 1fr) only auto-sizes this card's
   row to its own content height, leaving the rest of the viewport as dead space below it. Making
   .main's own grid a single full-height row (only on pages with .legal-container) fixes that
   without disturbing the existing horizontal centering, which already worked fine.
   (A long detour chasing a "mystery 85%-of-viewport cap" here turned out to be Terms.razor.css /
   Privacy.razor.css: Blazor CSS isolation scopes their `.legal-container { max-width: 680px;
   max-height: 85vh; }` with an auto-appended [b-xxxx] attribute selector, which out-specifies this
   plain class rule regardless of source order. Removed from both scoped files; this rule is now
   the only place those two properties are set.)
   z-index here too: PublicLayout's legal-links/powered-by footer (position:fixed, z-index:9999)
   stays mounted across route changes (it's part of the layout, not the page) and remained
   clickable through the Terms/Privacy card. Giving .legal-container its own high z-index alone
   did NOT fix this (confirmed live); .main itself has z-index:auto, so it never establishes a
   stacking context of its own, meaning nothing inside it can out-rank .main's sibling .legal-links
   at the body level no matter how high its z-index goes. .main needs the explicit z-index, scoped
   to only this page via :has() so Login/Register/ForgotPassword keep their footer usable as
   normal. */
.main:has(.legal-container) {
    grid-template-rows: 1fr;
    position: relative;
    z-index: 10000;
}

.legal-container {
    /* Wide, but not edge-to-edge: a small margin plus the card's own border-radius (inherited
       from .login-form-container) keeps a visible sliver of the blurred background around it, so
       it still reads as a card sitting over the app rather than a full-bleed page. */
    max-width: clamp(320px, 95vw, 1400px);
    height: 100%;
    max-height: 100%;
    align-self: stretch;
    /* The base rule's 1.5rem padding-top was on top of the close button's own row (see
       CloseButton.razor.css), making the header noticeably taller than the untouched 1.5rem
       padding-bottom below the scrolled content. Trimmed here so the button's own (now small)
       margin does most of the work instead of stacking on top of the full padding too. */
    padding-top: 0.5rem;
}

/* ── Shared enter/leave transition for Login / Register / ForgotPassword ──
   Animates margin-top, not transform: confirmed live (2026-07-11) that even a `transform: none`
   "to" keyframe held by animation-fill-mode:both still makes the browser treat this element as
   the containing block for any position:fixed descendant (here: TooltipValidationChecklist's
   speech-bubble), instead of the true viewport. The "ends on `none` so it's safe" reasoning this
   comment used to make is wrong in practice; same underlying anti-pattern as the transform-based
   page animations already fixed elsewhere (Book/GetBooked/ProviderCalendar/PublicProfile/Home),
   just proven wrong here specifically instead of assumed safe. Scale is dropped since margin can't
   replicate it; opacity + a small vertical slide is the closest equivalent. */
@keyframes auth-page-enter {
    from { opacity: 0; margin-top: 14px; }
    to   { opacity: 1; margin-top: 0; }
}

@keyframes auth-page-exit {
    from { opacity: 1; margin-top: 0; }
    to   { opacity: 0; margin-top: -18px; }
}

.auth-leaving {
    animation: auth-page-exit 0.32s cubic-bezier(0.4, 0, 0.2, 1) forwards;
    pointer-events: none;
}

.form-page-title {
    display: flex;
    /* flex-start, not center: when the title text wraps to a second line (confirmed live; Danish
       "Glemt adgangskode" on ForgotPassword wraps at its smaller font-size on a narrow card, while
       Login's "Log ind" and Register's "Registrér" stay on one line), centering would center the
       icon-slot against the whole multi-line block instead of pinning it to the row's top, making
       it drift away from the flush top-right corner Login/Register have. Identical result for a
       single-line title either way, so this is a no-op for the two pages that already worked. */
    align-items: flex-start;
    width: 100%;
    font-size: clamp(2rem, 4vh, 3rem);
    font-weight: bold;
    margin: 0 0 1.5rem 0;
}

/* Icon sits in a fixed-width slot on the left, mirrored by an empty slot of the same
   width on the right (.form-page-title-spacer); so the title text's own flex:1 box
   stays centered on the card's true center line instead of drifting toward the icon. */
.form-page-title-icon-slot,
.form-page-title-spacer {
    flex: 0 0 auto;
    width: 1.5em;
    display: flex;
    align-items: center;
    justify-content: center;
}

.form-page-title-icon {
    width: 1.25em;
    height: 1.25em;
    color: #007AFF;
    flex-shrink: 0;
}

[data-theme="dark"] .form-page-title-icon {
    color: #4da3ff;
}

.form-page-title-text {
    flex: 1 1 auto;
    min-width: 0;
    text-align: center;
}

.form-scroll-wrapper {
    flex: 1;
    min-height: 0; /* crucial: prevent content from forcing height */
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    overflow-y: auto;
    /* The card's own right padding was removed (see .login-form-container) and moved here instead;
       this is the scrolling element, so its scrollbar now renders at its own edge (flush with
       the card) rather than being doubly inset by a parent's padding on top of this box's own. */
    padding-right: 1rem;
    box-sizing: border-box;
}

    .form-scroll-wrapper form {
        max-width: 100%;
        display: flex;
        flex-direction: column;
        align-items: center;
        width: 70%;
    }

@media (max-width: 819px) {
    .form-scroll-wrapper form {
        width: 88%;
    }
}

.form-input {
    width: 100%;
    font-size: 1rem;
    padding: 0.75rem 1rem;
    border-radius: 0.5rem;
    box-sizing: border-box;
    background-color: #fff;
    border: 1px solid #ccc;
    margin: 0.5rem 0;
    height: 50px;
    /* Inter appears to re-shape/re-kern a run once more context follows it (e.g. typing "1@"
       then another char visibly nudges "@" down); disabling kerning/ligatures stops the browser
       from re-evaluating glyph position as the run grows. */
    font-kerning: none;
    font-variant-ligatures: none;
}

    .form-input:placeholder-shown::placeholder {
        opacity: 1;
        transition: opacity 0.2s ease;
    }

    .form-input:not(:placeholder-shown)::placeholder {
        opacity: 0;
    }

/* wrapper */
.form-button-wrap {
    position: relative;
    display: inline-block;
    margin: 20px;
}
    .form-button-wrap::before {
        content: "";
        position: absolute;
        left: -1px;
        right: -1px; /* slightly wider than button */
        top: -1px;
        height: 6px;
        border-radius: 10px;
        background: rgba(0, 0, 0, 0.35);
        opacity: 0.35;
        filter: blur(3px);
        pointer-events: none;
    }

    /* base plate under the button */
    .form-button-wrap::after {
        content: "";
        position: absolute;
        left: -4px;
        right: -4px; /* use right instead of width for clean rounding */
        top: 6px; /* ↓ start lower so it doesn't show at the top corners */
        bottom: -14px; /* extends below */
        background: #273F3A; /* base plate color */
        border-radius: 9px; /* slightly larger than the button */
        z-index: 0;
    }

    .form-button-wrap:has(.form-button:active)::before,
    .form-button-wrap:has(.form-button.loading)::before {
        opacity: 0;
    }

    /* Latched state = pressed visuals */
    .form-button-wrap.press-latched .form-button {
        top: 9px;
        background: #006fe6;
        box-shadow: inset 0 1px 0 #007AFF, inset 0 -3px 0 #005FCC;
    }

    /* Latched state = no top cast shadow */
    .form-button-wrap.press-latched::before {
        opacity: 0;
    }

/* button */
.form-button {
    position: relative;
    z-index: 1; /* above the plate */
    color: #fff;
    font-family: "Inter", system-ui, sans-serif;
    font-weight: 700;
    font-size: 1rem;
    text-align: center;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 20px 40px;
    background: #007AFF;
    line-height: 1;
    border: none;
    border-radius: 7px; /* button radius */
    overflow: hidden; /* ← ensures the shadow/ridge curves with the corners */

    text-shadow: 0 1px 0 #000;
    /* top highlight + rounded ledge that follows the button's radius */
    box-shadow: inset 0 1px 0 #C9E2FF, 0 10px 0 #005FCC;
    cursor: pointer;
}

    .form-button:hover {
        background: #1a8cff; /* a bit lighter than #007AFF */
        box-shadow: inset 0 1px 0 #D6E9FF, 0 10px 0 #0066D1;
        transition: background 0.2s ease, box-shadow 0.2s ease;
    }

    /* pressed */
    .form-button:active {
        top: 9px; /* lands on the 10px ledge */
        background: #006fe6;
        box-shadow: inset 0 1px 0 #007AFF, inset 0 -3px 0 #005FCC; /* pressed ridge – also curves because of overflow:hidden */
    }

    .form-button .button-content {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: max-content;
        height: 16px;
    }

    .form-button .button-label.is-hidden {
        visibility: hidden;
    }

    .form-button .loader {
        position: absolute;
    }

    .form-button.loading {
        background: #006fe6;
        box-shadow: inset 0 1px 0 #007AFF, inset 0 -3px 0 #005FCC;
        top: 9px;
        cursor: wait;
        pointer-events: none;
    }

        .form-button.loading:disabled {
            opacity: 1;
        }

    .form-button.loading,
    .form-button:active.loading {
        top: 9px; /* stay pressed */
        transform: none;
    }

    .form-button.loading {
        transition: none !important;
    }

.input-wrapper {
    width: 100%;
    box-sizing: border-box;
}

.text-center {
    text-align: center;
}

.app-motto {
    font-size: 1rem;
    color: #777;
    margin-top: -10px;
    margin-bottom: 20px;
    font-style: italic;
    text-align: center;
}

.login-form-wrapper {
    z-index: 1;
}

.login-page {
    display: flex;
    flex-direction: column;
    height: 100dvh;
    width: 100%;
    min-height: 0; /* ← important when working with flex children */
    overflow: hidden;
}

.register-page {
    display: flex;
    flex-direction: column;
    height: 100dvh;
    width: 100%;
    justify-content: center;
    min-height: 0; /* ← important when working with flex children, matches .login-page/.fp-page */
    overflow: hidden;
}

.app-branding,
.spacer {
    flex: 1; /* each takes equal available space */
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: clamp(0.75rem, 2vh, 2rem);
}

/* legal-links/powered-by are fixed at bottom:12px and aren't part of this flex layout at all, so
   on a page whose form is tall enough to squeeze everything else to its floor (Register), the
   plain min-height clamp above could shrink well below what those fixed elements actually need.
   Unconditional rather than gated behind a width/height media query: that used to only fire
   below max-height:750px, so it never applied on a narrow-but-tall phone like the Galaxy S25
   (360x780), where the card still ends up tall enough to squeeze the spacer down and get covered
   by the footer despite the viewport clearing that threshold. 44px is a fixed amount driven by the
   footer's own size, not a device-size guess, so there's no reason to gate it at all; harmless
   where there's already slack (Applies to all three auth pages), load-bearing where there isn't. */
.spacer {
    min-height: 44px;
}

/* Short phones (e.g. iPhone SE, ~667px tall): an even 1:1 split between the branding block and
   the bottom spacer leaves almost no headroom above the logo, so the fixed-position
   DarkModeToggle (top: 0.6rem) sits right on top of it. Clearance comes from padding-top (scaled
   with vh) rather than from inflating the flex share further: that keeps .app-branding's own box
   modest so centering its content doesn't reopen a big gap to the card below; padding-top eats
   into the centering area from the top only, nudging the logo down just enough to clear the
   toggle. Gated to max-height as well as max-width: taller phones (e.g. iPhone 17, ~874px) already
   have plenty of headroom with the plain 1:1 split further down, and layering this skew on top of
   that would just push the logo off-center for no reason.
   Applies to all three auth pages (Login/Register/ForgotPassword): tried scoping this away from
   ForgotPassword on the theory its shorter card would already leave enough natural clearance at a
   plain 1:1 split, but confirmed live that it still overlaps the toggle the same as the others;
   so it needs the same treatment, not a lighter one. */
@media (max-width: 819px) and (max-height: 750px) {
    .login-page .app-branding,
    .register-page .app-branding,
    .fp-page .app-branding {
        flex: 1.3;
        align-items: center;
        padding-top: clamp(12px, 4vh, 40px);
        /* The 1.3 share is a fraction of whatever's left over after the card, so a page with a
           shorter card (ForgotPassword's single-field first step, ~82px shorter than Login's form
           at SE) leaves more leftover; confirmed live: that inflated ForgotPassword's branding
           box to 215px vs Login's 161.6px at 375x667, even though both use the identical ratio.
           Capping it just above Login's own natural size (161.6px ≈ 24.2vh here) means Login/
           Register never touch the cap (no visible change), while ForgotPassword's excess growth
           is clamped and the freed space falls through to .spacer instead: matching Login's gap
           rather than ballooning past it just because its form is shorter. box-sizing:border-box
           so the cap covers the padding-top too: otherwise max-height only bounds the content
           box and padding-top (added on top under the default content-box model) pushes the
           actual rendered total past the intended cap anyway. */
        max-height: 25vh;
        box-sizing: border-box;
    }

    .login-page .spacer,
    .register-page .spacer,
    .fp-page .spacer {
        flex: 0.7;
    }
}

/* Register's own DarkModeToggle sits fixed top-right and only causes trouble once the card
   actually grows tall enough to reach up into that corner (worth having whenever there's room;
   Login/Home keep theirs unconditionally). Used to be guessed from a static width+height media
   query, but that broke on narrow-but-tall phones (e.g. Galaxy S25, 360x780): the query only
   fired below max-height:750px, yet a narrower width makes this form wrap/stack taller regardless
   of viewport height, so the card could still collide with the toggle above that threshold.
   registerToggle.js measures the real rendered overlap between the toggle and
   .login-form-container on load/resize instead, so it's correct for any device size rather than
   one more hand-tuned breakpoint.
   Hidden-by-default here (opacity, not display; the fixed-position box still needs a real
   rect for the JS to measure) rather than shown-by-default-then-hidden: the measurement can only
   run after the card has its first real layout, so starting visible made the toggle flash on
   screen for a frame on every load/navigation before JS had a chance to hide it on a colliding
   viewport. Same "hidden until proven safe" pattern as AppBranding's .branding-inner.hidden. */
.register-toggle-wrap {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
}

.register-toggle-wrap.toggle-fits {
    opacity: 1;
    pointer-events: auto;
}

/* Without this, a toggle that never fit (still opacity:0, no .toggle-fits) would still flash to
   opacity:1 for a frame when navigating away: .auth-leaving's shared keyframe animation starts
   from its own hardcoded `from { opacity: 1 }`, regardless of the element's actual current
   opacity. Only relevant to the never-shown case; a toggle that does have .toggle-fits keeps the
   normal fade-out. */
.register-toggle-wrap:not(.toggle-fits).auth-leaving {
    animation: none;
    opacity: 0;
}

/* Success screens are a centered stack, not the 3-region login/register/forgot-password
   layout, so the branding block shouldn't claim an equal flex share (which would blow it up
   to fill most of the viewport). It still needs align-self: stretch though; the success
   page centers its children by shrink-wrapping them to content width, but appBranding.js
   sizes the logo/title against *this* element's own width, so without stretch it collapses
   to near-zero and the script gives up and hides it entirely. */
.fp-success-page .app-branding,
.reg-success-page .app-branding {
    flex: 0 0 auto;
    align-self: stretch;
    min-height: 0;
}

.layout-header,
.layout-spacer {
    min-height: clamp(0.75rem, 2vh, 2rem);
}

.layout-content {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 100%;
}

.search-container {
    position: relative;
    width: 80%;
    max-width: 500px;
    margin: 0 auto;
}

.field-row {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
}

.half-width {
    flex: 1 1 50%;
    display: flex;
    flex-direction: column;
}

/* Ensure red border stays consistent on focus */
.input-error:focus {
    outline: none;
    border: 1px solid #e50000 !important;
    box-shadow: 0 0 0 2px rgba(229, 0, 0, 0.2); /* optional: subtle red glow */
}

/* Optional: Remove native blue or orange autofill glow */
input:-webkit-autofill {
    box-shadow: 0 0 0px 1000px white inset;
    -webkit-box-shadow: 0 0 0px 1000px white inset;
    -webkit-text-fill-color: #000;
}

.input-warning:focus {
    outline: none;
    border: 1px solid #f5a623;
    box-shadow: 0 0 0 2px rgba(245, 166, 35, 0.2);
}

/* If you're using input-success */
.input-success:focus {
    outline: none;
    border: 1px solid #26b050;
    box-shadow: 0 0 0 2px rgba(38, 176, 80, 0.2);
}

.top-left-nav {
    position: absolute;
    top: 0px;
    left: 0px;
    /* Both this and sibling cards like .login-form-container are position:relative/absolute with
       z-index:auto, so without this they paint in DOM order: the card (rendered after this in
       markup, e.g. Terms/Privacy) wins everywhere except the sliver its own border-radius doesn't
       cover, leaving the button clickable only in that corner cutout. */
    z-index: 5;
    background-color: #2b90d9;
    transition: background-color 0.25s ease;
    border-radius: 0 0 8px 0;
}

    /* Keeps this backing color in sync with .back-btn:hover so the rounded
       corner doesn't show a mismatched shade where the two curves don't
       perfectly overlap. */
    .top-left-nav:has(.back-btn:hover) {
        background-color: #1d7dc4;
    }

/* ─────────────────────────────────────────────
   THEME: CSS Custom Properties
   All theming is done through these variables.
   Scoped Blazor components inherit them via the
   normal DOM cascade, so they work transparently.
   ───────────────────────────────────────────── */

:root {
    /* Dark-mode toggle button */
    --toggle-bg:     rgba(255, 255, 255, 0.85);
    --toggle-color:  #555;
    --toggle-shadow: 0 2px 12px rgba(0, 0, 0, 0.15), 0 1px 4px rgba(0, 0, 0, 0.1);

    /* Service cards */
    --card-bg:             #ffffff;
    --card-border:         #ddd;
    --card-h3:             #333;
    --card-text:           #555;
    --card-hint:           #999;
    --card-deleted-bg:     #f9f9f9;
    --card-deleted-border: #e0e0e0;
    --card-deleted-h3:     #888;

    /* Admin analytics charts: fixed categorical order (never cycled/reassigned), validated with
       the dataviz skill's validate_palette.js against this app's own --card-bg surfaces (light
       #ffffff, dark #152538): CVD-safe, contrast-passing. Series 2/3/7 (aqua/yellow/magenta) sit
       below 3:1 text contrast on light by design: charts must never put text IN these colors
       (mark specs already require labels/legends to stay in ink tokens, never the series color). */
    --viz-series-1: #2a78d6; /* blue */
    --viz-series-2: #1baf7a; /* aqua */
    --viz-series-3: #eda100; /* yellow */
    --viz-series-4: #008300; /* green */
    --viz-series-5: #4a3aa7; /* violet */
    --viz-series-6: #e34948; /* red */
    --viz-series-7: #e87ba4; /* magenta */
    --viz-series-8: #eb6834; /* orange */
    --viz-gridline: #e1e0d9;
    --viz-baseline: #c3c2b7;
    --viz-good:     #0ca30c;
    --viz-warning:  #fab219;
    --viz-serious:  #ec835a;
    --viz-critical: #d03b3b;
    --card-deleted-text:   #aaa;

    /* Location badges */
    --location-badge-bg:      rgba(43, 144, 217, 0.1);
    --location-badge-text:    #2b90d9;
    --location-badge-border:  rgba(43, 144, 217, 0.25);
    --location-mobile-bg:     rgba(56, 161, 105, 0.1);
    --location-mobile-text:   #2e7d32;
    --location-mobile-border: rgba(56, 161, 105, 0.25);

    /* GetBooked page */
    --provider-page-bg:     #f5f5f5;
    --provider-page-shadow: rgba(0, 0, 0, 0.05);
    --add-btn-bg:           #fff;
    --add-btn-border:       #ddd;
    --add-btn-text:         #333;
    --bin-btn-bg:           #fff;
    --bin-btn-border:       #ddd;
    --bin-btn-color:        #999;

    /* Confirm modal */
    --confirm-modal-bg:           #fff;
    --confirm-modal-header-bg:    #f8f9fa;
    --confirm-modal-header-border:#eee;
    --confirm-modal-h3:           #222;
    --confirm-modal-body-text:    #444;
    --confirm-secondary-bg:       #e0e0e0;
    --confirm-secondary-text:     #222;
    --confirm-secondary-hover:    #d5d5d5;

    /* Restore mode */
    --restore-page-bg:      #fff8f0;
    --restore-page-shadow:  rgba(230, 120, 0, 0.12);
    --restore-banner-bg:    #fff3cd;
    --restore-banner-border:#ffe08a;
    --restore-banner-text:  #7a4f00;
    --exit-restore-border:  #c9950a;
    --exit-restore-text:    #7a4f00;
    --exit-restore-hover:   #ffe8a1;
    --bin-active-bg:        #fff3cd;
    --bin-active-border:    #c9950a;
    --bin-active-color:     #7a4f00;
    --bin-active-hover:     #ffe8a1;

    /* Reorder mode */
    --reorder-page-bg:       #eef5fe;
    --reorder-page-shadow:   rgba(43, 144, 217, 0.12);
    --reorder-banner-bg:     #dbeafe;
    --reorder-banner-border: #93c5fd;
    --reorder-banner-text:   #1e4e8c;
    --exit-reorder-border:   #2b90d9;
    --exit-reorder-text:     #1e4e8c;
    --exit-reorder-hover:    #bfdbfe;
    --reorder-active-bg:     #dbeafe;
    --reorder-active-border: #2b90d9;
    --reorder-active-color:  #1e4e8c;
    --reorder-active-hover:  #bfdbfe;
    --reorder-placeholder-bg:rgba(43, 144, 217, 0.08);

    /* Bookings modal: list rows */
    --sbm-row-bg:           #f8f9fa;
    --sbm-row-border:       #eee;
    --sbm-row-hover-bg:     #edf5fc;
    --sbm-day-color:        #999;
    --sbm-time-color:       #333;
    --sbm-phone-color:      #888;
    --sbm-chevron-color:    #bbb;
    --sbm-count-color:      #888;
    --sbm-toggle-border:    #ddd;
    --sbm-toggle-btn-bg:    #fff;
    --sbm-toggle-btn-color: #666;
    --sbm-back-btn-border:  #ddd;
    --sbm-back-btn-color:   #666;
    --sbm-back-btn-hover:   #f0f0f0;
    --sbm-detail-meta:      #555;
    --sbm-customer-bg:      #f8f9fa;
    --sbm-field-label:      #999;
    --sbm-field-value:      #222;
    --sbm-label-upper:      #6b7280;
    --sbm-empty-color:      #aaa;
    --sbm-loading-color:    #888;
    --sbm-spinner-border:   #ddd;

    /* Bookings modal: calendar */
    --cal-bg:               #fff;
    --cal-border:           #e5e7eb;
    --cal-month-color:      #1f2937;
    --cal-nav-btn-bg:       #f3f4f6;
    --cal-nav-btn-color:    #6b7280;
    --cal-nav-btn-hover-bg: #e5e7eb;
    --cal-nav-btn-hover-color: #111827;
    --cal-header-color:     #9ca3af;
    --cal-header-border:    #f0f0f0;
    --cal-cell-hover-bg:    #f3f4f6;
    --cal-selected-bg:      #eff6ff;
    --cal-selected-hover:   #dbeafe;
    --cal-day-num-color:    #374151;
    --cal-other-month:      #d1d5db;
    --cal-other-hover:      #fafafa;
    --cal-weekend-color:    #6b7280;
    --cal-divider:          #e5e7eb;
    --cal-placeholder:      #d1d5db;
    --cal-panel-title:      #374151;

    /* Status chips */
    --status-pending-bg:    #fff3cd;   --status-pending-text:    #856404;
    --status-confirmed-bg:  #d1edda;   --status-confirmed-text:  #155724;
    --status-cancelled-bg:  #f8d7da;   --status-cancelled-text:  #721c24;
    --status-completed-bg:  #e2e3e5;   --status-completed-text:  #383d41;
    --status-default-bg:    #d1ecf1;   --status-default-text:    #0c5460;
    --status-invoiced-bg:   #ebf8ff;   --status-invoiced-text:   #2b6cb0;
    --status-paid-bg:       #faf5ff;   --status-paid-text:       #553c9a;
    --status-disputed-bg:   #fff5f5;   --status-disputed-text:   #c53030;
}

[data-theme="dark"] {
    /* Dark-mode toggle button */
    --toggle-bg:     rgba(21, 37, 56, 0.90);
    --toggle-color:  #f0c040;
    --toggle-shadow: 0 2px 12px rgba(0, 0, 0, 0.5), 0 0 20px rgba(240, 192, 64, 0.15);

    /* Service cards */
    --card-bg:             #152538;
    --card-border:         #2a4060;
    --card-h3:             #e2e8f0;
    --card-text:           #94a3b8;
    --card-hint:           #5a7a94;
    --card-deleted-bg:     #0f1e2e;
    --card-deleted-border: #1e3050;
    --card-deleted-h3:     #3e5a72;

    /* Admin analytics charts (dark): same 8 slots, dark-band steps, validated against --card-bg
       #152538. Worst adjacent CVD separation (green/yellow) sits in the 8-12 floor band, legal
       only with the secondary encoding (direct labels/legend) the mark specs already require. */
    --viz-series-1: #3987e5;
    --viz-series-2: #199e70;
    --viz-series-3: #c98500;
    --viz-series-4: #008300;
    --viz-series-5: #9085e9;
    --viz-series-6: #e66767;
    --viz-series-7: #d55181;
    --viz-series-8: #d95926;
    --viz-gridline: #2c2c2a;
    --viz-baseline: #383835;
    --viz-good:     #0ca30c;
    --viz-warning:  #fab219;
    --viz-serious:  #ec835a;
    --viz-critical: #d03b3b;

    /* Form inputs */
    --input-bg:            #162030;
    --card-deleted-text:   #2a4060;

    /* Location badges (dark mode) */
    --location-badge-bg:      rgba(43, 144, 217, 0.15);
    --location-badge-text:    #60b8f0;
    --location-badge-border:  rgba(43, 144, 217, 0.3);
    --location-mobile-bg:     rgba(56, 161, 105, 0.15);
    --location-mobile-text:   #5dba7a;
    --location-mobile-border: rgba(56, 161, 105, 0.3);

    /* GetBooked page */
    --provider-page-bg:     #0d1b2a;
    --provider-page-shadow: rgba(0, 0, 0, 0.35);
    --add-btn-bg:           #162030;
    --add-btn-border:       #2a4060;
    --add-btn-text:         #c8d8e8;
    --bin-btn-bg:           #162030;
    --bin-btn-border:       #2a4060;
    --bin-btn-color:        #8fa5c0;

    /* Confirm modal */
    --confirm-modal-bg:           #152538;
    --confirm-modal-header-bg:    #0f2030;
    --confirm-modal-header-border:#1e3050;
    --confirm-modal-h3:           #e2e8f0;
    --confirm-modal-body-text:    #b8cad8;
    --confirm-secondary-bg:       #1e3050;
    --confirm-secondary-text:     #c8d8e8;
    --confirm-secondary-hover:    #2a4060;

    /* Restore mode: dark amber tones */
    --restore-page-bg:      #1a1200;
    --restore-page-shadow:  rgba(230, 150, 0, 0.22);
    --restore-banner-bg:    #1e1500;
    --restore-banner-border:#4a3000;
    --restore-banner-text:  #c89020;
    --exit-restore-border:  #7a5800;
    --exit-restore-text:    #c89020;
    --exit-restore-hover:   #2a1c00;
    --bin-active-bg:        #1e1500;
    --bin-active-border:    #7a5800;
    --bin-active-color:     #c89020;
    --bin-active-hover:     #2a1c00;

    /* Reorder mode: dark blue tones */
    --reorder-page-bg:       #0a2035;
    --reorder-page-shadow:   rgba(43, 144, 217, 0.25);
    --reorder-banner-bg:     #0f2540;
    --reorder-banner-border: #2a4a70;
    --reorder-banner-text:   #60b8f0;
    --exit-reorder-border:   #1e5090;
    --exit-reorder-text:     #60b8f0;
    --exit-reorder-hover:    #16324f;
    --reorder-active-bg:     #0f2540;
    --reorder-active-border: #1e5090;
    --reorder-active-color:  #60b8f0;
    --reorder-active-hover:  #16324f;
    --reorder-placeholder-bg:rgba(43, 144, 217, 0.18);

    /* Bookings modal: list rows */
    --sbm-row-bg:           #162030;
    --sbm-row-border:       #1e3050;
    --sbm-row-hover-bg:     #1a3050;
    --sbm-day-color:        #5a7a94;
    --sbm-time-color:       #e2e8f0;
    --sbm-phone-color:      #5a7a94;
    --sbm-chevron-color:    #2a4060;
    --sbm-count-color:      #5a7a94;
    --sbm-toggle-border:    #2a4060;
    --sbm-toggle-btn-bg:    #162030;
    --sbm-toggle-btn-color: #8fa5c0;
    --sbm-back-btn-border:  #2a4060;
    --sbm-back-btn-color:   #8fa5c0;
    --sbm-back-btn-hover:   #1e3050;
    --sbm-detail-meta:      #94a3b8;
    --sbm-customer-bg:      #162030;
    --sbm-field-label:      #5a7a94;
    --sbm-field-value:      #e2e8f0;
    --sbm-label-upper:      #5a7a94;
    --sbm-empty-color:      #3a5070;
    --sbm-loading-color:    #5a7a94;
    --sbm-spinner-border:   #2a4060;

    /* Bookings modal: calendar */
    --cal-bg:               #152538;
    --cal-border:           #2a4060;
    --cal-month-color:      #e2e8f0;
    --cal-nav-btn-bg:       #1a2d40;
    --cal-nav-btn-color:    #8fa5c0;
    --cal-nav-btn-hover-bg: #2a4060;
    --cal-nav-btn-hover-color: #e2e8f0;
    --cal-header-color:     #3a5070;
    --cal-header-border:    #1e3050;
    --cal-cell-hover-bg:    #1a3050;
    --cal-selected-bg:      #0d2d50;
    --cal-selected-hover:   #1a3d60;
    --cal-day-num-color:    #c8d8e8;
    --cal-other-month:      #1e3050;
    --cal-other-hover:      #0f1e2e;
    --cal-weekend-color:    #6b8fa8;
    --cal-divider:          #1e3050;
    --cal-placeholder:      #2a4060;
    --cal-panel-title:      #b8cad8;

    /* Status chips: dark but still colour-coded */
    --status-pending-bg:    #3a2800;   --status-pending-text:    #c89020;
    --status-confirmed-bg:  #0a2a14;   --status-confirmed-text:  #4aba6a;
    --status-cancelled-bg:  #2a0a0e;   --status-cancelled-text:  #e87070;
    --status-completed-bg:  #1e2430;   --status-completed-text:  #8fa5c0;
    --status-default-bg:    #0a2030;   --status-default-text:    #4ab8d0;
    --status-invoiced-bg:   #0a2030;   --status-invoiced-text:   #5b9bd5;
    --status-paid-bg:       #241a38;   --status-paid-text:       #a480d8;
    --status-disputed-bg:   #2a0a0e;   --status-disputed-text:   #e87070;
}

/* ─────────────────────────────────────────────
   TRANSITIONS: smooth day↔night switch
   ───────────────────────────────────────────── */

.main {
    transition: background-color 0.6s ease;
}

/* The ::before pseudo holds the light background image */
.main::before {
    transition: opacity 0.8s ease;
}

/* The ::after pseudo holds the dark background image.
   It starts invisible and fades in when dark mode activates. */
.main::after {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-image: url('/images/background-dark.png');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    filter: blur(10px);
    z-index: -1;
    opacity: 0;
    transition: opacity 0.8s ease;
    pointer-events: none;
}

.page-header,
.search-box,
.modal-content,
.booking-input,
.cancel-btn,
.autofill-btn,
.login-form-container,
.form-input {
    transition: background-color 0.5s ease, color 0.4s ease,
                border-color 0.4s ease, box-shadow 0.4s ease;
}

/* ─────────────────────────────────────────────
   DARK MODE OVERRIDES: global (non-scoped) elements
   ───────────────────────────────────────────── */

[data-theme="dark"] .main {
    background-color: #0d1b2a;
}

[data-theme="dark"] .main::before {
    opacity: 0;
}

[data-theme="dark"] .main::after {
    opacity: 1;
}

/* Page structure */
[data-theme="dark"] .page-header {
    background: #0f1e2e;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}

[data-theme="dark"] .page-title {
    color: #e2e8f0;
}

/* Search */
[data-theme="dark"] .search-box {
    background: #162030;
    border-color: #2a4060;
    color: #e2e8f0;
}

[data-theme="dark"] .search-box::placeholder {
    color: #5a7a94;
}

[data-theme="dark"] .search-box:focus {
    border-color: #4da3ff;
    box-shadow: 0 0 5px rgba(77, 163, 255, 0.4);
}

/* No results text */
[data-theme="dark"] .no-results {
    color: #5a7a94;
}

/* Modals */
[data-theme="dark"] .modal-content {
    background: #152538;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
    color: #e2e8f0;
}

[data-theme="dark"] .modal-title {
    color: #e2e8f0;
}

[data-theme="dark"] .modal-content h2,
[data-theme="dark"] .modal-content h3,
[data-theme="dark"] .modal-content p,
[data-theme="dark"] .modal-content label {
    color: #d0dce8;
}

[data-theme="dark"] .modal-content select,
[data-theme="dark"] .booking-input {
    background: #162030;
    border-color: #2a4060;
    color: #e2e8f0;
}

/* Tooltip checklist */
@media (min-width: 820px) {
    [data-theme="dark"] .tooltip-checklist {
        background: #0f1e2e;
        border-color: #2a4060;
        color: #b8cad8;
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
    }

    [data-theme="dark"] .tooltip-checklist::before {
        border-right-color: #2a4060;
    }

    [data-theme="dark"] .tooltip-checklist::after {
        border-right-color: #0f1e2e;
    }
}

/* Slightly brighter than the light-mode green/red: against the dark bubble background
   (#0f1e2e) the light-mode shades read as muddy and low-contrast. */
[data-theme="dark"] .tooltip-checklist p.check {
    color: #3fb950;
}

[data-theme="dark"] .tooltip-checklist p.cross {
    color: #f0625c;
}

/* Buttons */
[data-theme="dark"] .cancel-btn {
    background-color: #1e3050;
    color: #c8d8e8;
}

[data-theme="dark"] .cancel-btn:hover {
    background-color: #2a4060;
    border-color: #ff3b30;
    color: #ff3b30;
}

[data-theme="dark"] .autofill-btn {
    background: #162030;
    border-color: #2a4060;
    color: #b8cad8;
}

[data-theme="dark"] .autofill-btn:hover {
    background: #1e3050;
    border-color: #3a5070;
}

/* Login / Register form card */
[data-theme="dark"] .login-form-container {
    background-color: #152538;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.3);
    outline-color: rgba(77, 163, 255, 0.15);
}

[data-theme="dark"] .form-page-title {
    color: #e2e8f0;
}

[data-theme="dark"] .login-form-container label {
    color: #e2e8f0;
}

[data-theme="dark"] .app-motto {
    color: #5a7a94;
}

/* Branding two-tone, dark mode: lighten both colors for the dark background */
[data-theme="dark"] .bogbee-bog {
    color: #5badff;
}

[data-theme="dark"] .bogbee-bee {
    color: #ffb740;
}

[data-theme="dark"] .form-input {
    background-color: #162030;
    border-color: #2a4060;
    color: #e2e8f0;
}

[data-theme="dark"] .form-input::placeholder {
    color: #5a7a94;
}

/* Autofill inset shadow for dark mode */
[data-theme="dark"] input:-webkit-autofill,
[data-theme="dark"] input:-webkit-autofill:focus {
    box-shadow: 0 0 0px 1000px #162030 inset;
    -webkit-box-shadow: 0 0 0px 1000px #162030 inset;
    -webkit-text-fill-color: #e2e8f0;
}

/* 3-D button base plate: visible blue in dark mode so it doesn't vanish into the card */
[data-theme="dark"] .form-button-wrap::after {
    background: #0a3a6a;
}

/* Brighter bottom ledge so the button frame is visible against the dark card */
[data-theme="dark"] .form-button {
    box-shadow: inset 0 1px 0 #C9E2FF, 0 10px 0 #0a52c4;
}

[data-theme="dark"] .form-button:hover {
    box-shadow: inset 0 1px 0 #D6E9FF, 0 10px 0 #1060d4;
}

[data-theme="dark"] .form-button.loading,
[data-theme="dark"] .form-button:active {
    box-shadow: inset 0 1px 0 #007AFF, inset 0 -3px 0 #005FCC;
}

/* Links */
[data-theme="dark"] .login-link,
[data-theme="dark"] .register-link,
[data-theme="dark"] .forgot-password-link {
    color: #4da3ff;
}

/* Login error text */
[data-theme="dark"] .login-error {
    color: #ff9090;
}

/* Loading / spinner text */
[data-theme="dark"] .loading-message {
    color: #94a3b8;
}

/* Powered-by badge: slightly brighter in dark mode */
[data-theme="dark"] .powered-by {
    color: rgba(255, 255, 255, 0.25);
}

[data-theme="dark"] .powered-by-name {
    color: rgba(255, 255, 255, 0.38);
}

[data-theme="dark"] .powered-by:hover {
    color: rgba(255, 255, 255, 0.6);
}

[data-theme="dark"] .powered-by:hover .powered-by-name {
    color: rgba(255, 255, 255, 0.85);
}

/* ─── Google Places Autocomplete dropdown (.pac-container) ──────────────
   Appended to <body> by the legacy Autocomplete API, so must be global. */
.pac-container {
    font-family: 'Inter', system-ui, sans-serif;
    background: var(--card-bg, #fff);
    border: 1px solid var(--card-border, #ddd);
    border-radius: 0 0 10px 10px;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.12);
    box-sizing: border-box;
    margin-top: 4px;
    overflow: hidden;
    z-index: 1050;
    width: var(--pac-input-width) !important;
    transform: translateX(var(--pac-left-adjust, 0px));
}

.pac-item {
    padding: 9px 14px;
    border-top: 1px solid var(--card-border, #eee);
    color: var(--card-text, #555);
    cursor: pointer;
    font-size: 0.88rem;
    line-height: 1.4;
    transition: background 0.1s ease;
}

.pac-item:first-child {
    border-top: none;
}

.pac-item:hover,
.pac-item-selected {
    background: rgba(43, 144, 217, 0.07);
}

.pac-icon {
    display: none;
}

.pac-item-query {
    color: var(--card-h3, #222);
    font-weight: 500;
    font-size: 0.9rem;
    margin-right: 3px;
}

.pac-matched {
    color: #2b90d9;
    font-weight: 600;
}

.pac-container.pac-logo::after {
    background-color: var(--card-bg, #fff);
    border-top: 1px solid var(--card-border, #eee);
    padding: 5px 12px;
    font-size: 0.7rem;
}

[data-theme="dark"] .pac-container {
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
}

[data-theme="dark"] .pac-item:hover,
[data-theme="dark"] .pac-item-selected {
    background: rgba(43, 144, 217, 0.12);
}

[data-theme="dark"] .pac-matched {
    color: #5badff;
}

/* ─── Cropper.js: circular crop box in AvatarCropModal ────────────────────
   Cropper.js injects its elements dynamically; they carry no Blazor scope
   attribute, so these rules must be global (same reason as .pac-container). */
.crop-container .cropper-view-box {
    border-radius: 50%;
    outline: 2px solid rgba(0, 122, 255, 0.85);
}

.crop-container .cropper-face {
    border-radius: 50%;
    background-color: rgba(0, 0, 0, 0);
}

.crop-container .cropper-point {
    background-color: #007AFF;
    width: 8px;
    height: 8px;
    border-radius: 50%;
}

.crop-container .cropper-line {
    background-color: rgba(0, 122, 255, 0.4);
}

.crop-container .cropper-dashed {
    border-color: rgba(255, 255, 255, 0.35);
}

/* ── Saved-state button animation (reusable across all save buttons) ── */
.btn--saved {
    background: #22c55e !important;
    transition: background 0.3s ease !important;
}

.btn--saved:hover {
    background: #16a34a !important;
    opacity: 1 !important;
}

.btn-check {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

.btn-check-path {
    stroke: #fff;
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 16;
    stroke-dashoffset: 16;
    animation: btn-check-draw 0.35s ease-out forwards;
}

@keyframes btn-check-draw {
    to { stroke-dashoffset: 0; }
}

/* ── Invoice shared styles ─────────────────────────────────────── */
.invoice-line-items { display: flex; flex-direction: column; gap: 4px; margin: 8px 0; }
.invoice-line-header,
.invoice-line-row { display: grid; grid-template-columns: 3fr 0.7fr 0.9fr 1.3fr 1.3fr 28px; gap: 8px; align-items: center; }
.invoice-line-header { font-size: 0.75rem; color: #888; font-weight: 600; margin-bottom: 2px; }
.readonly-row { grid-template-columns: 3fr 0.7fr 0.9fr 1.3fr 1.3fr; }
.li-desc-input, .li-qty-input, .li-unit-input, .li-price-input { width: 100%; min-width: 0; }
.li-total { text-align: right; }
.li-total-val { font-weight: 600; font-size: 0.88rem; text-align: right; }
.li-remove-btn { background: none; border: none; cursor: pointer; color: #e53e3e; font-size: 1rem; padding: 0 4px; }
.invoice-add-row { display: flex; gap: 8px; margin-top: 6px; }
.invoice-total-row { display: flex; justify-content: space-between; align-items: center; margin-top: 12px; padding: 10px 0; border-top: 1.5px solid #e2e8f0; }
.invoice-total-label { font-weight: 600; }
.invoice-total-val { font-size: 1.15rem; font-weight: 700; color: #2b90d9; }
.invoice-section-label { font-weight: 600; font-size: 0.85rem; color: #555; margin: 12px 0 4px; text-transform: uppercase; letter-spacing: 0.05em; }
.invoice-notes label { font-size: 0.85rem; font-weight: 600; color: #555; }
.invoice-notes p { margin: 4px 0 0; color: #444; white-space: pre-wrap; }
/* Sits left-aligned in the same row as Pay Now / Reject, so backing out doesn't force a decision */
.invoice-close-btn { margin-right: auto; }
.invoice-rejection-banner { background: #fff5f5; border: 1px solid #fc8181; border-radius: 6px; padding: 8px 12px; color: #c53030; font-size: 0.88rem; margin-bottom: 8px; }
.invoice-rejection-banner strong { display: block; margin-bottom: 2px; }
.invoice-rejection-banner p { margin: 0; color: #742a2a; }

/* Deposit-credit line: server-generated, locked (negative amount crediting a deposit already
   charged via Stripe); tinted to match .dep-charged so it reads as settled money, not a
   normal editable/removable row. */
.li-deposit-credit { background: #f0fff4; border-radius: 4px; }
.li-deposit-credit .li-desc-val, .li-deposit-credit .li-desc-locked, .li-deposit-credit .li-total-val { color: #276749; font-style: italic; }
.deposit-credit-preview { display: flex; align-items: center; gap: 6px; background: #f0fff4; color: #276749; border-radius: 6px; padding: 8px 12px; font-size: 0.85rem; margin: 8px 0; }

/* Line items: full 5/6-column grid on desktop; on mobile the description gets its own full-width
   row and the qty/unit/price/total (and remove, when editable) reflow onto a compact row below,
   so a long description never has to fight the numeric columns for space. */
@media (max-width: 819px) {
    .invoice-line-header { display: none; }

    .invoice-line-row.readonly-row {
        grid-template-columns: repeat(4, 1fr);
        grid-template-areas:
            "desc desc desc desc"
            "qty  unit price total";
        row-gap: 4px;
        padding: 10px 0;
        border-bottom: 1px solid #eee;
    }

    .invoice-line-row:not(.readonly-row) {
        grid-template-columns: repeat(4, 1fr) 28px;
        /* Row 2's last cell used to be "." (empty); the remove button only exists on row 1, so
           that 28px track had nothing in row 2, leaving the qty/unit/price/total group looking
           like it stopped ~28px short of the row's true right edge instead of using the full
           width. Extending "total" across both of the last two tracks fills that gap; .li-total-
           val is already text-align:right, so the number just lands flush against the real edge
           instead of floating short of it. */
        grid-template-areas:
            "desc desc desc desc remove"
            "qty  unit  price total total";
        row-gap: 4px;
        padding: 10px 0;
        border-bottom: 1px solid #eee;
    }

    .li-desc-val, .li-desc-input { grid-area: desc; }
    .li-qty-val, .li-qty-input { grid-area: qty; }
    .li-unit-val, .li-unit-input { grid-area: unit; }
    .li-price-val, .li-price-input { grid-area: price; }
    .li-total-val { grid-area: total; }
    .li-remove-btn { grid-area: remove; }
}

/* Invoice state badges */
.invoice-state-badge { display: inline-block; padding: 4px 10px; border-radius: 20px; font-size: 0.82rem; font-weight: 600; margin-bottom: 12px; }
.state-draft, .inv-draft { background: #edf2f7; color: #718096; }
.state-sent, .inv-sent { background: #ebf8ff; color: #2b6cb0; }
.state-accepted, .inv-accepted { background: #f0fff4; color: #276749; }
.state-rejected, .inv-rejected { background: #fff5f5; color: #c53030; }
.state-paid, .inv-paid { background: #faf5ff; color: #553c9a; }
.state-cancelled, .inv-cancelled { background: #f7ede8; color: #9c6644; }

/* Deposit state badges (reuse .invoice-state-badge / .invoice-badge shape) */
.dep-not-yet-due { background: #edf2f7; color: #718096; }
.dep-pending { background: #fff3cd; color: #856404; }
.dep-charged { background: #f0fff4; color: #276749; }
.dep-refunding { background: #ebf8ff; color: #2b6cb0; }
.dep-refunded { background: #edf2f7; color: #718096; }
.dep-refund-failed { background: #fff5f5; color: #c53030; }
/* Kept, not lost: deliberately distinct from .dep-charged's green so a cancelled booking never
   reads like an active, healthy deposit still tied to an upcoming job. Shares .state-cancelled's
   tone, the same "this is a settled outcome" language used elsewhere. */
.dep-forfeited { background: #f7ede8; color: #9c6644; }

/* Actionable follow-up shown under a RefundFailed badge: the badge alone was a dead end with
   no way to actually reach anyone. */
.deposit-support-contact { font-size: 0.8rem; color: #c53030; margin: 4px 0 0; }
.deposit-support-contact a { color: #c53030; font-weight: 600; }

/* Row invoice badge */
.invoice-badge { display: inline-block; padding: 2px 7px; border-radius: 12px; font-size: 0.72rem; font-weight: 600; margin-left: 4px; }

/* Invoice respond section */
.invoice-respond-section { margin-top: 16px; }
.invoice-respond-prompt { font-size: 0.9rem; color: #444; margin-bottom: 10px; }
.reject-btn { background: #fff5f5; color: #c53030; border: 1px solid #fc8181; }

/* Stripe Payment Element container + in-progress state */
#payment-element { margin-bottom: 14px; }
.invoice-processing { font-size: 0.88rem; color: #555; text-align: center; margin: 10px 0; }

/* Invoice status in MyBookings detail panel */
.invoice-status-section { margin-top: 16px; border-top: 1px solid #e2e8f0; padding-top: 14px; }

/* ServiceBookingsModal / ProviderCalendar invoice banner: the whole row is clickable, opening
   the invoice (edit for Draft/Rejected, view otherwise), so it needs its own hover cue instead of
   relying on a dedicated button. Uses filter rather than a fixed hover color since the banner's
   background already varies per invoice state (.inv-draft/.inv-sent/etc). */
.sbm-invoice-banner { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 8px; margin: 6px 0 12px; font-size: 0.88rem; font-weight: 500; cursor: pointer; transition: filter 0.15s ease; }
.sbm-invoice-banner:hover { filter: brightness(0.96); }
.sbm-invoice-amount { margin-left: auto; }
/* System-generated deposit receipt, distinct from the provider's own job invoicing: same green
   accent as .dep-charged/.li-deposit-credit ties it visually to "deposit already settled". */
.sbm-invoice-deposit { border-left: 3px solid #276749; }

/* Create-invoice action, sitting left-aligned in the same row as the Update/Back buttons */
.invoice-create-btn {
    background-color: #38a169;
    color: #fff;
    border: none;
    padding: 10px 16px;
    border-radius: 6px;
    cursor: pointer;
    font-weight: 600;
    margin-right: auto;
    transition: background-color 0.25s ease, box-shadow 0.25s ease;
}
.invoice-create-btn:hover { background-color: #2f855a; box-shadow: 0 0 8px rgba(56, 161, 105, 0.4); }
.invoice-create-btn:active { transform: translateY(0); box-shadow: 0 0 4px rgba(56, 161, 105, 0.3); }

/* Invoice builder modal */
.invoice-builder-modal { max-width: 860px; width: 95vw; }
.invoice-view-modal { max-width: 720px; }
.invoice-view-meta { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.ivm-row { display: flex; gap: 12px; font-size: 0.88rem; }
.ivm-row label { font-weight: 600; min-width: 70px; color: #555; }

/* Provider-only payout breakdown (Total - platform fee - gateway fee = payout) */
.invoice-payout-breakdown { margin-top: 8px; padding: 10px 12px; background: #f7fafc; border: 1px solid #e2e8f0; border-radius: 8px; display: flex; flex-direction: column; gap: 6px; }
.invoice-payout-breakdown .ivm-row { justify-content: space-between; }
.invoice-payout-breakdown .ivm-row label { min-width: unset; }
.invoice-payout-breakdown .ivm-row.payout-total { border-top: 1px solid #e2e8f0; padding-top: 6px; margin-top: 2px; font-weight: 700; }
.invoice-payout-breakdown .ivm-row.payout-total label { color: #222; }
.invoice-payout-breakdown .ivm-row.payout-total span { color: #2e7d32; font-size: 1rem; }
.ivm-fee-unavailable { color: #999; font-style: italic; font-size: 0.85rem; }

/* Catalog picker */
.catalog-picker { border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; margin-top: 6px; }
.catalog-item-row { display: flex; justify-content: space-between; padding: 8px 12px; cursor: pointer; font-size: 0.88rem; }
.catalog-item-row:hover { background: #f7fafc; }
.catalog-item-row:not(:last-child) { border-bottom: 1px solid #e2e8f0; }
.cat-name { font-weight: 500; }
.cat-price { color: #718096; }

/* btn-send variant */
.btn-send { background: #276749; }
.btn-send:hover { background: #22543d; }
