html, body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

/* Single source of truth for the app-shell chrome heights (MainLayout.razor.css/BottomNav.razor.css
   render the actual bars; these tokens just publish their numbers). Declared here — global, unscoped —
   rather than in MainLayout.razor.css's scoped CSS, because Blazor's CSS isolation appends its scope
   attribute only to elements a component itself renders, so a scoped `:root { }` rule silently matches
   nothing (`:root[b-xxxx]` never exists — see CLAUDE.md's Blazor traps). Any page that draws its own
   full-bleed content (a game, a wizard step, etc.) should size against these instead of hardcoding the
   same numbers, so a future chrome resize can't drift out of sync with what pages assume (the bug this
   fixed 2026-08-19: RigRacePage's fullscreen calc omitted --ty-bottomnav-h entirely, leaving the shell's
   own BottomNav clearance as unclaimed padding below it and pushing the whole document past 100dvh on
   every tablet/phone width, worst at phone landscape where every px of a ~390px-tall viewport counts). */
:root {
    --ty-topbar-h: 3.5rem;
    /* The footer nav bar is on screen at every width (it is the shell's constant primary navigation, not a
       mobile-only affordance), so this is a single unconditional number rather than a per-breakpoint one.
       Matches BottomNav.razor.css's own rendered height (~60px content + its padding); a round number,
       since pages only need "roughly how much to clear", not a pixel-exact echo of the nav's measured box. */
    --ty-bottomnav-h: 60px;

    /* The trial countdown strip (Modules.Shared/Subscription/TrialBanner) docks directly ON TOP of the
       footer nav, so it occupies the band the corner FABs were already sitting in. Two tokens, because
       they answer different questions:
         --ty-trialstrip-h          how tall the strip IS. A published constant, like --ty-bottomnav-h
                                    above, and TrialBanner.razor.css pins its own min-height to it so the
                                    number here cannot quietly stop being true. 62px = 8px padding twice,
                                    the dismiss button's 44px WCAG touch target, and the 2px top border.
         --ty-trialstrip-clearance  how much EXTRA chrome that already clears the footer nav must move up.
                                    Zero by default, because the strip is absent far more often than it is
                                    present — a paid tenant, a platform admin, a dismissed session, or the
                                    paywall instead.
         --ty-trialstrip-overlay    how far up from the VIEWPORT BOTTOM the strip reaches — the nav band
                                    plus the strip itself. For chrome that does NOT already clear the footer
                                    nav: the sidebar, which the nav bar starts to the right of but the
                                    full-width strip crosses. Using the clearance there instead leaves the
                                    rail's last item exactly one nav-bar short, i.e. still underneath. */
    --ty-trialstrip-h: 3.875rem;
    --ty-trialstrip-clearance: 0px;
    --ty-trialstrip-overlay: 0px;

    /* The corner-FAB rail. Three files used to hand-compute this same arithmetic (the messages launcher, the
       shell's quick-add "+", the customers page's action button) and a fourth would have collided with an
       existing slot, so the geometry is published once here instead. Slots run right-to-left:
         1 = messages (Communications), 2 = the shell's quick-add "+", 3 = whatever the current PAGE adds.
       --ty-fab-bottom is the shared bottom edge: clear of the footer nav bar (which is on screen at every
       width) plus the phone's home-indicator strip. Anything docking in this corner should use these rather
       than repeat the sums. */
    --ty-fab-size: 3.25rem;
    --ty-fab-gap: .75rem;
    --ty-fab-inset: 1.25rem;
    --ty-fab-bottom: calc(var(--ty-bottomnav-h, 60px) + var(--ty-trialstrip-clearance, 0px) + .75rem + var(--ty-safe-bottom, env(safe-area-inset-bottom, 0px)));
    --ty-fab-slot-1: var(--ty-fab-inset);
    --ty-fab-slot-2: calc(var(--ty-fab-inset) + (var(--ty-fab-size) + var(--ty-fab-gap)));
    --ty-fab-slot-3: calc(var(--ty-fab-inset) + 2 * (var(--ty-fab-size) + var(--ty-fab-gap)));
}

/* Raise the FAB rail whenever the trial strip is actually on screen.
   ------------------------------------------------------------------------------------------------
   Driven off the strip's PRESENCE IN THE DOM rather than a flag someone has to remember to set,
   because TrialBanner renders conditionally — a paid tenant, a platform admin, an escape route, or a
   dismissed session removes the element outright. :has() therefore tracks it exactly, including the
   moment the user hits the strip's own dismiss button: the element goes, the clearance goes with it,
   and the buttons drop back down. A stateful class toggled over JS interop would have to be kept in
   step with all four of those conditions, and would be wrong the first time one of them changed.

   MUST be :root, not body. A custom property's var() references are substituted at computed-value time
   ON THE ELEMENT THAT DECLARES IT — so --ty-fab-bottom, declared on :root above, reads --ty-trialstrip-
   clearance's value AS COMPUTED ON :root, and inherits down already resolved. Setting the clearance on
   body would therefore change nothing at all: the FABs would inherit a --ty-fab-bottom that had already
   been flattened using :root's 0px. (This cost a red test; it looks like it ought to work.) Re-declaring
   the clearance on :root instead re-resolves --ty-fab-bottom there, and source order does not matter
   because the cascade settles each property before the other reads it.

   The rule lives in this GLOBAL sheet rather than a scoped one for the same reason the token block above
   does: Blazor's CSS isolation would rewrite the selector to `:root[b-xxxx]`, which matches nothing (see
   CLAUDE.md's Blazor traps).

   Where :has() is unsupported the buttons simply sit where they do today — an overlap, not a broken
   layout. Every current target (Chromium WebView2/Android, WKWebView, and the desktop browsers) has
   had it since 2023. */
:root:has(.ty-trialstrip) {
    --ty-trialstrip-clearance: var(--ty-trialstrip-h);
    --ty-trialstrip-overlay: calc(var(--ty-bottomnav-h, 60px) + var(--ty-trialstrip-h));
}

/* The strip's own HEIGHT, here rather than in TrialBanner.razor.css, because it is shell geometry the
   rest of the chrome is entitled to rely on — --ty-fab-bottom above does arithmetic with it — and not
   private styling. Keeping it beside the token it must agree with is what stops the two drifting; a
   padding change in the component's scoped sheet can no longer silently move the strip out from under
   the buttons that were told how tall it is. Everything about how the strip LOOKS stays in its own
   scoped file. border-box, because the token is the outside edge: the 2px top border is part of what
   other chrome must clear, not extra on top of it. */
.ty-trialstrip {
    box-sizing: border-box;
    min-height: var(--ty-trialstrip-h, 3.875rem);
}


/* ============================================================================
   LAYER 1 - the base --ty-* token layer  (/design/ui section 3 & 4)
   ----------------------------------------------------------------------------
   The three-layer cascade is  :root  ->  [data-module]  ->  [data-tenant], with a
   selectable platform theme ([data-theme="trusty"|"graft"], themes/*.css) sitting
   between :root and the module layer. This block is the FLOOR: every token any
   component may reference is declared here with a sane light-theme default, so

       *no token is ever undefined in any theme* - including "none" (vanilla).

   That matters because an undefined custom property silently falls back to the
   literal a component happened to write inline (or to nothing at all), which is
   exactly how "it looks fine on Trusty and broken on Graft" bugs happen: the
   component's inline fallback is always a LIGHT value, so on the dark theme it
   paints a white block on slate. Declaring the floor here means a theme only has
   to RE-POINT tokens, never remember to define them.

   Load order is load-bearing: app.css is linked BEFORE themes/*.css in every host
   document, and :root and [data-theme="..."] have equal specificity - so the theme
   files win on source order. Do not move these declarations after the themes.

   Adding a token: declare it here AND in BOTH themes/trusty.css and
   themes/graft.css. Components reference only these semantic names - never a raw
   hex, never a domain colour directly (/design/ui section 3, "the one rule").
   ============================================================================ */
:root {
    /* --- Domain palette source-of-truth (mirrors ModuleTheme.PrimaryColor) --- */
    --ty-domain-window-cleaning: #1E6FD9;
    --ty-domain-gardening:       #2E9E4F;
    --ty-domain-scaffolding:     #D98A1E;
    --ty-domain-electrician:     #7C3AED;
    --ty-domain-plumber:         #0891B2;
    --ty-domain-solar-installer: #EA580C;
    --ty-domain-inspections:     #BE185D;
    --ty-domain-surveys:         #0F766E; /* a capability module with a face of its own (IThemedModule) */

    /* --- Colour, semantic. Primary is re-pointed per domain by Layer 2. --- */
    --ty-color-primary:          #1E6FD9;
    --ty-color-primary-ink:      #1457b0;
    --ty-color-primary-soft:     #eaf2fd;
    --ty-color-primary-strong:   #1457b0;
    --ty-color-primary-contrast: #ffffff;
    --ty-color-accent:           #1457b0;

    /* Surfaces, lightest to deepest. `surface` is a card/sheet, `-2` the page behind
       it, `-3` an inset control, `sunken` a recessed well (table zebra, code block,
       empty-state panel), `muted`/`subtle` the two quieter fills between them. */
    --ty-color-surface:        #ffffff;
    --ty-color-surface-2:      #f6f8fa;
    --ty-color-surface-3:      #eef1f4;
    --ty-color-surface-sunken: #f3f5f8;
    --ty-color-surface-muted:  #eef1f5;
    --ty-color-surface-subtle: #f6f8fa;

    --ty-color-text:        #1c2128;
    --ty-color-text-muted:  #57606a;
    --ty-color-text-subtle: #8a95a1;
    /* "ink" = the strong reading colour on a surface that stays light-on-paper in
       every theme (crash screens, rendered documents, the public quote page), so it
       must NOT flip with the dark theme the way --ty-color-text does. */
    --ty-color-ink:         #1b2733;
    --ty-color-ink-muted:   #55636f;
    /* ...and its inverse: text drawn ON an ink / solid-dark block. */
    --ty-color-text-onink:       #ffffff;
    --ty-color-text-onink-muted: rgba(255, 255, 255, .72);

    --ty-color-border:        #e7ebf0;
    --ty-color-border-strong: #d0d7de;
    --ty-color-border-soft:   #f1f4f7;
    --ty-color-border-subtle: rgba(0, 0, 0, .06);

    /* --- Status: base / ink (readable text) / soft (tinted fill) / border --- */
    --ty-color-success:        #26b050;
    --ty-color-success-ink:    #1a7f37;
    --ty-color-success-soft:   #e6f6ea;
    --ty-color-success-border: #bfe3cc;
    --ty-color-warning:        #D98A1E;
    --ty-color-warning-ink:    #9a5b00;
    --ty-color-warning-soft:   #fdf1dd;
    /* The pin: a personal default ("my schedule opens in Week") — pink so it reads as *mine*, distinct from every status colour. */
    --ty-color-pin:            #e0409a;
    /* The project chip: this job was booked as part of a multi-trade project. Copy-pasted as a bare
       #0F766E in all five job editors (WcJobEditor.razor.css etc — see the styling audit's finding #3);
       not repointed with the platform theme is deliberate, same as --ty-color-pin above, so "this belongs
       to a project" reads the same teal in Trusty and Graft. New/converted pages should reference this
       token (color-mix(in srgb, var(--ty-color-project) 12%, transparent) for the soft fill, 30% for the
       border) rather than hardcoding the hex again; the five job editors themselves are a second pass. */
    --ty-color-project:        #0F766E;
    --ty-color-warning-border: #f2d9a4;
    --ty-color-danger:         #e50000;
    --ty-color-danger-ink:     #b32121;
    --ty-color-danger-soft:    #fdecec;
    --ty-color-danger-border:  #f4d3cf;
    --ty-color-info:           #1457b0;
    --ty-color-info-ink:       #1457b0;
    --ty-color-info-soft:      #eaf2fd;
    --ty-color-info-border:    #cfe0f7;

    /* Scrim behind sheets / drawers / modals. Dark in every theme (it dims the page
       under the sheet), but each theme tunes it to its own surface contrast.
       -rgb is the bare triplet so a component that needs its own opacity can write
       rgba(var(--ty-color-overlay-rgb), .34) and STILL follow the theme - a raw
       rgba(20, 26, 33, .34) does not, and is far too weak over Graft's dark page. */
    --ty-color-overlay:     rgba(12, 18, 28, .55);
    --ty-color-overlay-rgb: 12, 18, 28;

    /* Shadow colour as a bare triplet, for the handful of drop-shadows with bespoke
       geometry (a FAB, a slide-over drawer, a dropdown) that cannot use --ty-shadow-sm/md/lg.
       Everything else should reach for the elevation tokens instead. */
    --ty-shadow-rgb: 12, 18, 28;

    /* Search-result highlight (the <mark> in document content search). Needs BOTH halves
       as tokens: a #fff1a8 highlight with `color: inherit` puts the dark theme's near-white
       body text on pale yellow at ~1.2:1 - unreadable. */
    --ty-color-highlight-bg: #fff1a8;
    --ty-color-highlight-fg: #1c2128;

    /* Third-party brand marks. The solid fill is the brand's own colour and must not drift;
       -ink is the readable-on-our-surface variant, which DOES change per theme (GOV.UK green
       on Graft's #16202b card is ~2.3:1 and fails WCAG AA). */
    --ty-brand-hmrc:          #00703c;
    --ty-brand-hmrc-contrast: #ffffff;
    --ty-brand-hmrc-ink:      #00703c;
    --ty-brand-hmrc-soft:     #e6f0ea;

    /* Tooltip / hint bubble. INVERTED against the page by design (a dark bubble on a light
       app, a light bubble on a dark one) - a hardcoded #1f2937 bubble sits on Graft's
       #0f1720 page with almost no edge, so the hint reads as a smudge rather than a popover. */
    --ty-color-tooltip-bg: #1f2937;
    --ty-color-tooltip-fg: #ffffff;

    /* --- Spacing --- */
    --ty-space-xs: 4px;  --ty-space-sm: 8px;  --ty-space-md: 12px;
    --ty-space-lg: 16px; --ty-space-xl: 24px; --ty-space-2xl: 32px; --ty-space-3xl: 48px;

    /* --- Radius --- */
    --ty-radius-control: 8px;
    --ty-radius-card:    12px;
    --ty-radius-panel:   16px;
    --ty-radius-pill:    999px;

    /* --- Typography --- */
    --ty-font-base: 'Helvetica Neue', Helvetica, Arial, sans-serif;
    --ty-font-mono: ui-monospace, Menlo, Consolas, monospace;
    --ty-font-xs: 12px; --ty-font-sm: 14px; --ty-font-base-size: 16px;
    --ty-font-lg: 20px; --ty-font-xl: 24px; --ty-font-2xl: 32px;
    --ty-weight-regular: 400; --ty-weight-medium: 500;
    --ty-weight-semibold: 600; --ty-weight-bold: 700;
    --ty-line-body: 1.5; --ty-line-heading: 1.25;
    --ty-tracking-tight: -0.02em; --ty-tracking-wide: 0.12em;

    /* --- Elevation --- */
    --ty-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
    --ty-shadow-md: 0 1px 3px rgba(0, 0, 0, 0.07), 0 12px 30px -18px rgba(0, 0, 0, 0.22);
    --ty-shadow-lg: 0 2px 4px rgba(0, 0, 0, 0.08), 0 30px 60px -30px rgba(0, 0, 0, 0.40);

    /* --- Motion --- */
    --ty-motion-micro: 120ms; --ty-motion-standard: 200ms;
    --ty-ease: cubic-bezier(0.2, 0, 0, 1);

    /* --- Focus --- */
    --ty-focus-ring: 0 0 0 0.2rem rgba(30, 111, 217, 0.35);

    /* --- Z-index --- */
    --ty-z-nav: 100; --ty-z-sticky: 200; --ty-z-dropdown: 300;
    --ty-z-modal: 400; --ty-z-toast: 500;

    /* --- Safe-area insets (notch / home indicator). Real values on iOS + Android
       under the MAUI BlazorWebView and installed PWAs, 0 everywhere else. Pages that
       draw their own full-bleed chrome pad against these, never env() directly, so a
       head can override them. --- */
    --ty-safe-top:    env(safe-area-inset-top, 0px);
    --ty-safe-bottom: env(safe-area-inset-bottom, 0px);
    --ty-safe-left:   env(safe-area-inset-left, 0px);
    --ty-safe-right:  env(safe-area-inset-right, 0px);

    /* --- Document Library, the manila-folder file manager (/design/ui section 4.4) --- */
    --ty-doc-accent:        #D98A1E;
    --ty-doc-folder-bg:     #faf1dc;
    --ty-doc-folder-border: #ecd9a9;
    --ty-doc-folder-name:   #1c2128;
    --ty-doc-folder-meta:   #796d4e; /* was #9a8b63 = 2.99:1 on the manila tile; now 4.54:1 */

    --ty-file-pdf-bg:     #fdeceb; --ty-file-pdf-fg:     #c23e3b; /* was #d64541 = 3.84:1; now 4.54:1 */
    --ty-file-doc-bg:     #eef4fd; --ty-file-doc-fg:     #1457b0;
    --ty-file-sheet-bg:   #e8f5ec; --ty-file-sheet-fg:   #1f7a38;
    --ty-file-video-bg:   #f0ecfb; --ty-file-video-fg:   #7658c9; /* was #7a5bd0 = 4.27:1; now 4.51:1 */
    --ty-file-image-bg:   #e4f4f4; --ty-file-image-fg:   #137a7a; /* was #178f8f = 3.46:1; now 4.53:1 */
    --ty-file-default-bg: #eef1f4; --ty-file-default-fg: #57606a;

    /* --- Access (/design/ui "Team & Access" spec) --- */
    --ty-seg-bg:     #eceef1;
    --ty-seg-border: transparent;
    --ty-seg-fg:     #57606a;
    --ty-seg-on-bg:  #ffffff;
    --ty-seg-on-fg:  #1c2128;
    --ty-seg-on-shadow: 0 1px 3px rgba(0, 0, 0, .14);

    --ty-status-active-bg:    #e6f6ea; --ty-status-active-fg:    #1a7f37;
    --ty-status-invited-bg:   #fdf1dd; --ty-status-invited-fg:   #9a5b00;
    --ty-status-suspended-bg: #fdecea; --ty-status-suspended-fg: #c0392b;

    --ty-matrix-on-bg:  #e6f6ea; --ty-matrix-on-fg:  #1e8f4e;
    --ty-matrix-off-bg: #f4f5f7; --ty-matrix-off-fg: #cbd2da;
    --ty-matrix-del-bg: #fdecea; --ty-matrix-del-fg: #c0392b;

    --ty-act-bg:     #f4f6f9; --ty-act-border:     #e4e8ee; --ty-act-fg:     #8a95a1;
    --ty-act-on-bg:  #eef8f1; --ty-act-on-border:  #a9dcb8; --ty-act-on-fg:  #1e8f4e;
    --ty-act-del-bg: #fdecea; --ty-act-del-border: #f4c9c4; --ty-act-del-fg: #c0392b;

    --ty-rolerow-sel-bg:     #ffffff;
    --ty-rolerow-sel-border: #cfe0f7;
    --ty-rolerow-sel-ring:   0 0 0 3px #eaf2fd, 0 6px 16px -12px rgba(30, 111, 217, .5);
    --ty-rolerail-on:        #1E6FD9;

    --ty-accent-1-bg: #eaf2fd; --ty-accent-1-fg: #1457b0;
    --ty-accent-2-bg: #f0ecfb; --ty-accent-2-fg: #6b46c1;
    --ty-accent-3-bg: #e4f4f4; --ty-accent-3-fg: #0f6b6b;
    --ty-accent-4-bg: #e8f5ec; --ty-accent-4-fg: #1f7a38;
    --ty-accent-5-bg: #fbf0db; --ty-accent-5-fg: #9a5b00;
    --ty-accent-6-bg: #f1f4f7; --ty-accent-6-fg: #57606a;
}

/* ----------------------------------------------------------------------------
   LAYER 2 - domain module themes, theme-agnostic floor. Each themes/*.css repeats
   these under its own [data-theme] scope with theme-tuned ink/soft values; this
   block is what makes a [data-module] surface still pick up its trade colour when
   the platform theme is "none".

   Primary-DERIVED tokens (ink / soft / strong / accent) must be RE-DECLARED in
   every [data-module] block, never left to a var() alias at :root - an inherited
   custom property is computed once on the element that declares it, so an alias
   declared on <html> would keep the root's blue inside a green gardening surface.
   ---------------------------------------------------------------------------- */
[data-module="window-cleaning"] {
    --ty-color-primary: var(--ty-domain-window-cleaning);
    --ty-color-primary-ink: #1457b0; --ty-color-primary-soft: #eef4fd;
    --ty-color-primary-strong: #1457b0; --ty-color-accent: #1457b0;
    --ty-color-primary-contrast: #ffffff; /* 4.85:1 on this trade's fill */
}
[data-module="gardening"] {
    --ty-color-primary: var(--ty-domain-gardening);
    --ty-color-primary-ink: #1f7a38; --ty-color-primary-soft: #e8f5ec;
    --ty-color-primary-strong: #1f7a38; --ty-color-accent: #1f7a38;
    --ty-color-primary-contrast: #0f1720; /* 5.26:1 on this trade's fill */
}
[data-module="scaffolding"] {
    --ty-color-primary: var(--ty-domain-scaffolding);
    --ty-color-primary-ink: #a5670f; --ty-color-primary-soft: #fbf0db;
    --ty-color-primary-strong: #a5670f; --ty-color-accent: #a5670f;
    --ty-color-primary-contrast: #0f1720; /* 6.53:1 on this trade's fill */
}
[data-module="electrician"] {
    --ty-color-primary: var(--ty-domain-electrician);
    --ty-color-primary-ink: #5b21b6; --ty-color-primary-soft: #f1ebfd;
    --ty-color-primary-strong: #5b21b6; --ty-color-accent: #5b21b6;
    --ty-color-primary-contrast: #ffffff; /* 5.70:1 on this trade's fill */
}
[data-module="plumber"] {
    --ty-color-primary: var(--ty-domain-plumber);
    --ty-color-primary-ink: #075e73; --ty-color-primary-soft: #e2f4f8;
    --ty-color-primary-strong: #075e73; --ty-color-accent: #075e73;
    --ty-color-primary-contrast: #0f1720; /* 4.90:1 on this trade's fill */
}
[data-module="solar-installer"] {
    --ty-color-primary: var(--ty-domain-solar-installer);
    --ty-color-primary-ink: #b4460b; --ty-color-primary-soft: #fdeee3;
    --ty-color-primary-strong: #b4460b; --ty-color-accent: #b4460b;
    --ty-color-primary-contrast: #0f1720; /* 5.07:1 on this trade's fill */
}
[data-module="inspections"] {
    --ty-color-primary: var(--ty-domain-inspections);
    --ty-color-primary-ink: #9d174d; --ty-color-primary-soft: #fce7f3;
    --ty-color-primary-strong: #9d174d; --ty-color-accent: #9d174d;
    --ty-color-primary-contrast: #ffffff; /* 6.1:1 on the rose fill */
}
[data-module="surveys"] {
    --ty-color-primary: var(--ty-domain-surveys);
    --ty-color-primary-ink: #0f5f59; --ty-color-primary-soft: #e6f4f1;
    --ty-color-primary-strong: #0f5f59; --ty-color-accent: #0f5f59;
    --ty-color-primary-contrast: #ffffff; /* 5.9:1 on the teal fill */
}

/* On tablet & phone the app shell must never scroll horizontally — it keeps the fixed footer nav bar at the
   viewport width (so all its items show) and stops wide chrome (top-row, headers, cards) from forcing a
   horizontal scroll. Wide inner content (tables, card strips) keeps its own overflow-x:auto and scrolls
   locally. Desktop is left untouched to avoid changing the sticky side-rail / top-row behaviour. */
@media (max-width: 1024.98px) {
    html, body { overflow-x: hidden; }
}

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

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;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* ----------------------------------------------------------------------------
   .ty-badge-quiet - the neutral count/label badge.

   Replaces Bootstrap's `bg-light text-dark` / `text-bg-light`, which are POLARITY-FIXED:
   Bootstrap 5.3's dark mode deliberately leaves --bs-light-rgb and --bs-dark-rgb alone
   (so `.bg-light` still means "literally light"), which paints a near-white pill with
   near-black text on Graft's slate cards. Token-driven, so it flips with the theme.
   Pair with Bootstrap's own `.badge` for the sizing/shape.
   ---------------------------------------------------------------------------- */
.badge.ty-badge-quiet {
    background-color: var(--ty-color-surface-sunken, #f3f5f8);
    color: var(--ty-color-text-muted, #57606a);
}

/* ============================================================================
   TradeYoke loaders — a reusable, branded set of loading indicators + skeletons.
   Pure CSS (no images), theme-aware via the --ty-* tokens, and defined here in the
   globally-loaded app.css so EVERY layer can use them by class name (like Bootstrap's
   spinner-border) — the shell, capability modules AND domain RCLs, none of which can
   share a Razor component but all load this stylesheet. Colour follows --ty-color-primary,
   so the yoke turns window-cleaning blue / gardening green under a [data-module] surface.

   Primary spinner (the logo's two rings + bar, rotating with a gentle pulse):
     <span class="ty-spinner" role="status" aria-label="Loading">
       <span class="ty-spinner__bar"></span>
       <span class="ty-spinner__ring ty-spinner__ring--l"></span>
       <span class="ty-spinner__ring ty-spinner__ring--r"></span>
     </span>
   Sizes: ty-spinner--xs (inline/buttons) · ty-spinner--sm · (default) · ty-spinner--lg.
   Centred section loader:  <div class="ty-load"><span class="ty-spinner">…</span><span class="ty-load__cap">Loading…</span></div>
   Blocking overlay:        wrap content in position:relative and add <div class="ty-loadveil">…</div>
   Skeletons:               <span class="ty-sk" style="width:60%;height:11px"></span>  (see ty-sk--circle / ty-skrow)
   ============================================================================ */

.ty-spinner {
    --ty-spin-sz: 3.5rem;
    position: relative;
    display: inline-block;
    width: var(--ty-spin-sz);
    height: var(--ty-spin-sz);
    color: var(--ty-color-primary, #1e6fd9);
    flex: none;
    animation: ty-spin 1.4s cubic-bezier(.65, 0, .35, 1) infinite;
}
.ty-spinner--xs { --ty-spin-sz: 1.15rem; }
.ty-spinner--sm { --ty-spin-sz: 2.25rem; }
.ty-spinner--lg { --ty-spin-sz: 4.75rem; }

.ty-spinner__bar {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 74%;
    height: calc(var(--ty-spin-sz) * .12);
    min-height: 2px;
    border-radius: 99px;
    background: currentColor;
    opacity: .9;
}
.ty-spinner__ring {
    position: absolute;
    top: 50%;
    width: calc(var(--ty-spin-sz) * .43);
    height: calc(var(--ty-spin-sz) * .43);
    border: calc(var(--ty-spin-sz) * .1) solid currentColor;
    border-radius: 999px;
    transform: translateY(-50%) scale(.82);
}
.ty-spinner__ring--l { left: 0; animation: ty-spin-pulse 1.4s ease-in-out infinite; }
.ty-spinner__ring--r { right: 0; animation: ty-spin-pulse 1.4s ease-in-out infinite .7s; }

@keyframes ty-spin { to { transform: rotate(360deg); } }
@keyframes ty-spin-pulse {
    0%, 100% { transform: translateY(-50%) scale(.82); opacity: .55; }
    50% { transform: translateY(-50%) scale(1.06); opacity: 1; }
}

/* Centred section loader (spinner + optional caption). */
.ty-load {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .9rem;
    padding: 2.5rem 1rem;
    text-align: center;
}
.ty-load__cap { font-size: .8rem; color: var(--ty-color-text-subtle, #8a95a1); }
.ty-load__title { font-size: .85rem; font-weight: 600; color: var(--ty-color-text, #1c2128); }

/* Blocking overlay for a first-paint / refresh — sits over the (usually skeleton) content. */
.ty-loadveil {
    position: absolute;
    inset: 0;
    z-index: 5;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .85rem;
    background: var(--ty-color-surface, #fff);
    background: color-mix(in srgb, var(--ty-color-surface, #fff) 80%, transparent);
    backdrop-filter: blur(2px);
}

/* Skeleton placeholder — shape it inline (width/height) to mirror the content that's coming. */
.ty-sk {
    display: block;
    border-radius: 7px;
    background: linear-gradient(90deg,
        var(--ty-color-surface-3, #eef1f4) 25%,
        var(--ty-color-surface-2, #e2e7ec) 37%,
        var(--ty-color-surface-3, #eef1f4) 63%);
    background-size: 400% 100%;
    animation: ty-sk-shimmer 1.3s ease-in-out infinite;
}
.ty-sk--circle { border-radius: 999px; }
.ty-sk--pill { border-radius: 999px; }
.ty-skrow {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .75rem .9rem;
    border-bottom: 1px solid var(--ty-color-border, #f2f4f7);
}
.ty-skrow:last-child { border-bottom: none; }

@keyframes ty-sk-shimmer {
    0% { background-position: 100% 0; }
    100% { background-position: -100% 0; }
}

/* Respect users who've asked for less motion — hold a calm static state. */
@media (prefers-reduced-motion: reduce) {
    .ty-spinner,
    .ty-spinner__ring--l,
    .ty-spinner__ring--r { animation: none; }
    .ty-spinner__ring { transform: translateY(-50%) scale(1); opacity: .85; }
    .ty-sk { animation: none; }
}

/* ── Anonymous auth pages (registration, email confirmation) ─────────────────────────────────────────
   One calm, chrome-free column on the empty layout: brand mark, heading, a single reassurance line, then
   the controls. Deliberately NOT the admin-form look — this is the first screen anyone sees of the
   product. Global rather than scoped CSS because /signup and /verify share it, so the journey never
   changes visual language mid-flow. Everything is a --ty-* semantic token, so the platform themes and
   their dark surfaces come for free. */

.ty-auth {
    width: 100%;
    max-width: 25rem;
    margin: 0 auto;
    padding: clamp(var(--ty-space-2xl, 32px), 8vh, 5rem) var(--ty-space-lg, 16px) var(--ty-space-3xl, 48px);
}

.ty-auth__mark {
    display: block;
    width: 2.75rem;
    height: 2.75rem;
    margin: 0 auto var(--ty-space-xl, 24px);
    border-radius: var(--ty-radius-card, 12px);
}

/* Outcome screens (check your email, link expired) lead with a state icon instead of the brand mark. */
.ty-auth__icon {
    width: 3.25rem;
    height: 3.25rem;
    margin: 0 auto var(--ty-space-lg, 16px);
    display: grid;
    place-items: center;
    border-radius: var(--ty-radius-pill, 999px);
    font-size: 1.5rem;
    background: var(--ty-color-primary-soft, #eaf2fd);
    color: var(--ty-color-primary, #1E6FD9);
}

.ty-auth__icon--good {
    background: var(--ty-color-success-soft, #e6f6ea);
    color: var(--ty-color-success-ink, #1a7f37);
}

.ty-auth__icon--warn {
    background: var(--ty-color-warning-soft, #fdf1dd);
    color: var(--ty-color-warning-ink, #9a5b00);
}

.ty-auth__title {
    margin: 0 0 var(--ty-space-sm, 8px);
    font-size: clamp(1.6rem, 1.2rem + 1.6vw, var(--ty-font-2xl, 32px));
    font-weight: var(--ty-weight-semibold, 600);
    line-height: var(--ty-line-heading, 1.25);
    letter-spacing: var(--ty-tracking-tight, -.02em);
    color: var(--ty-color-text, #1c2128);
    text-align: center;
    text-wrap: balance;
}

.ty-auth__lede {
    margin: 0 0 var(--ty-space-xl, 24px);
    color: var(--ty-color-text-muted, #57606a);
    line-height: var(--ty-line-body, 1.5);
    text-align: center;
    text-wrap: pretty;
}

.ty-auth__step {
    margin: 0 0 var(--ty-space-sm, 8px);
    font-size: var(--ty-font-xs, 12px);
    font-weight: var(--ty-weight-semibold, 600);
    letter-spacing: var(--ty-tracking-wide, .12em);
    text-transform: uppercase;
    text-align: center;
    color: var(--ty-color-text-subtle, #8a95a1);
}

/* Federated buttons: full width, quiet surface, the provider mark pinned left with the label optically
   centred — the shape a registration page is expected to have.

   NOTE the .ty-auth ancestor on every rule below that colours an ANCHOR. The platform themes carry
   `[data-theme="…"] a { color: var(--ty-color-primary) }`, which is specificity (0,1,1) and would beat a
   lone class — an anchor styled as a button then renders its label in the same blue as its own
   background, i.e. invisible. Two classes (0,2,0) wins without resorting to !important. */
.ty-auth .ty-auth__provider {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--ty-space-sm, 8px);
    width: 100%;
    min-height: 3rem;
    margin-bottom: var(--ty-space-sm, 8px);
    padding: 0 var(--ty-space-xl, 24px);
    border: 1px solid var(--ty-color-border-strong, #d0d7de);
    border-radius: var(--ty-radius-control, 8px);
    background: var(--ty-color-surface, #fff);
    color: var(--ty-color-text, #1c2128);
    font-weight: var(--ty-weight-medium, 500);
    text-decoration: none;
    transition: background var(--ty-motion-micro, 120ms) var(--ty-ease, ease),
                border-color var(--ty-motion-micro, 120ms) var(--ty-ease, ease);
}

.ty-auth .ty-auth__provider:hover {
    background: var(--ty-color-surface-2, #f6f8fa);
    border-color: var(--ty-color-text-subtle, #8a95a1);
    color: var(--ty-color-text, #1c2128);
}

.ty-auth .ty-auth__provider i {
    position: absolute;
    left: var(--ty-space-lg, 16px);
    font-size: 1.125rem;
}

/* "That provider isn't wired here." Information, not failure — the email door beside it still works, so
   this reads quieter than .ty-auth__alert and carries no red. */
.ty-auth__notice {
    display: flex;
    gap: var(--ty-space-sm, 8px);
    margin: var(--ty-space-sm, 8px) 0 0;
    padding: var(--ty-space-md, 12px) var(--ty-space-lg, 16px);
    border-radius: var(--ty-radius-card, 12px);
    background: var(--ty-color-surface-2, #f6f8fa);
    color: var(--ty-color-text-muted, #57606a);
    font-size: var(--ty-font-sm, 14px);
    line-height: var(--ty-line-body, 1.5);
    text-wrap: pretty;
}

.ty-auth__divider {
    display: flex;
    align-items: center;
    gap: var(--ty-space-md, 12px);
    margin: var(--ty-space-xl, 24px) 0;
    color: var(--ty-color-text-subtle, #8a95a1);
    font-size: var(--ty-font-sm, 14px);
}

.ty-auth__divider::before,
.ty-auth__divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--ty-color-border, #e7ebf0);
}

.ty-auth__field { margin-bottom: var(--ty-space-lg, 16px); }

.ty-auth__label {
    display: block;
    margin-bottom: var(--ty-space-xs, 4px);
    font-size: var(--ty-font-sm, 14px);
    font-weight: var(--ty-weight-medium, 500);
    color: var(--ty-color-text, #1c2128);
}

.ty-auth__input {
    display: block;
    width: 100%;
    min-height: 3rem;
    padding: var(--ty-space-sm, 8px) var(--ty-space-md, 12px);
    border: 1px solid var(--ty-color-border-strong, #d0d7de);
    border-radius: var(--ty-radius-control, 8px);
    background: var(--ty-color-surface, #fff);
    color: var(--ty-color-text, #1c2128);
    font-family: inherit;
    /* 16px floor: anything smaller and iOS Safari zooms the page on focus. */
    font-size: var(--ty-font-base-size, 16px);
    line-height: var(--ty-line-body, 1.5);
}

.ty-auth__input:focus {
    outline: 2px solid transparent;
    border-color: var(--ty-color-primary, #1E6FD9);
    box-shadow: var(--ty-focus-ring, 0 0 0 .2rem rgba(30, 111, 217, .35));
}

/* Blazor stamps `valid modified` on every touched EditForm input, and the template rule above paints
   that a green outline. On a page this plain it reads as an alert about nothing — the field being fine
   is the default, not news. Errors still speak, through .is-invalid below.
   The :not() mirrors the rule being neutralised because BOTH themes restate it under their own
   [data-theme] prefix, which ties this on class count and then wins on load order — matching their
   shape puts this one specificity point ahead of both. */
.ty-auth .ty-auth__input.valid.modified:not([type=checkbox]) { outline: none; }

.ty-auth__input[readonly] {
    background: var(--ty-color-surface-2, #f6f8fa);
    color: var(--ty-color-text-muted, #57606a);
}

/* Same is-invalid class name the rest of the app's forms use, so the shared FieldValidationState UX
   holds here too — restyled to this page's language rather than Bootstrap's. */
.ty-auth__input.is-invalid,
.ty-auth__input.is-invalid:focus {
    border-color: var(--ty-color-danger, #e50000);
}

.ty-auth__input.is-invalid:focus {
    box-shadow: 0 0 0 .2rem var(--ty-color-danger-soft, #fdecec);
}

.ty-auth__hint {
    margin: var(--ty-space-xs, 4px) 0 0;
    font-size: var(--ty-font-sm, 14px);
    color: var(--ty-color-text-muted, #57606a);
}

/* The consent tick: a quiet footnote-sized row, but a real gate — see .ty-auth__checkbox.is-invalid. */
.ty-auth__check {
    display: flex;
    align-items: flex-start;
    gap: var(--ty-space-sm, 8px);
    font-size: var(--ty-font-sm, 14px);
    line-height: var(--ty-line-body, 1.5);
    color: var(--ty-color-text-muted, #57606a);
    cursor: pointer;
    text-wrap: pretty;
}

.ty-auth .ty-auth__checkbox {
    flex: none;
    width: 1.15rem;
    height: 1.15rem;
    margin-top: .15rem;                       /* optically level with the first line of text */
    accent-color: var(--ty-color-primary, #1E6FD9);
    cursor: pointer;
}

/* A checkbox has no border of its own to redden, so the invalid state is an outline — same is-invalid
   marker as the text fields, and the same specificity shape as the theme rule it has to outrank. */
.ty-auth .ty-auth__checkbox.is-invalid:not([type=text]) {
    outline: 2px solid var(--ty-color-danger, #e50000);
    outline-offset: 2px;
}

.ty-auth__error {
    margin: var(--ty-space-xs, 4px) 0 0;
    font-size: var(--ty-font-sm, 14px);
    font-weight: var(--ty-weight-medium, 500);
    color: var(--ty-color-danger-ink, #b32121);
}

.ty-auth__alert {
    margin: 0 0 var(--ty-space-lg, 16px);
    padding: var(--ty-space-md, 12px) var(--ty-space-lg, 16px);
    border: 1px solid var(--ty-color-danger-border, #f4d3cf);
    border-radius: var(--ty-radius-card, 12px);
    background: var(--ty-color-danger-soft, #fdecec);
    color: var(--ty-color-danger-ink, #b32121);
    line-height: var(--ty-line-body, 1.5);
}

.ty-auth .ty-auth__submit {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--ty-space-sm, 8px);
    width: 100%;
    min-height: 3rem;
    margin-top: var(--ty-space-xl, 24px);
    padding: 0 var(--ty-space-lg, 16px);
    border: 1px solid transparent;
    border-radius: var(--ty-radius-control, 8px);
    background: var(--ty-color-primary, #1E6FD9);
    color: var(--ty-color-primary-contrast, #fff);
    font-family: inherit;
    font-size: var(--ty-font-base-size, 16px);
    font-weight: var(--ty-weight-semibold, 600);
    text-decoration: none;
    cursor: pointer;
    transition: background var(--ty-motion-micro, 120ms) var(--ty-ease, ease);
}

.ty-auth .ty-auth__submit:hover:not(:disabled) {
    background: var(--ty-color-primary-strong, #1457b0);
    color: var(--ty-color-primary-contrast, #fff);
}

.ty-auth .ty-auth__submit:disabled { opacity: .65; cursor: default; }

.ty-auth .ty-auth__submit--quiet {
    background: var(--ty-color-surface, #fff);
    border-color: var(--ty-color-border-strong, #d0d7de);
    color: var(--ty-color-text, #1c2128);
}

.ty-auth .ty-auth__submit--quiet:hover:not(:disabled) {
    background: var(--ty-color-surface-2, #f6f8fa);
    color: var(--ty-color-text, #1c2128);
}

.ty-auth .ty-auth__provider:focus-visible,
.ty-auth .ty-auth__submit:focus-visible,
.ty-auth .ty-auth__trade:focus-visible {
    outline: 2px solid transparent;
    box-shadow: var(--ty-focus-ring, 0 0 0 .2rem rgba(30, 111, 217, .35));
}

.ty-auth__foot {
    margin: var(--ty-space-xl, 24px) 0 0;
    text-align: center;
    color: var(--ty-color-text-muted, #57606a);
    font-size: var(--ty-font-sm, 14px);
}

.ty-auth__back {
    display: inline-flex;
    align-items: center;
    gap: var(--ty-space-xs, 4px);
    margin-top: var(--ty-space-lg, 16px);
    padding: 0;
    border: 0;
    background: none;
    color: var(--ty-color-text-muted, #57606a);
    font: inherit;
    font-size: var(--ty-font-sm, 14px);
    cursor: pointer;
}

.ty-auth__back:hover { color: var(--ty-color-text, #1c2128); }

/* Trade picker — selectable tiles in each trade's own colour rather than a column of checkboxes. An
   incompatible combination stays visible but unavailable, so the rule can be seen rather than guessed. */
.ty-auth__trades {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
    gap: var(--ty-space-sm, 8px);
    margin: 0;
    padding: 0;
    border: 0;
}

.ty-auth__trade {
    display: flex;
    align-items: center;
    gap: var(--ty-space-sm, 8px);
    min-height: 3rem;
    padding: var(--ty-space-sm, 8px) var(--ty-space-md, 12px);
    border: 1px solid var(--ty-color-border-strong, #d0d7de);
    border-radius: var(--ty-radius-control, 8px);
    background: var(--ty-color-surface, #fff);
    color: var(--ty-color-text, #1c2128);
    font: inherit;
    font-size: var(--ty-font-sm, 14px);
    font-weight: var(--ty-weight-medium, 500);
    text-align: left;
    cursor: pointer;
    transition: border-color var(--ty-motion-micro, 120ms) var(--ty-ease, ease),
                background var(--ty-motion-micro, 120ms) var(--ty-ease, ease);
}

.ty-auth__trade i { font-size: 1.125rem; color: var(--d, var(--ty-color-primary, #1E6FD9)); }

.ty-auth__trade:hover:not(:disabled) { border-color: var(--d, var(--ty-color-primary, #1E6FD9)); }

.ty-auth__trade[aria-pressed="true"] {
    border-color: var(--d, var(--ty-color-primary, #1E6FD9));
    box-shadow: inset 0 0 0 1px var(--d, var(--ty-color-primary, #1E6FD9));
    background: color-mix(in srgb, var(--d, #1E6FD9) 8%, var(--ty-color-surface, #fff));
}

.ty-auth__trade:disabled { opacity: .5; cursor: not-allowed; }

.ty-auth__trade-check { margin-left: auto; color: var(--d, var(--ty-color-primary, #1E6FD9)); }

/* The address a link went to: the one thing on the screen worth re-reading, so it wraps instead of
   overflowing a phone. */
.ty-auth__address {
    display: block;
    margin: 0 0 var(--ty-space-lg, 16px);
    font-weight: var(--ty-weight-semibold, 600);
    color: var(--ty-color-text, #1c2128);
    text-align: center;
    overflow-wrap: anywhere;
}

/* ── Segmented control (.ty-seg) — the app-wide Day/Week/Month, Planned/Best, 1/2/3 switch ──────────────
   One pill, one moving highlight. Themed through the --ty-seg-* tokens (declared in the floor + both
   themes), so a dark theme gets a dark trough and a primary-coloured selection for free. Variants:
   .ty-seg--views (the primary switch on a page: taller, bolder), .ty-seg--sub (a qualifier: smaller,
   quieter), .ty-seg--pin (a personal default: the selection is pink, like the pin it belongs to). */
.ty-seg {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 4px;
    border-radius: 999px;
    background: var(--ty-seg-bg, #eceef1);
    border: 1px solid var(--ty-seg-border, transparent);
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .06);
}
.ty-seg-item, .ty-seg__opt {
    appearance: none;
    border: 0;
    background: transparent;
    font: 700 13px var(--ty-font-base, inherit);
    letter-spacing: -0.005em;
    color: var(--ty-seg-fg, #57606a);
    padding: 7px 16px;
    min-height: 32px;
    border-radius: 999px;
    cursor: pointer;
    white-space: nowrap;
    transition: background .15s, color .15s, box-shadow .15s, transform .18s cubic-bezier(.34, 1.56, .64, 1);
}
.ty-seg-item:hover:not(.on):not(:disabled), .ty-seg__opt:hover:not(.on):not(:disabled) { color: var(--ty-color-text, #1c2128); background: rgba(255, 255, 255, .55); }
.ty-seg-item:active:not(:disabled), .ty-seg__opt:active:not(:disabled) { transform: scale(.97); }
.ty-seg-item:focus-visible, .ty-seg__opt:focus-visible { outline: 2px solid var(--ty-color-primary, #1b6ec2); outline-offset: 2px; }
.ty-seg-item:disabled, .ty-seg__opt:disabled { cursor: default; opacity: .6; }
.ty-seg-item.on, .ty-seg__opt.on {
    background: var(--ty-seg-on-bg, #fff);
    color: var(--ty-seg-on-fg, #1c2128);
    box-shadow: var(--ty-seg-on-shadow, 0 1px 3px rgba(0, 0, 0, .14));
}
/* The primary switch on a page — bigger type, a comfortable thumb target, primary-coloured selection. */
.ty-seg--views { padding: 5px; }
.ty-seg--views .ty-seg-item, .ty-seg--views .ty-seg__opt { font-size: 14.5px; font-weight: 800; padding: 9px 20px; min-height: 40px; }
.ty-seg--views .ty-seg-item.on, .ty-seg--views .ty-seg__opt.on { background: var(--ty-color-primary, #1b6ec2); color: var(--ty-color-primary-contrast, #fff); box-shadow: 0 6px 16px -8px var(--ty-color-primary, #1b6ec2); }
/* A qualifier beside the primary switch — smaller and quieter. */
.ty-seg--sub { padding: 3px; }
.ty-seg--sub .ty-seg-item, .ty-seg--sub .ty-seg__opt { font-size: 12.5px; padding: 5px 12px; min-height: 28px; }
.ty-seg--sub .ty-seg-item.on, .ty-seg--sub .ty-seg__opt.on { color: var(--ty-color-primary, #1b6ec2); }
/* A personal default — the pin's pink. */
.ty-seg--pin .ty-seg-item.on, .ty-seg--pin .ty-seg__opt.on { background: var(--ty-color-pin, #e0409a); color: #fff; box-shadow: 0 6px 16px -8px var(--ty-color-pin, #e0409a); }

/* The round pin button beside a view/mode switch ("make this my default" — SchedulePage, PlanningV2Page,
   the job wizard/full-form switch). Kept here (rather than only in scheduling.css, where the rule
   originated) because app.css is the one stylesheet every page and every hosted panel is guaranteed to
   have loaded — a page that hosts the pin as a CHILD component (e.g. JobDetailPage inside the jobs list's
   panel) can't rely on a HeadContent link from a shallower host surviving the render churn a nested
   component goes through. The scheduling.css copy is left as-is for the pages that already reference it. */
.ty-pin { appearance: none; display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; margin-left: 6px; border-radius: 999px; border: 1.5px solid var(--ty-color-border-strong, #c2cad3); background: var(--ty-color-surface, #fff); color: var(--ty-color-text-subtle, #8a95a1); font-size: 15px; cursor: pointer; transition: transform .18s cubic-bezier(.34, 1.56, .64, 1), color .15s, border-color .15s, background .15s; vertical-align: middle; }
.ty-pin:hover { color: var(--ty-color-pin, #e0409a); border-color: var(--ty-color-pin, #e0409a); transform: rotate(-20deg) scale(1.08); }
.ty-pin.on { color: #fff; background: var(--ty-color-pin, #e0409a); border-color: var(--ty-color-pin, #e0409a); box-shadow: 0 6px 16px -8px var(--ty-color-pin, #e0409a); }
.ty-pin.on:hover { transform: scale(1.06); }

/* ============================================================================
   The shared "playful" kit — cards, chips, avatars, fields, controls, tables,
   buttons. (2026-09-06 styling-consistency pass.)
   ----------------------------------------------------------------------------
   Before this, the window-cleaning/gardening look (cards, icon chips, avatars,
   segmented toggles, switches, priced rails) was re-declared per component under
   a dozen private prefixes (wc-/wcj-, gd-/gdj-, sf-, vd-, pj-, pay-, bk-…) — real
   in each one, but copy-pasted 14+ times because Blazor's scoped CSS can't cross
   components, and there was no shared class to reach for instead. This block is
   that shared vocabulary, promoted once to the globally-loaded app.css (every
   head links this file, so it reaches Web/WASM/MAUI alike) so a page written
   against `.ty-card`/`.ty-field`/`.ty-btn`/… gets the same look everywhere
   without a component import — the recipe is lifted verbatim from the reference
   file, `src/domains/TradeYoke.Domains.WindowCleaning/Jobs/WcJobEditor.razor.css`.
   Reconciliation notes (do not "clean up" these without re-reading them):
     - .ty-btn / .ty-field already existed, but only inside the non-scoped
       `access.css` (linked from just the Access page) — moved here verbatim so
       the SAME classes work everywhere; access.css no longer redeclares them.
       Every spelling already in use elsewhere keeps working: the BEM modifiers
       (`--primary`/`--danger`/`--sm`) from access.css AND the single-dash ones
       (`ty-btn-primary`, `ty-btn-ghost`, `ty-btn-quiet`) this same file's own
       phone tap-target media query below has referenced since 2026-08 without
       either ever being defined — both resolve to one rule set now.
     - `.ty-chip` (a filter/trade toggle) and `.ty-pill` + tone-* (a status tag)
       already exist as their own global vocabulary in the non-scoped
       `scheduling.css` (five job-detail panels + the schedule board). `.ty-chip`
       is left untouched; `.ty-pill` + tone-* is re-declared here byte-for-byte
       (so the same class works on pages that never load scheduling.css) without
       adding anything scheduling.css doesn't already have — `.ty-icochip` (the
       wc-card recipe's 30px icon square, unrelated to either) and `.ty-pill--tap`
       (an interactive/toggle pill, the "wc-pill" recipe) are new names chosen so
       nothing here gives the status pill a border or pointer cursor it never
       asked for.
     - `.ty-seg` / `.ty-seg-item` already exist (the Day/Week/Month switch);
       `.ty-seg__opt` is added as an alias throughout so the BEM spelling used
       in the wc-/gd- job editors' own `.wcj-seg`/`.wcj-segopt` also resolves.
   ============================================================================ */

/* --- Card: the one visual unit for "a themed block of content". --- */
.ty-card {
    background: var(--ty-color-surface, #fff);
    border: 1px solid var(--ty-color-border, #e7ebf0);
    border-radius: var(--ty-radius-card, 12px);
    box-shadow: var(--ty-shadow-sm, 0 1px 2px rgba(12, 18, 28, .04));
    overflow: hidden;
}
.ty-card__hd {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 13px 16px;
    border-bottom: 1px solid var(--ty-color-border, #f0f2f5);
}
.ty-card__title { font-weight: 700; font-size: .9rem; color: var(--ty-color-text, #1c2128); }
.ty-card__sub { font-size: .75rem; color: var(--ty-color-text-subtle, #98a2ad); }
.ty-card__body { padding: 16px; }
.ty-card__foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    flex-wrap: wrap;
    padding: 12px 16px;
    border-top: 1px solid var(--ty-color-border, #f0f2f5);
    background: var(--ty-color-surface-2, #f6f8fa);
}

/* --- Icon chip: the 30px rounded-square icon badge in every card header. Named .ty-icochip (not
   .ty-chip) because .ty-chip is already the filter/trade toggle pill in scheduling.css. --- */
.ty-icochip {
    width: 30px; height: 30px; flex: none;
    border-radius: 9px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--ty-color-primary-soft, #eaf2fd);
    color: var(--ty-color-primary, #1E6FD9);
    font-size: 14px;
}
.ty-icochip--amber   { background: var(--ty-color-warning-soft, #fdf1dd); color: var(--ty-color-warning-ink, #9a5b00); }
.ty-icochip--success { background: var(--ty-color-success-soft, #e6f6ea); color: var(--ty-color-success-ink, #1a7f37); }
.ty-icochip--danger  { background: var(--ty-color-danger-soft, #fdecec);  color: var(--ty-color-danger-ink, #b32121); }
.ty-icochip--sm { width: 24px; height: 24px; border-radius: 7px; font-size: 12px; }

/* --- Avatar: the 44px coloured-square initials badge (customer/team rows, card headers). Distinct from
   the round 34px `.ty-avatar` (access.css — hashed accent colour per person, Team/Users lists). --- */
.ty-ava {
    width: 44px; height: 44px; flex: none;
    border-radius: 12px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--ty-color-primary, #1E6FD9);
    color: var(--ty-color-primary-contrast, #fff);
    font-weight: 700; font-size: .9rem;
}
.ty-ava--sm { width: 34px; height: 34px; border-radius: 10px; font-size: .78rem; }

/* --- Fields: label + input, one visual language for every form on every page. Promoted from access.css
   (see the reconciliation note above) — .ty-select / .ty-textarea are new, sharing the same input shape. --- */
.ty-field { display: flex; flex-direction: column; gap: 4px; }

.ty-field__label {
    font: 600 11px var(--ty-font-base, inherit);
    color: var(--ty-color-text-muted, #57606a);
    text-transform: uppercase;
    letter-spacing: .04em;
}

.ty-field--full { grid-column: 1 / -1; }

.ty-field__input,
.ty-select,
.ty-textarea {
    display: block;
    width: 100%;
    font: 400 13.5px var(--ty-font-base, inherit);
    color: var(--ty-color-text, #1c2128);
    background: var(--ty-color-surface, #fff);
    border: 1px solid var(--ty-color-border-strong, #d0d7de);
    border-radius: var(--ty-radius-control, 8px);
    padding: 9px 12px;
}
.ty-field__input:focus, .ty-field__input:focus-visible,
.ty-select:focus, .ty-select:focus-visible,
.ty-textarea:focus, .ty-textarea:focus-visible {
    outline: none;
    border-color: var(--ty-color-primary, #1b6ec2);
    box-shadow: var(--ty-focus-ring, 0 0 0 .2rem rgba(30, 111, 217, .35));
}
/* Invalid field — red border, matching the Bootstrap is-invalid look used on the other forms (the
   form-validation skill's "is-invalid + invalid-feedback" contract holds for these controls too). */
.ty-field__input.is-invalid, .ty-select.is-invalid, .ty-textarea.is-invalid {
    border-color: var(--ty-color-danger, #dc3545);
}
.ty-field__input.is-invalid:focus, .ty-select.is-invalid:focus, .ty-textarea.is-invalid:focus,
.ty-field__input.is-invalid:focus-visible, .ty-select.is-invalid:focus-visible, .ty-textarea.is-invalid:focus-visible {
    border-color: var(--ty-color-danger, #dc3545);
    box-shadow: 0 0 0 .2rem color-mix(in srgb, var(--ty-color-danger, #e50000) 30%, transparent);
}
.ty-field__input[readonly], .ty-field__input:disabled,
.ty-select:disabled, .ty-textarea[readonly], .ty-textarea:disabled {
    background: var(--ty-color-surface-2, #f6f8fa);
    color: var(--ty-color-text-muted, #57606a);
}

/* A native <select> loses its own chevron once themed (appearance:none), so one is drawn back on as a
   background-image. Two colours rather than one var() because an SVG data-uri is a static image — it
   cannot read a CSS custom property — so Graft's chevron is repointed the same way the rest of this file
   repoints icon svgs for the dark theme (see docs/UI-Design.html; no existing precedent used currentColor
   here, so this follows the closest analogue: a straight light/dark swap keyed off [data-theme]). */
.ty-select {
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 6L8 10.5 12.5 6' fill='none' stroke='%238a95a1' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    background-size: 13px;
    padding-right: 32px;
}
[data-theme="graft"] .ty-select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 6L8 10.5 12.5 6' fill='none' stroke='%23b9c2cc' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.ty-textarea { min-height: 5.5rem; resize: vertical; font-family: inherit; }

/* --- Card-styled table: rounded container, soft header, hover rows (lifted from the one place this
   already existed, UsersTab.razor.css's scoped `.ty-table` — repeated here, byte-for-byte, so it also
   works unscoped on every list still using a raw Bootstrap `<table class="table">`; wrap the table in
   `.ty-table-wrap` for the scroll container + border, same as `.ty-users__tablewrap` did). --- */
.ty-table-wrap {
    overflow-x: auto;
    background: var(--ty-color-surface, #fff);
    border: 1px solid var(--ty-color-border, #e7ebf0);
    border-radius: var(--ty-radius-card, 12px);
}
.ty-table { width: 100%; border-collapse: collapse; }
.ty-table th {
    text-align: left;
    font: 700 10px var(--ty-font-base, inherit);
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--ty-color-text-subtle, #8a95a1);
    padding: 14px 16px 11px;
}
.ty-table td {
    padding: 14px 16px;
    border-top: 1px solid var(--ty-color-border, #e7ebf0);
    vertical-align: middle;
    font-size: 13.5px;
    color: var(--ty-color-text, #1c2128);
}
.ty-table tbody tr:hover td { background: var(--ty-color-surface-2, #f6f8fa); }
.ty-table__actions { display: flex; gap: 6px; flex-wrap: wrap; white-space: nowrap; }

/* --- Buttons (.ty-btn) — see the reconciliation note at the top of this section. Both spellings of every
   modifier resolve to the same rule, so existing markup using either one keeps working unchanged. --- */
.ty-btn,
.ty-btn:link,
.ty-btn:visited {
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    font: 600 12.5px var(--ty-font-base, inherit);
    color: var(--ty-color-text, #1c2128);
    text-decoration: none;
}
.ty-btn {
    background: var(--ty-color-surface, #fff);
    border: 1px solid var(--ty-color-border-strong, #d0d7de);
    padding: 8px 14px;
    border-radius: var(--ty-radius-control, 8px);
    cursor: pointer;
    transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease, box-shadow 120ms ease, transform 120ms ease;
}
.ty-btn:hover:not(:disabled) { background: var(--ty-color-surface-3, #eef1f4); }
.ty-btn:active:not(:disabled) { transform: scale(.98); }
.ty-btn:focus-visible { outline: none; box-shadow: var(--ty-focus-ring, 0 0 0 .2rem rgba(30, 111, 217, .35)); }
.ty-btn:disabled { opacity: .55; cursor: not-allowed; }

.ty-btn--primary, .ty-btn-primary,
.ty-btn--primary:link, .ty-btn-primary:link,
.ty-btn--primary:visited, .ty-btn-primary:visited {
    color: var(--ty-color-primary-contrast, #fff);
}
.ty-btn--primary, .ty-btn-primary {
    background: var(--ty-color-primary, #1b6ec2);
    border-color: var(--ty-color-primary, #1b6ec2);
}
.ty-btn--primary:hover:not(:disabled), .ty-btn-primary:hover:not(:disabled) {
    background: var(--ty-color-primary-ink, #1457b0);
    border-color: var(--ty-color-primary-ink, #1457b0);
}

.ty-btn--ghost, .ty-btn-ghost {
    background: transparent;
    border-color: var(--ty-color-border-strong, #d0d7de);
}
.ty-btn--ghost:hover:not(:disabled), .ty-btn-ghost:hover:not(:disabled) {
    background: var(--ty-color-surface-3, #eef1f4);
}

.ty-btn--quiet, .ty-btn-quiet {
    background: transparent;
    border-color: transparent;
    color: var(--ty-color-text-muted, #57606a);
}
.ty-btn--quiet:hover:not(:disabled), .ty-btn-quiet:hover:not(:disabled) {
    color: var(--ty-color-text, #1c2128);
    background: var(--ty-color-surface-3, #eef1f4);
}

.ty-btn--danger, .ty-btn-danger,
.ty-btn--danger:link, .ty-btn-danger:link,
.ty-btn--danger:visited, .ty-btn-danger:visited {
    color: var(--ty-color-danger, #e50000);
}
.ty-btn--danger, .ty-btn-danger {
    background: transparent;
    border-color: var(--ty-color-danger, #e50000);
}
.ty-btn--danger:hover:not(:disabled), .ty-btn-danger:hover:not(:disabled) {
    color: var(--ty-color-primary-contrast, #fff);
    background: var(--ty-color-danger, #e50000);
}

.ty-btn--sm, .ty-btn-sm { padding: 5px 10px; font-size: 11.5px; }

/* Square icon-only button. .ty-iconbtn already exists in scheduling.css with this same footprint (plus a
   hover pop only meaningful alongside that file's other motion) — declared compatibly here, not
   overridden, so pages with and without scheduling.css loaded look the same. */
.ty-btn--icon, .ty-iconbtn {
    width: 36px; height: 36px; padding: 0; flex: none;
    border-radius: var(--ty-radius-control, 10px);
    font-size: 16px;
}

/* --- Soft info hint (the wcj-hint recipe) --- */
.ty-hint {
    display: block;
    font-size: .72rem;
    line-height: 1.5;
    color: var(--ty-color-primary-ink, #1457b0);
    background: var(--ty-color-primary-soft, #eaf2fd);
    border: 1px solid var(--ty-color-primary-soft, #eaf2fd);
    border-radius: 9px;
    padding: 8px 10px;
}
.ty-hint--warning { color: var(--ty-color-warning-ink, #9a5b00); background: var(--ty-color-warning-soft, #fdf1dd); border-color: var(--ty-color-warning-soft, #fdf1dd); }
.ty-hint--danger   { color: var(--ty-color-danger-ink, #b32121);  background: var(--ty-color-danger-soft, #fdecec); border-color: var(--ty-color-danger-soft, #fdecec); }

/* --- Status pill (.ty-pill + tone-*): promoted from scheduling.css (loaded only by the schedule board +
   five job-detail panels) so the same class works on every page, e.g. team/vehicle expiry badges. Values
   copied verbatim — scheduling.css's own identical rule simply re-applies on pages that also load it, so
   nothing there changes. --- */
.ty-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font: 600 10.5px var(--ty-font-base, inherit);
    padding: 3px 9px;
    border-radius: var(--ty-radius-pill, 999px);
    white-space: nowrap;
}
.ty-pill.tone-neutral { color: var(--ty-color-text-muted, #57606a); background: var(--ty-color-surface-3, #eef1f4); }
.ty-pill.tone-info    { color: var(--ty-color-info-ink, #1457b0);    background: var(--ty-color-info-soft, #eaf2fd); }
.ty-pill.tone-success { color: var(--ty-color-success-ink, #1a7f37); background: var(--ty-color-success-soft, #e6f6ea); }
.ty-pill.tone-warning { color: var(--ty-color-warning-ink, #9a5b00); background: var(--ty-color-warning-soft, #fdf1dd); }
.ty-pill.tone-danger  { color: var(--ty-color-danger-ink, #b32121);  background: var(--ty-color-danger-soft, #fdecec); }

/* --- Interactive/toggle pill (the "wc-pill" recipe — an "Any time"-style tap-to-select pill). Named
   .ty-pill--tap, and .on layered on top of the status pill above, rather than changing .ty-pill's own
   base rule: the status pill must not gain a border or a pointer cursor it never asked for. --- */
.ty-pill--tap {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: .8rem; font-weight: 600;
    white-space: nowrap;
    color: var(--ty-color-text-muted, #57606a);
    background: var(--ty-color-surface, #fff);
    border: 1px solid var(--ty-color-border, #d8dee6);
    padding: 8px 13px;
    border-radius: 999px;
    cursor: pointer;
}
.ty-pill--tap:hover:not(:disabled) { background: var(--ty-color-surface-2, #f6f8fa); }
.ty-pill--tap.on {
    background: var(--ty-color-primary-soft, #eaf2fd);
    border-color: var(--ty-color-primary, #1E6FD9);
    color: var(--ty-color-primary-ink, #1457b0);
}
.ty-pill--tap:disabled { opacity: .55; cursor: not-allowed; }

/* --- Custom on/off switch (the wcj-switch recipe) — NOT `form-check form-switch`, so it looks identical
   in every theme instead of inheriting Bootstrap's own switch chrome. Two ways to drive it, both supported
   by the same class: a plain `<button class="ty-switch">` toggling its own `.on` class (the wcj-switch
   original), OR — the more common case when converting an existing Bootstrap page — directly on
   `<input type="checkbox" role="switch" class="ty-switch" @bind="...">`, where `appearance:none` strips the
   native checkbox box and `:checked` drives the same visual state as `.on`. Either way the element keeps
   its real semantics (a real checkbox stays keyboard/screen-reader operable as a checkbox), which is why
   this is preferred over rebuilding the control as a non-form button. --- */
.ty-switch {
    appearance: none;
    -webkit-appearance: none;
    width: 40px; height: 23px; flex: none;
    border: 0; padding: 0; margin: 0;
    border-radius: 999px;
    background: var(--ty-color-border-strong, #cfd6de);
    position: relative;
    vertical-align: middle;
    cursor: pointer;
    transition: background .15s;
}
.ty-switch::after {
    content: "";
    position: absolute; top: 2px; left: 2px;
    width: 19px; height: 19px;
    border-radius: 999px;
    background: var(--ty-color-surface, #fff);
    transition: left .15s;
}
.ty-switch.on, .ty-switch:checked { background: var(--ty-color-primary, #1E6FD9); }
.ty-switch.on::after, .ty-switch:checked::after { left: 19px; }
.ty-switch:disabled { opacity: .5; cursor: not-allowed; }
.ty-switch:focus-visible { outline: none; box-shadow: var(--ty-focus-ring, 0 0 0 .2rem rgba(30, 111, 217, .35)); }

/* --- Underline tab strip (the wcj-tabs recipe — a fourth, now shared, home for the tab pattern
   SettingsPage/TeamDetail/VehicleDetail each currently reinvent as settings-tab/sf-tab/vd-tab). --- */
.ty-tabs { display: flex; gap: 16px; border-bottom: 1px solid var(--ty-color-border, #eef1f4); flex-wrap: wrap; }
.ty-tab {
    display: inline-flex; align-items: center; gap: 6px;
    background: none; border: 0; border-bottom: 2px solid transparent;
    padding: 9px 2px; min-height: 40px;
    font-size: .8rem; font-weight: 600; white-space: nowrap;
    color: var(--ty-color-text-muted, #8a95a1);
    cursor: pointer;
}
.ty-tab:hover { color: var(--ty-color-text, #1c2128); }
.ty-tab.on { color: var(--ty-color-text, #1c2128); border-bottom-color: var(--ty-color-primary, #1E6FD9); }
.ty-tab:focus-visible { outline: none; box-shadow: var(--ty-focus-ring, 0 0 0 .2rem rgba(30, 111, 217, .35)); }

/* --- Responsive form grid + action row --- */
.ty-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--ty-space-lg, 16px);
}
.ty-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ty-grid > .ty-field--full { grid-column: 1 / -1; }
@media (max-width: 47.99rem) {
    .ty-grid, .ty-grid--3 { grid-template-columns: 1fr; }
}

.ty-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--ty-space-sm, 8px);
    flex-wrap: wrap;
}
.ty-actions--between { justify-content: space-between; }
.ty-actions--start { justify-content: flex-start; }

@media (max-width: 600px) {
    .ty-btn--icon, .ty-iconbtn, .ty-pill--tap, .ty-switch { min-width: 40px; min-height: 40px; }
}

/* ============================================================================
   Bootstrap override layer (2026-09-06 styling-consistency pass).
   ----------------------------------------------------------------------------
   Most of the app's C/B-grade pages are a correctly themed card or table shell
   wrapped around plain Bootstrap controls — `form-control`, `btn btn-primary`,
   `<table class="table">` — because there was nowhere else to reach for (see
   the shared kit section above this one). Restyling those Bootstrap classes
   here, once, lifts every one of those pages onto the playful look without
   editing each file — this IS the leverage move for the pages this pass never
   gets to individually.

   Scoped under `body` rather than a per-head shell wrapper (`.page` in
   Layout/MainLayout.razor, something else again in the Management console,
   which is about to link this same file over its own `.layout`/`.sidebar`/
   `.content` shell) — a wrapper class is one more thing every current AND
   future head has to happen to share, and there is no reason it should have
   to. `body` reaches every host without naming any of them.
   This still never touches the two things the brief calls out to leave alone
   — not by scoping around them, but because neither uses any class this layer
   restyles: the sign-in/registration flow has its own `.ty-auth__*` kit
   (`.ty-auth__input`, `.ty-auth__submit`, …), never `form-control`/`btn-*`, and
   the two arcade game pages are confirmed (grepped) never to reach for
   `form-control`, `btn-*`, `card`, `table`, `list-group-item`, `modal-*`,
   `alert-*` or `input-group-text` anywhere in their markup. If either one ever
   does, this section starts styling it — which would be the correct outcome
   for a class that means "Bootstrap form control" wherever it appears, not a
   sign this scoping was wrong.

   Every value is a --ty-* token — same rule as the rest of this file — so both
   platform themes (themes/trusty.css, themes/graft.css) repoint these exactly
   as they do every other component. A component with its OWN scoped override
   for one of these classes (loaded after this file, and Blazor's scoped
   attribute selector matches this same specificity) still wins, so nothing
   here fights an intentional per-page choice — it only fills the pages that
   never made one.
   ============================================================================ */

body .form-control,
body .form-select {
    font-size: 13.5px;
    color: var(--ty-color-text, #1c2128);
    background-color: var(--ty-color-surface, #fff);
    border: 1px solid var(--ty-color-border-strong, #d0d7de);
    border-radius: var(--ty-radius-control, 8px);
}
body .form-control:focus,
body .form-select:focus {
    color: var(--ty-color-text, #1c2128);
    background-color: var(--ty-color-surface, #fff);
    border-color: var(--ty-color-primary, #1b6ec2);
    box-shadow: var(--ty-focus-ring, 0 0 0 .2rem rgba(30, 111, 217, .35));
}
body .form-control.is-invalid,
body .form-select.is-invalid {
    border-color: var(--ty-color-danger, #dc3545);
}
body .form-control.is-invalid:focus,
body .form-select.is-invalid:focus {
    border-color: var(--ty-color-danger, #dc3545);
    box-shadow: 0 0 0 .2rem color-mix(in srgb, var(--ty-color-danger, #e50000) 30%, transparent);
}
body .form-control::placeholder { color: var(--ty-color-text-subtle, #8a95a1); opacity: 1; }
body .form-control:disabled, body .form-control[readonly],
body .form-select:disabled {
    background-color: var(--ty-color-surface-2, #f6f8fa);
    color: var(--ty-color-text-muted, #57606a);
}
body .form-label {
    font: 600 11px var(--ty-font-base, inherit);
    color: var(--ty-color-text-muted, #57606a);
    text-transform: uppercase;
    letter-spacing: .04em;
}
body .form-text { color: var(--ty-color-text-subtle, #8a95a1); }
body .invalid-feedback { color: var(--ty-color-danger-ink, #b32121); }

body .form-check-input { border-color: var(--ty-color-border-strong, #d0d7de); }
body .form-check-input:checked {
    background-color: var(--ty-color-primary, #1b6ec2);
    border-color: var(--ty-color-primary, #1b6ec2);
}
body .form-check-input:focus {
    border-color: var(--ty-color-primary, #1b6ec2);
    box-shadow: var(--ty-focus-ring, 0 0 0 .2rem rgba(30, 111, 217, .35));
}

body .btn {
    border-radius: var(--ty-radius-control, 8px);
    font-weight: 600;
    transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease, box-shadow 120ms ease, transform 120ms ease;
}
body .btn:active:not(:disabled) { transform: scale(.98); }
body .btn:focus-visible { box-shadow: var(--ty-focus-ring, 0 0 0 .2rem rgba(30, 111, 217, .35)); }

body .btn-primary {
    color: var(--ty-color-primary-contrast, #fff);
    background-color: var(--ty-color-primary, #1b6ec2);
    border-color: var(--ty-color-primary, #1b6ec2);
}
body .btn-primary:hover, body .btn-primary:focus {
    color: var(--ty-color-primary-contrast, #fff);
    background-color: var(--ty-color-primary-ink, #1457b0);
    border-color: var(--ty-color-primary-ink, #1457b0);
}
body .btn-secondary {
    color: var(--ty-color-text, #1c2128);
    background-color: var(--ty-color-surface-3, #eef1f4);
    border-color: var(--ty-color-surface-3, #eef1f4);
}
body .btn-secondary:hover, body .btn-secondary:focus {
    color: var(--ty-color-text, #1c2128);
    background-color: var(--ty-color-border, #e7ebf0);
    border-color: var(--ty-color-border, #e7ebf0);
}
body .btn-outline-secondary {
    color: var(--ty-color-text-muted, #57606a);
    background-color: var(--ty-color-surface, #fff);
    border-color: var(--ty-color-border-strong, #d0d7de);
}
body .btn-outline-secondary:hover, body .btn-outline-secondary:focus {
    color: var(--ty-color-text, #1c2128);
    background-color: var(--ty-color-surface-3, #eef1f4);
    border-color: var(--ty-color-border-strong, #d0d7de);
}
body .btn-outline-primary {
    color: var(--ty-color-primary, #1b6ec2);
    background-color: transparent;
    border-color: var(--ty-color-primary, #1b6ec2);
}
body .btn-outline-primary:hover, body .btn-outline-primary:focus {
    color: var(--ty-color-primary-contrast, #fff);
    background-color: var(--ty-color-primary, #1b6ec2);
    border-color: var(--ty-color-primary, #1b6ec2);
}
body .btn-danger {
    color: #fff;
    background-color: var(--ty-color-danger, #e50000);
    border-color: var(--ty-color-danger, #e50000);
}
body .btn-danger:hover, body .btn-danger:focus {
    background-color: var(--ty-color-danger-ink, #b32121);
    border-color: var(--ty-color-danger-ink, #b32121);
}
body .btn-outline-danger {
    color: var(--ty-color-danger, #e50000);
    background-color: transparent;
    border-color: var(--ty-color-danger, #e50000);
}
body .btn-outline-danger:hover, body .btn-outline-danger:focus {
    color: #fff;
    background-color: var(--ty-color-danger, #e50000);
    border-color: var(--ty-color-danger, #e50000);
}
body .btn-success {
    color: #fff;
    background-color: var(--ty-color-success, #26b050);
    border-color: var(--ty-color-success, #26b050);
}
body .btn-success:hover { background-color: var(--ty-color-success-ink, #1a7f37); border-color: var(--ty-color-success-ink, #1a7f37); }
body .btn-outline-success {
    color: var(--ty-color-success-ink, #1a7f37);
    background-color: transparent;
    border-color: var(--ty-color-success, #26b050);
}
body .btn-outline-success:hover { color: #fff; background-color: var(--ty-color-success, #26b050); border-color: var(--ty-color-success, #26b050); }
body .btn-link { color: var(--ty-color-primary, #1b6ec2); }
body .btn-group .btn, body .btn-group-sm .btn { border-radius: var(--ty-radius-control, 8px); }

body .card {
    border: 1px solid var(--ty-color-border, #e7ebf0);
    border-radius: var(--ty-radius-card, 12px);
    box-shadow: var(--ty-shadow-sm, 0 1px 2px rgba(12, 18, 28, .04));
    background: var(--ty-color-surface, #fff);
}
body .card-body { padding: 16px; }
body .card-header {
    background: var(--ty-color-surface, #fff);
    border-bottom: 1px solid var(--ty-color-border, #f0f2f5);
    font-weight: 700;
}

body .table {
    --bs-table-bg: transparent;
    --bs-table-hover-bg: var(--ty-color-surface-2, #f6f8fa);
    color: var(--ty-color-text, #1c2128);
}
body .table > thead {
    font: 700 10px var(--ty-font-base, inherit);
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--ty-color-text-subtle, #8a95a1);
}
body .table > :not(caption) > * > * { border-bottom-color: var(--ty-color-border, #e7ebf0); padding: 12px 14px; }
body .table-responsive,
body table.table:not(.ty-table) {
    background: var(--ty-color-surface, #fff);
    border: 1px solid var(--ty-color-border, #e7ebf0);
    border-radius: var(--ty-radius-card, 12px);
}
body .table-responsive { overflow: auto; }

body .list-group-item {
    border-color: var(--ty-color-border, #e7ebf0);
    background: var(--ty-color-surface, #fff);
    color: var(--ty-color-text, #1c2128);
}
body .list-group-item:first-child { border-top-left-radius: var(--ty-radius-card, 12px); border-top-right-radius: var(--ty-radius-card, 12px); }
body .list-group-item:last-child { border-bottom-left-radius: var(--ty-radius-card, 12px); border-bottom-right-radius: var(--ty-radius-card, 12px); }
body .list-group-item.active {
    background: var(--ty-color-primary-soft, #eaf2fd);
    border-color: var(--ty-color-primary-soft, #eaf2fd);
    color: var(--ty-color-primary-ink, #1457b0);
}
body .list-group-item-action:hover { background: var(--ty-color-surface-2, #f6f8fa); }

body .modal-content {
    border: 1px solid var(--ty-color-border, #e7ebf0);
    border-radius: var(--ty-radius-panel, 16px);
    box-shadow: var(--ty-shadow-lg, 0 30px 60px -30px rgba(0, 0, 0, .4));
}
body .modal-header, body .modal-footer { border-color: var(--ty-color-border, #e7ebf0); }
body .modal-header { padding: 16px 20px; }
body .modal-body { padding: 20px; }
body .modal-footer { padding: 14px 20px; }
body .modal-title { font-weight: 700; }

body .alert { border-radius: var(--ty-radius-card, 12px); border-width: 1px; }
body .alert-primary   { background: var(--ty-color-primary-soft, #eaf2fd); border-color: var(--ty-color-primary-soft, #eaf2fd); color: var(--ty-color-primary-ink, #1457b0); }
body .alert-success   { background: var(--ty-color-success-soft, #e6f6ea); border-color: var(--ty-color-success-border, #bfe3cc); color: var(--ty-color-success-ink, #1a7f37); }
body .alert-warning   { background: var(--ty-color-warning-soft, #fdf1dd); border-color: var(--ty-color-warning-border, #f2d9a4); color: var(--ty-color-warning-ink, #9a5b00); }
body .alert-danger    { background: var(--ty-color-danger-soft, #fdecec);  border-color: var(--ty-color-danger-border, #f4d3cf);  color: var(--ty-color-danger-ink, #b32121); }
body .alert-secondary,
body .alert-info      { background: var(--ty-color-surface-2, #f6f8fa); border-color: var(--ty-color-border, #e7ebf0); color: var(--ty-color-text-muted, #57606a); }

body .input-group-text {
    background: var(--ty-color-surface-2, #f6f8fa);
    border-color: var(--ty-color-border-strong, #d0d7de);
    color: var(--ty-color-text-muted, #57606a);
}

/* ============================================================================
   Tap targets: the floor, on the device the work is recorded on (2026-09-03).

   A responsive audit at 390px found at least one control under 32px on all
   sixteen screens — the header's language flag at 60x21 on every one of them,
   the list checkboxes at 17x17, the pager's arrows at 35x28, the info buttons
   at 24x24. None of them are broken; all of them are missed, in the rain, with
   one thumb, which is the same thing.

   Apple and Google both ask for 44px. This raises the floor to 40 on a phone
   without moving any layout: the padding grows into space the rows already
   have, and nothing here changes above 600px, where a mouse is doing the work.

   On !important: every one of these classes is declared in a scoped .razor.css,
   whose selectors carry an extra [b-xxxxxxxxxx] and whose bundle loads after
   this file. A plain rule here would lose both ways and change nothing at all —
   which is exactly how a floor like this gets written, shipped, and quietly
   does nothing. It is scoped to the phone and to the size properties only.
   ============================================================================ */
@media (max-width: 600px) {
    /* The shared icon and chip vocabulary, wherever it is used. */
    .ty-iconbtn,
    .ty-pager__btn,
    .ty-chip,
    .cl-chip,
    .jl-chip,
    .inv-chip,
    .ci-btn,
    .cl-act,
    .p2-iconbtn,
    a.ty-dir {
        min-width: 40px !important;
        min-height: 40px !important;
    }

    /* Icon-only buttons centre their glyph once they have a floor. */
    .ty-iconbtn,
    .ty-pager__btn,
    .ci-btn,
    .cl-act,
    .p2-iconbtn,
    a.ty-dir {
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    /* The header's language link — the one control on literally every screen. */
    .ty-lang-flag {
        min-height: 40px !important;
        padding-inline: .55rem !important;
    }

    /* A checkbox is 17px of ink whatever you do to it; what a thumb needs is the
       area around it, so the box grows and the row's own padding does the rest. */
    .form-check-input,
    .cl-check {
        width: 1.35rem !important;
        height: 1.35rem !important;
    }

    /* The tour's own controls sit over the page and are hit in exactly the same
       conditions as everything under them. */
    .ty-tour-replay-btn,
    .coach-btn,
    .coach-skip {
        min-height: 40px !important;
    }
}

/* The rest of the phone floor, measured rather than guessed: everything above was
   the shared icon vocabulary; this is the ordinary button vocabulary, which sits
   at 31px — one pixel under — almost everywhere because Bootstrap's .btn padding
   was never chosen for a thumb. Same scope, same reason, same media query. */
@media (max-width: 600px) {
    .btn,
    .ty-btn,
    .ty-btn-primary,
    .ty-btn-ghost,
    .ty-btn-quiet,
    .p2-btn,
    .p2-cta,
    .pl-act,
    .pl-map-toggle,
    .nc-back,
    .ad-toggle,
    .ty-dl-segbtn {
        min-height: 40px !important;
    }

    /* The little round help and hint buttons that sit beside a label: big enough
       to hit, small enough that they do not out-shout the field they explain. */
    .hb,
    .fh-btn {
        min-width: 36px !important;
        min-height: 36px !important;
    }

    /* Links that live inside a sentence cannot grow a min-height without pushing
       the line apart, so they grow their padding instead. */
    a.ow-link {
        display: inline-block;
        padding-block: .45rem;
    }

    /* Any checkbox or radio, not only the ones wearing a Bootstrap class. */
    input[type="checkbox"],
    input[type="radio"] {
        width: 1.35rem !important;
        height: 1.35rem !important;
    }
}

/* The last three, once the floor above was measured rather than assumed: two icon-only
   buttons that had height but no width, and the checkboxes that Bootstrap does not class. */
@media (max-width: 600px) {
    .pl-map-toggle,
    .ty-dl-segbtn {
        min-width: 40px !important;
    }

    input[type="checkbox"],
    input[type="radio"] {
        width: 2rem !important;
        height: 2rem !important;
    }
}


/* ══ Take payment in person (FB-054) ══════════════════════════════════════════════════════════════════
   The payment code panel on a trade's job screen. Global rather than per-domain because five trades draw
   the identical block from the same Core seam, and five copies of one card is five places to drift.
   The code sits on a white plate whatever the theme: a QR on a tinted background is a QR a camera argues
   with, and this one is read across a doorstep. */
.ty-takepay {
    margin-top: .75rem;
    padding: 1rem;
    border: 1px solid var(--ty-color-border, #e3e6ea);
    border-radius: var(--ty-radius-card, 12px);
    background: var(--ty-color-surface, #fff);
    text-align: center;
}
.ty-takepay-hd { display: flex; align-items: center; gap: .5rem; font-weight: 700; text-align: left; }
.ty-takepay-x { margin-left: auto; padding: 0 .25rem; color: var(--ty-color-text-muted, #6b7280); text-decoration: none; }
.ty-takepay-qr { display: inline-block; margin: .75rem auto .5rem; padding: 12px; background: #fff; border-radius: 8px; }
.ty-takepay-qr svg { display: block; width: 220px; height: 220px; }
.ty-takepay-hint { color: var(--ty-color-text-muted, #6b7280); font-size: .85rem; }
.ty-takepay-link { margin-top: .5rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .72rem; word-break: break-all; color: var(--ty-color-text-muted, #6b7280); }
.ty-takepay-acts { display: flex; justify-content: center; gap: .5rem; margin-top: .75rem; }

/* ---- Layout utility: let a flex/grid child shrink below its content width ---------------------------------
   Bootstrap 5 has no .min-w-0, and dozens of components pair it with .text-truncate inside a flex row. It
   used to be defined only in the Modules.Shared wizard stylesheet, which the web head loads on wizard pages
   alone — so everywhere else the class did nothing and long text pushed its row's trailing buttons out of
   view (2026-09-17: the directions panel's Close button). Global, because it is used globally. */
.min-w-0 { min-width: 0; }

