:root {
    /* Light theme only: a phone in dark mode must not paint a dark canvas or dark form controls behind the app. */
    color-scheme: light;

    --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
    sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';

    --primary: black;
    --primary-hover: black;
    --primary-lighter: #171717;
    --primary-foreground: white;
    --accent: #f4f4f5;
    --off-white: #f5f5f5;
    /* What a light-mode browser paints behind the app; set explicitly so dark mode can't change it. */
    --page-background: white;
    --off-white-darker: #bfbfbf;
    --border-colour: #e5e5e5;
    --border-colour-darker: #a9a9a9;
    --shadow: rgb(207 207 207 / 0.60);
    --muted-foreground: #737373;
    --footer-foreground: #a3a3a3; /* the © line: lighter than muted text so it stays quiet next to content */
    --active-hover: #454545;

    /* The shadcn/ui names the components were written with, as aliases of the real tokens above. Components say
       var(--border) and never repeat a fallback colour, so changing a token here reaches all of them. */
    --background: var(--page-background);
    --border: var(--border-colour);
    --input: var(--border-colour);
    --muted: var(--accent);
    --secondary: var(--accent);
    --secondary-foreground: var(--primary);
    --accent-foreground: var(--primary);
    --popover: var(--page-background);
    --popover-foreground: var(--primary);

    /* Badge tints: a light background and a darker text colour per hue (Badge, StatusBadge, JobLineItemTypeBadge). Blue
       and green text are the info and success tokens. A badge says "blue", never a hex colour. */
    --badge-red-bg: #fee2e2;
    --badge-red-fg: #b91c1c;
    --badge-orange-bg: #ffedd5;
    --badge-orange-fg: #c2410c;
    --badge-amber-bg: #fef3c7;
    --badge-amber-fg: #b45309;
    --badge-green-bg: #dcfce7;
    --badge-green-fg: var(--success);
    --badge-blue-bg: #dbeafe;
    --badge-blue-fg: var(--info);
    --badge-violet-bg: #ede9fe;
    --badge-violet-fg: #7c3aed;
    --badge-pink-bg: #fce7f3;
    --badge-pink-fg: #db2777;
    --badge-slate-bg: #f1f5f9;
    --badge-slate-fg: #64748b;
    --badge-teal-bg: #ccfbf1;
    --badge-teal-fg: #0f766e;
    --badge-indigo-bg: #e0e7ff;
    --badge-indigo-fg: #4338ca;
    --badge-purple-bg: #f3e8ff;
    --badge-purple-fg: #7e22ce;
    --badge-emerald-bg: #d1fae5;
    --badge-emerald-fg: #047857;

    --destructive: #dc2626;
    --destructive-foreground: #fef2f2;
    --warning: #d97706;
    --warning-foreground: #fffbeb;
    --success: #16a34a;
    --success-foreground: #f0fdf4;
    --info: #2563eb;
    --info-foreground: #eff6ff;
    /* The info toast: sky, lighter than the info blue, which read as purple on a toast; 5.6:1. */
    --toast-info-bg: #f0f9ff;
    --toast-info-fg: #0369a1;

    /* Photo markup ink (the swatches and photo-markup.js both read these). */
    --markup-red: #ef4444;
    --markup-yellow: #facc15;
    --markup-white: #ffffff;

    /* The markup editor's dark stage and toolbar, and the photo viewer's controls. */
    --markup-stage: #1c1c1e;
    --markup-toolbar: rgb(28 28 30 / 0.85);
    --markup-outline: rgb(0 0 0 / 0.35);
    --photo-viewer-control: rgb(255 255 255 / 0.85);

    --radius: 0.625rem;
    --card-radius: 1.125rem;
    --spacing: 0.25rem;

    --text-sm: 0.875rem;
    --text-xs: 0.75rem;
    /* Small labels in dense grids (the calendar's hour gutter, plates in its chips, Home's week card). */
    --text-2xs: 0.6875rem;
    /* Text in an input, select or textarea. Safari on iPhone zooms the page when a field under 16px gets focus, so
       touch screens get 16px (below). Never maximum-scale=1 in the viewport meta instead: it breaks pinch-zoom. */
    --text-input: var(--text-sm);

    /* Motion (CLAUDE.md, UI & Blazor Guidelines; Specs/Spec - Quiet Delight.md §4), three tiers:
       - micro (--motion-fast, 120ms): a state changing in place (a tab's content, an indicator, an exit);
       - base (--motion-base, 180ms): opacity and a move of at most 4px or a scale of at least 0.96 (enters, popovers);
       - spatial (--motion-spatial, 220ms): only for something that changes place or size (a wizard's step entering from
         its side, the phone drawer, a modal easing to its new size), a move of at most 24px (the drawer: its own height),
         never blocking input. */
    --motion-fast: 120ms;
    --motion-base: 180ms;
    --motion-spatial: 220ms;
    --ease-out: cubic-bezier(0.2, 0, 0, 1);
    --ease-in: cubic-bezier(0.4, 0, 1, 1);

    /* The keyboard focus ring: 4.7:1 on white, over the 3:1 a focus indicator needs. */
    --ring: #737373;
    /* A standalone control's ring (buttons, cards, icon buttons, tabs, the checkbox): offset, so it reads on any fill. */
    --focus-ring: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
    /* A row inside a card or list: drawn inside, where a card's overflow: hidden can't clip it. */
    --focus-ring-inset: inset 0 0 0 2px var(--ring);
    /* A focused field: its border in --ring plus this soft halo. */
    --field-focus-ring: 0 0 0 3px color-mix(in srgb, var(--ring) 25%, transparent);
    /* A field's border under a fine pointer's hover (inside @media (hover: hover) only). */
    --input-hover: var(--border-colour-darker);
    /* How long a just-added note keeps its highlight before it fades (QD-M9: no long holds). */
    --highlight-hold: 800ms;
    /* A pressed row, menu item, tab or icon button: one step darker than the --accent hover tint, so a press reads on
       desktop too. Buttons and cards keep their 0.98 scale instead. */
    --pressed: #e4e4e7;
    /* The colour of a raw link that sets none. */
    --link: #0071c1;

    --top-row-height: 4rem;
    /* How far down the scroll area's sticky elements must stop to clear the app's top bar, which is sticky at every
       width (MainLayout) and exactly --top-row-height tall. */
    --sticky-offset: var(--top-row-height);
}

@media (pointer: coarse) {
    :root {
        --text-input: 1rem;
    }
}

*, *::before, *::after {
    box-sizing: border-box;
}

html, body {
    font-family: var(--font-sans);
}

html {
    background: var(--page-background);
}

/* Browsers give form controls their own system font instead of inheriting it, so any raw <button>, <input>… would
   render in a different typeface. Inherit everywhere; a component that wants another family (e.g. monospace plates)
   still sets it in its own CSS, which wins over this element selector. */
button, input, select, textarea, optgroup {
    font-family: inherit;
}

/* Raw fields (a read-only link to copy, the search bar) get the field size too; the field components set it in their
   own CSS with the same token. */
input, select, textarea {
    font-size: var(--text-input);
}

body {
    margin: 0;
    max-width: 100%;
    overflow-x: clip;
    /* Safety net: an unbroken long word (an email in a sentence) breaks instead of overflowing its card. Words that fit
       are never broken. */
    overflow-wrap: break-word;
}

#app {
    min-width: 0;
    min-height: 100dvh;
}

h1:focus {
    outline: none;
}

a {
    color: var(--link);
}

.content {
    padding-top: 1.5rem;
    width: 100%;
    max-width: 1200px;
    min-width: 0;
    margin-inline: auto;
    padding-inline: 1.5rem;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid red;
}

.validation-message {
    color: red;
}

.center-vertically {
    display: flex;
    align-items: center;
}

.space-between {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.center-horizontally {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
}

#blazor-error-ui {
    color-scheme: light only;
    background: var(--warning-foreground);
    border-top: 1px solid var(--warning);
    color: var(--primary);
    font-size: var(--text-sm);
    bottom: 0;
    box-shadow: 0 -1px 2px var(--shadow);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 2.5rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    /* Above every modal and the update notice (AppUpdateNotice.razor.css): once the app has stopped, Reload must be
       reachable. */
    z-index: 100001;
}

#blazor-error-ui .reload {
    margin-left: calc(var(--spacing) * 2);
    font-weight: 600;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 0.75rem;
    top: 0.5rem;
}

.w-100 {
    width: 100%;
}

.h-100 {
    height: 100%;
}

/* The phone layout (viewport.js, T-119): a width at or under 768px, or a touch screen under 500px tall (a phone in landscape). */
@media (width <= 768px), ((pointer: coarse) and (height < 500px)) {
    .desktop-only {
        display: none;
    }

    .mobile-center {
        text-align: center;
    }

    .max-w-60p-mobile {
        max-width: 60%;
    }
}

@media (width >= 769px) and (not ((pointer: coarse) and (height < 500px))) {
    .mobile-only {
        display: none;
    }
}

input {
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
}

input::placeholder {
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
}

/* Loading screen */
#garage-loader {
    position: fixed;
    inset: 0;
    z-index: 9999;

    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2rem;

    background: var(--page-background);
    color: var(--primary);

    opacity: 1;
    transition: opacity var(--motion-base) var(--ease-out);
}

#garage-loader.garage-loader--fade-out {
    opacity: 0;
    pointer-events: none;
}

.garage-loader__brand {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    font-size: 1.5rem;
    font-weight: 600;
    letter-spacing: -0.035em;
}

.garage-loader__logo {
    width: 2.25rem;
    height: 2.25rem;
    object-fit: contain;
}

.garage-loader__indicator {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 3px;
    height: 1.25rem;
}

.garage-loader__indicator span {
    width: 3px;
    height: 20px;
    border-radius: 999px;
    background: var(--primary);
    transform: scaleY(0.4);
    animation: garage-loader-bounce 900ms ease-in-out infinite;
}

.garage-loader__indicator span:nth-child(1) {
    animation-delay: 0ms;
}

.garage-loader__indicator span:nth-child(2) {
    animation-delay: 100ms;
}

.garage-loader__indicator span:nth-child(3) {
    animation-delay: 200ms;
}

.garage-loader__indicator span:nth-child(4) {
    animation-delay: 300ms;
}

@media (hover: hover) {
.custom-select > option:hover {
    background-color: var(--off-white);
    border-radius: 0.5rem;
}
}

.custom-select > option {
    padding: 6px;
}

@keyframes garage-loader-bounce {
    0%,
    60%,
    100% {
        transform: scaleY(0.4);
        opacity: 0.35;
    }

    30% {
        transform: scaleY(1);
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    #garage-loader {
        transition: none;
    }

    .garage-loader__indicator span {
        animation: none;
        opacity: 0.5;
    }
}

@media (prefers-color-scheme: dark) {
    #garage-loader {
        background: #09090b;
        color: #fff;
    }

    .garage-loader__indicator span {
        background: #fff;
    }
}

.grid-responsive {
    display: grid;
    gap: 1rem;
    min-width: 0;
}

.grid-responsive-2 {
    grid-template-columns: minmax(0, 1fr);
}

.grid-responsive-3 {
    grid-template-columns: minmax(0, 1fr);
}

/* Use the available application area, rather than viewport width: the sidebar can
   substantially reduce that space. These are intentional 1-or-2 and 1-or-3 grids. */
@container (width >= 40rem) {
    .grid-responsive-2 {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@container (width >= 769px) {
    .grid-responsive-3 {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

.grid-responsive > *,
.card,
.card-title-with-icon {
    min-width: 0;
}

.card-title-with-icon {
    display: flex;
    align-items: center;
    align-content: center;
    justify-content: space-between;
    gap: 0.75rem;
}

.card-title-with-icon > svg {
    flex: 0 0 auto;
}

.padding-0 {
    padding: 0;
}

.margin-0 {
    margin: 0;
}

.toned-down-h1 {
    font-weight: 600;
    font-size: 1.5rem;
}

/* Text that doesn't fit (CLAUDE.md, UI & Blazor Guidelines). */

/* Free text in a row, card or table cell: one line with an ellipsis. Give the element a title with the full text. */
.truncate {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* A labelled value in a card tile: at most two lines, then an ellipsis. Give the element a title with the full text. */
.clamp-2 {
    min-width: 0;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    overflow-wrap: anywhere;
    white-space: normal;
}

/* Free text followed by things that must stay whole (a badge, an icon), in a table cell or a row: only the text, marked
   .truncate, shrinks. */
.text-with-extras {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    min-width: 0;
}

.text-with-extras > :not(.truncate) {
    flex-shrink: 0;
}

/* Codes, numbers, money, dates and phone numbers: never broken. The layout gives them their width. */
.nowrap {
    white-space: nowrap;
}

/* Emails and URLs on detail pages and in running text: break anywhere rather than overflow. */
.wrap-anywhere {
    overflow-wrap: anywhere;
}

label {
    display: grid;
    gap: 0.375rem;
    flex: 1;
}

label > span {
    font-size:var(--text-sm);
    font-weight:500;
}

.required-label::after {
    padding-inline-start: 2px;
    content: "*";
    font-weight: 700;
    color: var(--destructive);
}

.vertical-stack {
    display: flex;
    flex-direction: column;
}

.horizontal-stack {
    display: flex;
    flex-direction: row;
}

.flex-1 {
    flex: 1;
}

.border-right {
    border-right: 1px solid var(--off-white-darker);
}

.border-left {
    border-left: 1px solid var(--off-white-darker);
}

.form-container {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    container-type: inline-size;
}

.stacked-buttons-container {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    container-type: inline-size;
}

.subtle-text {
    color: var(--muted-foreground);
    font-size: var(--text-xs);
    font-weight: 500;
}

.subtext {
    color: var(--muted-foreground);
    font-size: var(--text-sm);
}

.subtext-md {
    color: var(--muted-foreground);
    font-size: .925rem;
}

.fw-500 {
    font-weight: 500;
}

.opacity-70 {
    opacity: 70%;
}

.invisible {
    visibility: hidden;
}

/* Hidden from sight but still in the page, e.g. a file input opened from a button: some mobile browsers won't open a
   picker for an input that is display: none. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.gap-2 {
    gap: 2rem;
}

.gap-1 {
    gap: 1rem;
}

.gap-half {
    gap: 0.5rem;
}

.gap-quarter {
    gap: 0.25rem;
}

.validation-message {
    color: var(--destructive);
    font-weight: 400;
    font-size: var(--text-xs);
}

.font-medium {
    font-weight: 500;
}

.text-sm {
    font-size: var(--text-sm);
}

.text-md {
    font-size: 0.925rem;
}

.text-lg {
    font-size: 1.075rem;
}

.text-xl {
    font-size: 1.125rem;
}

hr {
    margin-block: 1rem;
    border: none;
    height: 1px;
    background-color: var(--border-colour);
}

.autocomplete-result {
    display: grid;
    gap: 0.125rem;
}

/* A stock level below zero (InventoryFormatting.OnHandClass). */
.inventory-negative {
    color: var(--destructive);
    font-weight: 600;
}

.clickable {
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

@media (hover: hover) {
    .clickable:hover {
        background-color: var(--off-white) !important;
    }
}

.clickable:active {
    background-color: var(--pressed) !important;
}

.text-bold {
    font-weight: 600;
}

.text-italic {
    font-style: italic;
}

.background-off-white {
    background-color: var(--off-white);
    border-radius: var(--radius);
}

.background-accent {
    background: color-mix(in srgb, var(--accent) 50%, transparent);
}

.p-1 {
    padding: 1rem;
}

.p-half {
    padding: 0.5rem;
}

.p-three-quarter {
    padding: 0.75rem;
}

.ps-1 {
    margin-inline-start: 1rem;
}

.ps-half {
    margin-inline-start: 0.5rem;
}

.ms-half {
    margin-inline-start: 0.5rem;
}

.ms-1 {
    margin-inline-start: 1rem;
}

.me-half {
    margin-inline-end: 0.5rem;
}

.me-quarter {
    margin-inline-end: 0.25rem;
}

.px-0 {
    padding-left: 0 !important;
    padding-right: 0 !important;
}

.px-1 {
    padding-left: 1rem !important;
    padding-right: 1rem !important;
}

.px-half {
    padding: 0 0.5rem !important;
}

.pt-0 {
    padding-top: 0 !important;
}

.pt-quarter {
    padding-top: 0.25rem !important;
}

.pt-half {
    padding-top: 0.5rem !important;
}

.pe-2 {
    padding-right: 2rem;
}

.me-1 {
    margin-right: 1rem;
}

.me-2 {
    margin-right: 2rem;
}

.icon-container {
    display: flex;
    align-items: center;
    border-radius: var(--radius);
    background-color: var(--off-white);
    padding: 0.5rem;
}

.pb-0 {
    padding-bottom: 0 !important;
}

.border-radius-bottom {
    border-bottom-right-radius: var(--radius);
    border-bottom-left-radius: var(--radius);
}

.overflow-hidden {
    overflow: hidden;
}

.modal-footer-content {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 0.75rem;
}

/* On a phone the footer's buttons stack full width, the primary on top (it is last in the markup, so column-reverse puts it
   first) and Cancel at the bottom: two long labels ("Guardar y agregar otro") never push each other off a 360 px dialog.
   The coarse-pointer sticky row in Modal.razor.css stacks the same way (modal.js measures its height for the scroll padding).
   The wizard's own footer (QuickActionsWizardModal.razor.css) follows the same rule. */
@media (max-width: 480px) {
    .modal-footer-content {
        flex-direction: column-reverse;
        align-items: stretch;
    }

    .modal-footer-content > * {
        width: 100%;
    }

    .modal-footer-content .btn {
        width: 100%;
        justify-content: center;
    }
}

.text-center {
    text-align: center;
}

.rotate-90 {
    rotate: 90deg;
}

/* Motion. One small vocabulary: a fade with a 4px rise. Page and content enters play once, when the element is created. */
@keyframes fade-in-up {
    from {
        opacity: 0;
        transform: translateY(4px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* The routed page, keyed by its type in MainLayout (a page change fades in, a query or id change doesn't), and content
   that replaces a skeleton. The fill mode is `backwards`, never `both`: `both` keeps the last keyframe's transform after the
   animation ends, and a transformed ancestor becomes the containing block of every `position: fixed` popover inside it
   (the suggestion list and menus would open offset by the page's position). */
.page-enter,
.fade-in {
    animation: fade-in-up var(--motion-base) var(--ease-out) backwards;
}

/* QD-C1, the Paid in full moment (S1t.8): the check and "Paid in full" enter once, where the payment that settled the
   invoice was recorded (InvoiceTotalsCard, the invoice page's overview). A micro-tier scale from 0.96 and a base-tier fade,
   `backwards` so no transform is left behind. The view keeps the class until its next load, so re-renders never restart
   or cut it. */
.invoice-paid-enter {
    animation: invoice-paid-scale var(--motion-fast) var(--ease-out) backwards,
        invoice-paid-fade var(--motion-base) var(--ease-out) backwards;
}

@keyframes invoice-paid-scale {
    from {
        transform: scale(0.96);
    }

    to {
        transform: scale(1);
    }
}

@keyframes invoice-paid-fade {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

/* Press feedback on clickable cards and buttons (Button.razor.css and Card.razor carry their own transitions). */
.btn:active:not(:disabled):not(.btn-loading),
.card-link:active,
.stat-card-clickable:active,
.invoice-card:active,
.transaction-card:active,
.job-card:active,
.job-board-card:active,
.customer-job-card:active,
.customer-vehicle-card:active,
.vehicle-job-card:active {
    transform: scale(0.98);
}

/* The shared keyboard ring on the other clickable card classes (`.card-link` has its own rule with the hover lift). */
:is(.invoice-card, .transaction-card, .job-card, .job-board-card, .customer-job-card, .customer-vehicle-card, .vehicle-job-card):focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

/* No press scale while the card's menu is open, or when the press starts on the menu's trigger or on a card action
   (`data-card-action`): a transformed card becomes the containing block of the menu's `position: fixed` panel, so the panel
   jumps away from the finger and the release lands on the card, which opens the record instead of running the menu's
   item (S1o item 3). A button inside the card still scales on its own press (`.btn:active`). */
:is(.card-link, .stat-card-clickable, .invoice-card, .transaction-card, .job-card, .job-board-card, .customer-job-card, .customer-vehicle-card, .vehicle-job-card):active:has(.dm-content, .dm-trigger-wrapper:active, [data-card-action]:active) {
    transform: none;
}

.stat-card-clickable,
.invoice-card,
.transaction-card,
.job-card,
.job-board-card,
.customer-job-card,
.customer-vehicle-card,
.vehicle-job-card {
    transition: transform var(--motion-fast) var(--ease-out), box-shadow var(--motion-fast) var(--ease-out), border-color var(--motion-fast) var(--ease-out);
}

/* Reduced motion: every enter, exit and press effect is instant (opacity changes included), all in this block. Only the
   skeleton shimmer and the spinner opt out in their own files: they keep what tells the state (QD-M9, S1t.26). A new
   component with an animation adds its class to the first rule; its hover and focus transitions are already covered by
   the `*` rule. */
@media (prefers-reduced-motion: reduce) {
    .page-enter,
    .fade-in,
    .invoice-paid-enter,
    .modal-backdrop,
    .modal-panel,
    .toast,
    .dm-content,
    .autocomplete-content,
    .suggest-content,
    .note-card-new,
    .report-section-moved-a,
    .report-section-moved-b,
    .flash-note-line,
    .save-indicator-line,
    .tabs-content,
    .tooltip-content.tooltip-positioned,
    .stepper-step[class*="--enter-"] {
        animation: none !important;
        transition: none !important;
    }

    /* The mobile drawer and its backdrop just appear. */
    .sidebar,
    .sidebar-backdrop {
        transition: none !important;
    }

    /* Every other colour, border, shadow and opacity transition (the hover and focus states of the components): the
       change is instant. Animations are listed above, never here, so spinners and the like opt out in their own files. */
    *,
    *::before,
    *::after {
        transition: none !important;
    }

    /* The wear gauge's thumb is a pseudo-element the `*` rule doesn't reach (S1t.22, B-18). */
    .wear-gauge-input::-webkit-slider-thumb {
        transition: none !important;
    }

    /* Two rules, not a list: a browser drops a whole list that holds a pseudo-element it doesn't know. */
    .wear-gauge-input::-moz-range-thumb {
        transition: none !important;
    }

    /* A closed modal and a dismissed toast just go (their services remove the nodes a moment later). */
    .modal-wrapper--closing,
    .toast--closing {
        display: none !important;
    }

    .btn,
    .card-link,
    .ui-collapse,
    .card-content-collapse,
    .card-content-collapse > .card-content,
    .line-inventory-collapse,
    .line-inventory-section-toggle,
    .line-inventory-section-toggle svg,
    .pricing-bands-collapse,
    .stat-group-collapse,
    .stat-card-clickable,
    .invoice-card,
    .transaction-card,
    .job-card,
    .job-board-card,
    .customer-job-card,
    .customer-vehicle-card,
    .vehicle-job-card,
    [class*="-list-content"] {
        transition: none !important;
    }

    /* The `*` rule above also drops the 150 ms delay of the list pages' `.is-refreshing` dim (and the Estimate tab's, and an
       estimate line's amount while its inline save runs), so a fast refresh would blink. The dim stays instant (no fade)
       but still waits 150 ms; un-dimming stays instant. */
    [class*="-list-content"].is-refreshing,
    .estimate-tab-refreshing,
    .estimate-item-amount-saving {
        transition: opacity 0s linear 150ms !important;
    }

    .btn:active:not(:disabled):not(.btn-loading),
    .card-link:active,
    .stat-card-clickable:active,
    .invoice-card:active,
    .transaction-card:active,
    .job-card:active,
    .job-board-card:active,
    .customer-job-card:active,
    .customer-vehicle-card:active,
    .vehicle-job-card:active {
        transform: none;
    }
}


/* ==========================================================================================================
   Global component styles
   These rules style the elements the UI components render, and other components' ::deep rules and child content
   rely on them, so they are global (they used to be a <style> element inside each component, repeated for every
   instance). Kept at the end of the file so they keep winning over the rules above at equal specificity, as the
   <style> elements did. One block per component.
   ========================================================================================================== */

/* TabsList's edge fade (Components/UI/Tabs/TabsList.razor.css): registered so the fade's width can transition. */
@property --tabs-fade-start {
    syntax: "<length>";
    inherits: false;
    initial-value: 0px;
}

@property --tabs-fade-end {
    syntax: "<length>";
    inherits: false;
    initial-value: 0px;
}

/* Card (Components/UI/Card/Card.razor) */
.card {
    display: flex;
    flex-direction: column;
    width: 100%;
    border: 1px solid var(--border-colour);
    border-radius: var(--card-radius, var(--radius, 0.625rem));
    background-color: var(--primary-foreground);
    color: var(--primary);
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
}

.card-link {
    cursor: pointer;
    transition: box-shadow var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
}

.card-link {
    -webkit-tap-highlight-color: transparent;
}

@media (hover: hover) {
    .card-link:hover {
        box-shadow: 0 4px 12px rgb(0 0 0 / 0.1);
    }
}

/* The ring plus the hover's lift. */
.card-link:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring), 0 4px 12px rgb(0 0 0 / 0.1);
}

/* CardHeader (Components/UI/Card/CardHeader.razor) */
.card-header {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 1.5rem 1.5rem 0;
}

.card-header-expandable {
    align-items: center;
    padding: 0.75rem 1.5rem;
}

/* The condensed expandable header lines up with the condensed content's 1 rem (only JobReminderFields uses both). */
.card-header-expandable.card-header-condensed {
    padding: 0.5rem 1rem;
}

.card-header:not(.card-header-expandable).card-header-condensed {
    padding: 1rem 1.5rem 0;
}

.card-header-expandable > .card-header-content,
.card-header-expandable > .card-expand-trigger {
    /* Use layout offset instead of transform so dropdowns inside the header
       can stack above content rendered after the header. */
    position: relative;
    top: 2px;
}

.card-header-content {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
    flex: 1;
    min-width: 0;
}

.card-expand-trigger {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    padding: 0.125rem;
    border: 0;
    background: transparent;
    color: inherit;
    cursor: pointer;
}

.card-expand-trigger svg {
    transition: transform var(--motion-fast) var(--ease-out);
}

.card-chevron-collapsed {
    transform: rotate(-90deg);
}

/* CardTitle (Components/UI/Card/CardTitle.razor) */
.card-title {
    margin: 0;
    font-size: 1.125rem;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.025em;
    white-space: nowrap;
}

/* CardDescription (Components/UI/Card/CardDescription.razor) */
.card-description {
    margin: 0;
    color: var(--muted-foreground, #737373);
    font-size: var(--text-sm);
    line-height: 1.25rem;
}

/* CardContent (Components/UI/Card/CardContent.razor) */
.card-content-collapse {
    display: grid;
    grid-template-rows: 1fr;
    min-height: 0;
    opacity: 1;
    transition: grid-template-rows var(--motion-base) var(--ease-out), opacity var(--motion-fast) var(--ease-out);
}

.card-content-collapse-collapsed {
    grid-template-rows: 0fr;
    opacity: 0;
}

.card-content-collapse > .card-content {
    min-height: 0;
    overflow: hidden;
    /* The closed card's padding (CardContent.razor.css) shrinks with the rows, so the card doesn't first snap shorter. */
    transition: padding var(--motion-base) var(--ease-out);
}

/* A closed card's content padding is removed in CardContent.razor.css (its scoped padding would beat a rule here). */

/* CardFooter (Components/UI/Card/CardFooter.razor) */
.card-footer {
    display: flex;
    align-items: center;
    padding: 0 1.5rem 1.5rem;
    white-space: nowrap;
}

.card-footer.card-footer-condensed {
    padding-bottom: 1rem;
}

.card-footer-collapse {
    display: grid;
    grid-template-rows: 1fr;
    min-height: 0;
    opacity: 1;
    transition: grid-template-rows var(--motion-base) var(--ease-out), opacity var(--motion-fast) var(--ease-out);
}

.card-footer-collapse-collapsed {
    grid-template-rows: 0fr;
    opacity: 0;
}

.card-footer-collapse > .card-footer {
    min-height: 0;
    overflow: hidden;
}

/* TableRow (Components/UI/Table/TableRow.razor) */
.table-row-link {
    cursor: pointer;
}

/* TableCell (Components/UI/Table/TableCell.razor) */
.table-cell-link {
    cursor: pointer;
}

/* Skeleton (Components/UI/Skeleton/Skeleton.razor) */
.skeleton {
    display: block;
    flex-shrink: 0;
    border-radius: var(--radius, 0.625rem);
    background: var(--muted);
    background: linear-gradient(
        110deg,
        var(--muted) 20%,
        color-mix(in srgb, var(--muted) 35%, var(--background)) 50%,
        var(--muted) 80%
    );
    background-size: 300% 100%;
    animation: skeleton-shimmer 1.1s linear infinite;
}

@keyframes skeleton-shimmer {
    0% { background-position: 100% 0; }
    100% { background-position: -150% 0; }
}

@media (prefers-reduced-motion: reduce) {
    .skeleton {
        animation: none;
        background: var(--muted);
    }
}

/* Icons (S1x, T-34): every icon component renders .icon-tabler; an icon never shrinks in a flex row (a long label beside it
   squeezed the 16 px svg to a few pixels). */
.icon-tabler {
    flex-shrink: 0;
}

/* .touch-target (S1x, T1): on a touch screen an element gets a 44 px hit area through an invisible ::after, without its
   box or the layout changing. The inset grows each axis up to 2.75rem and never shrinks one that is already bigger
   (a wide link grows only vertically). Add the class to the raw element in a component's own markup.
   Not for an element that clips its own overflow (.truncate, overflow: hidden): its ::after is clipped and hits
   nothing; those get min-height: 2.75rem on a coarse pointer instead. Neighbours must keep their areas apart (a gap at
   least the growth, or min-height instead); measure.js reports the overlaps. */
@media (pointer: coarse) {
    .touch-target {
        position: relative;
    }

    .touch-target::after {
        content: "";
        position: absolute;
        inset: min(0px, calc((100% - 2.75rem) / 2));
    }
}

/* PageFieldReveal (Components/PageFieldReveal.razor, scripts/scroll.js followPageFields; T-76): while Chrome's keyboard
   overlays a page (a page field was tapped), the body gets the keyboard's height under its content, so a field near the
   page's end can scroll up above the keyboard. Set and removed by the script; never in a modal. */
:root.page-keyboard-open body {
    padding-bottom: var(--page-keyboard-height, 0px);
}
