/* ============================================================================
   Connect-One Technologies - design system

   Hand-built, no framework. The previous attempt mixed a purchased theme's
   utility classes with hand-written rules and neither system owned the spacing,
   which is exactly why it looked off. One system now, one scale, one file.

   ZERO EXTERNAL REQUESTS. Fonts are self-hosted and subset to Latin (15KB each).
   No CDN, no Google Fonts, no analytics - the public site must render fast with
   nothing outside this server, and a SA company should not leak visitor IPs to a
   third party by default.
   ============================================================================ */

/* ---- Fonts ------------------------------------------------------------- */
@font-face {
    font-family: 'Montserrat';
    src: url('fonts/Montserrat-Regular.woff2') format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Montserrat';
    src: url('fonts/Montserrat-SemiBold.woff2') format('woff2');
    font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Montserrat';
    src: url('fonts/Montserrat-Bold.woff2') format('woff2');
    font-weight: 700; font-style: normal; font-display: swap;
}

/* ---- Tokens ------------------------------------------------------------ */
:root {
    /* Deep space canvas. Near-black with a blue cast reads as "technology"
       without the flat #000 that kills depth. */
    --bg:            #06080f;
    --bg-raised:     #0b0f1a;
    --surface:       rgba(255, 255, 255, .035);
    --surface-hover: rgba(255, 255, 255, .06);
    --border:        rgba(255, 255, 255, .09);
    --border-bright: rgba(255, 255, 255, .16);

    --text:          #e8ecf4;
    --text-dim:      #93a0b8;
    --text-faint:    #5d6b85;

    /* One accent ramp, used everywhere. Cyan -> violet is the current idiom for
       AI/tech and it survives on a dark canvas without vibrating. */
    /* ⚠️ Referenced by the users page as `var(--warn, #c77)` and defined NOWHERE, so every warning
       on that page - "Not set up", "Disabled", "Temporarily locked" - fell back to a muddy
       brown-pink for as long as the page has existed. Same amber the status chips use. */
    --warn: #fcd34d;

    --a1: #22d3ee;   /* cyan   */
    --a2: #6366f1;   /* indigo */
    --a3: #a855f7;   /* violet */
    --grad: linear-gradient(120deg, var(--a1), var(--a2) 55%, var(--a3));

    /* Spacing scale. Every gap in the site comes from here - no ad-hoc values. */
    --s1: .5rem;  --s2: .75rem; --s3: 1rem;   --s4: 1.5rem;
    --s5: 2rem;   --s6: 3rem;   --s7: 4.5rem; --s8: 7rem;   --s9: 10rem;

    --wrap: 1180px;
    --radius: 16px;
    --radius-lg: 24px;
}

/* ---- Reset ------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: 17px;
    line-height: 1.65;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
h1, h2, h3, h4 { margin: 0; line-height: 1.1; letter-spacing: -.025em; font-weight: 700; }
p { margin: 0; }

.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 var(--s4); }
/* Reading width for forms and single-column pages. Was referenced by the "back office unavailable"
   page in Program.cs and by the commitment editor while never being DEFINED - so both rendered
   full-width and nobody noticed, because an undefined class fails silently and looks like a
   deliberate layout. */
.wrap.narrow { max-width: 60ch; }

/* ---- Ambient background ------------------------------------------------
   Two slow-drifting gradient orbs plus a fine grid. Pure CSS - no images, so
   nothing to load and nothing to look dated. */
.ambient { position: fixed; inset: 0; z-index: -2; overflow: hidden; pointer-events: none; }

/* Content layer, explicitly above the fixed ambient decoration. .site-header already had
   z-index: 50; main and the footer had no stacking context at all, and relied purely on the
   decoration's negative z-index to stay behind them. Cheap insurance: a blurred, animated,
   fixed-position decorative layer is exactly the kind of thing that wins a paint-order argument
   after some later change, and the failure mode is invisible content rather than an error. */
main, .site-footer { position: relative; z-index: 1; }
.orb {
    position: absolute; border-radius: 50%;
    filter: blur(100px); opacity: .5;
    animation: drift 26s ease-in-out infinite alternate;
}
.orb-1 { width: 46vw; height: 46vw; left: -12vw; top: -14vw; background: radial-gradient(circle, #0891b2, transparent 68%); }
.orb-2 { width: 40vw; height: 40vw; right: -10vw; top: 12vh; background: radial-gradient(circle, #6d28d9, transparent 68%); animation-delay: -9s; }
.orb-3 { width: 34vw; height: 34vw; left: 32vw; bottom: -16vw; background: radial-gradient(circle, #4f46e5, transparent 70%); animation-delay: -17s; }
@keyframes drift {
    from { transform: translate3d(0, 0, 0) scale(1); }
    to   { transform: translate3d(4vw, 4vh, 0) scale(1.16); }
}
.grid-overlay {
    position: fixed; inset: 0; z-index: -1; pointer-events: none;
    background-image:
        linear-gradient(rgba(255,255,255,.028) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255,255,255,.028) 1px, transparent 1px);
    background-size: 64px 64px;
    mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 30%, transparent 78%);
    -webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 30%, transparent 78%);
}

/* ---- Header ------------------------------------------------------------ */
.site-header {
    position: sticky; top: 0; z-index: 50;
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    background: rgba(6, 8, 15, .72);
    border-bottom: 1px solid transparent;
    transition: border-color .3s ease, background .3s ease;
}
.site-header.scrolled { border-bottom-color: var(--border); background: rgba(6, 8, 15, .9); }
.header-inner { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); min-height: 76px; }

.brand { display: inline-flex; align-items: center; }
/* The supplied wordmark PNG is ~3:1, so height is the only knob. 44px keeps the tagline
   line legible without crowding the 76px header row. White variant only on this site -
   every surface it sits on is the dark canvas; the dark-on-light file is in
   /images for surfaces we don't own yet (mail, documents). */
.brand-logo { height: 44px; width: auto; display: block; }

.site-nav { display: flex; align-items: center; gap: var(--s1); }
.site-nav a {
    padding: .5rem .85rem; border-radius: 9px; font-size: .92rem;
    color: var(--text-dim); transition: color .2s, background .2s;
}
.site-nav a:hover { color: var(--text); background: var(--surface); }
.site-nav .cta { color: var(--text); border: 1px solid var(--border-bright); }
.site-nav .cta:hover { border-color: var(--a1); color: #fff; }
.signout-form { display: inline; margin: 0; }
/* Sign out is a BUTTON in a form (a POST, so it cannot be a link), but it sits in the nav beside
   real links and must read as one of them. It used --text-faint while .site-nav a uses --text-dim,
   which made the one control that signs you out look DISABLED next to everything else. Spotted in a
   screenshot of the live site, not by reading the CSS - the two tokens are one step apart and the
   difference only shows in context. */
.linklike {
    background: none; border: 0; font: inherit; cursor: pointer;
    padding: .5rem .85rem; border-radius: 9px; font-size: .92rem;
    color: var(--text-dim); transition: color .2s, background .2s;
}
.linklike:hover { color: var(--text); background: var(--surface); }

/* ---- Hero -------------------------------------------------------------- */
.hero { padding: clamp(5rem, 13vw, 11rem) 0 var(--s8); position: relative; }
.eyebrow {
    display: inline-flex; align-items: center; gap: .55rem;
    padding: .4rem .9rem; border-radius: 100px;
    border: 1px solid var(--border); background: var(--surface);
    font-size: .78rem; letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-dim); margin-bottom: var(--s4);
}
.eyebrow .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--a1); box-shadow: 0 0 10px var(--a1); }

h1.hero-title {
    font-size: clamp(2.4rem, 6.2vw, 4.6rem);
    max-width: 17ch;
    margin-bottom: var(--s4);
}
.gradient-text {
    background: var(--grad);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
}
.hero-lead { font-size: clamp(1.05rem, 1.7vw, 1.25rem); color: var(--text-dim); max-width: 58ch; margin-bottom: var(--s6); }
.hero-actions { display: flex; gap: var(--s3); flex-wrap: wrap; }

/* ---- Hero layout + panel ----------------------------------------------
   Single column by default; the visual only earns its place once there is real
   width for it, so it sits BELOW the copy on narrow screens and beside it from
   1040px up. Source order puts the copy first, so a phone gets the message
   before the decoration either way. */
.hero-grid { display: grid; gap: var(--s7); align-items: center; }
@media (min-width: 1040px) {
    .hero-grid { grid-template-columns: 1.02fr .98fr; gap: var(--s8); }
    /* The headline has half the width it had, so the ceiling of the clamp comes
       down with it - otherwise 17ch of 4.6rem type wraps to five ragged lines. */
    .hero-grid .hero-title { font-size: clamp(2.4rem, 4.3vw, 3.7rem); max-width: 15ch; }
}

.panel {
    border-radius: var(--radius-lg);
    border: 1px solid var(--border);
    background: linear-gradient(160deg, rgba(255,255,255,.045), rgba(255,255,255,.012));
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    box-shadow: 0 30px 80px rgba(0,0,0,.5);
    padding: var(--s5);
}
.panel-head { display: flex; align-items: center; gap: .6rem; padding-bottom: var(--s4); border-bottom: 1px solid var(--border); }
.panel-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--a1); box-shadow: 0 0 12px var(--a1); animation: pulse 2.4s ease-in-out infinite; }
.panel-title { font-size: .82rem; letter-spacing: .09em; text-transform: uppercase; color: var(--text-dim); }
.panel-meta { margin-left: auto; font-size: .78rem; color: var(--text-faint); }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

.pipe { list-style: none; margin: var(--s4) 0 0; padding: 0; display: grid; gap: var(--s4); }
.pipe-step { display: grid; grid-template-columns: 14px 1fr; align-items: center; gap: .75rem 1rem; position: relative; }
.pipe-node { width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--a1); grid-row: span 2; align-self: start; margin-top: 3px; }
.pipe-step.done .pipe-node { background: var(--a1); }
.pipe-step.live .pipe-node { border-color: var(--a3); box-shadow: 0 0 0 4px rgba(168,85,247,.16); }
/* The connector is drawn by the node's ::after rather than a separate element, so
   it cannot drift out of alignment with the dot it belongs to. */
.pipe-step:not(:last-child) .pipe-node::after {
    content: ""; position: absolute; left: 5px; top: 17px; width: 2px; height: calc(100% - 6px);
    background: linear-gradient(var(--a1), rgba(34,211,238,.12));
}
.pipe-name { font-size: .92rem; font-weight: 600; }
.pipe-bar { grid-column: 2; height: 4px; border-radius: 4px; background: rgba(255,255,255,.07); overflow: hidden; }
.pipe-bar i { display: block; height: 100%; border-radius: 4px; background: var(--grad); }
.pipe-bar i.run { width: 38%; animation: run 2.8s cubic-bezier(.4,0,.2,1) infinite; }
@keyframes run { 0% { width: 8%; opacity: .7; } 70% { width: 88%; opacity: 1; } 100% { width: 8%; opacity: .7; } }

.panel-foot { display: flex; align-items: center; gap: .5rem; margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--border); font-size: .84rem; color: var(--text-dim); }
.panel-foot svg { color: var(--a1); flex: none; }

/* ---- Buttons ----------------------------------------------------------- */
.btn {
    display: inline-flex; align-items: center; gap: .45rem;
    padding: .62rem 1.25rem; border-radius: 10px;
    font-size: .88rem; font-weight: 600; cursor: pointer;
    /* An <a class="btn"> inherits body's 1.65 while a <button> takes the UA's `normal`, so the two
       came out 45px against 39.8px sitting side by side. One line box for both. */
    line-height: 1.2;
    border: 1px solid transparent; transition: transform .2s, box-shadow .25s, background .2s, border-color .2s;
    /* These three exist because a <button> is not an <a>, and this rule was written for an <a>.
       Without them the UA stylesheet keeps its own defaults on every <button class="btn">:

         font-family -> Arial. EVERY button on this site rendered in Arial while the text beside it
                        was Montserrat. Not subtle at 15px semibold, and it was everywhere.
         background  -> buttonface, i.e. #f0f0f0. Any button without .btn-primary (which sets its
                        own gradient) was a LIGHT GREY PILL on a #06080f page - the schedule
                        editor's Cancel, and the Edit/Pause pair on every row of the schedules
                        table.
         color       -> buttontext, i.e. black, on a button carrying neither variant.

       .btn-primary and .btn-ghost both declare background/colour after this and are unaffected. */
    font-family: inherit;
    background: transparent;
    color: inherit;
}
.btn:hover { transform: translateY(-2px); }
.btn-primary { background: var(--grad); color: #fff; box-shadow: 0 8px 30px rgba(99, 102, 241, .35); }
.btn-primary:hover { box-shadow: 0 12px 40px rgba(99, 102, 241, .5); }
.btn-ghost { background: var(--surface); border-color: var(--border); color: var(--text); }
.btn-ghost:hover { background: var(--surface-hover); border-color: var(--border-bright); }

/* A `.btn` carrying NEITHER variant is not a design decision, it is a variant somebody forgot -
   eleven of them across the estate: both 2FA steps, account detail, users, the expenses list, plus
   four wearing a `.btn-sm` that is defined nowhere in this file. Until the reset above they showed
   as light grey system pills with black text; with it, and without this rule, they would be text on
   a transparent border - invisible. Ghost is the right floor: it is the quiet variant, and quiet is
   what every one of those sites meant. */
.btn:not(.btn-primary):not(.btn-ghost) { background: var(--surface); border-color: var(--border); color: var(--text); }
.btn:not(.btn-primary):not(.btn-ghost):hover { background: var(--surface-hover); border-color: var(--border-bright); }

/* ---- Sections ---------------------------------------------------------- */
section { position: relative; }
.section { padding: var(--s8) 0; }
.section-head { max-width: 62ch; margin-bottom: var(--s7); }
.section-head.center { margin-inline: auto; text-align: center; }
h2.section-title { font-size: clamp(1.9rem, 3.6vw, 3rem); margin-bottom: var(--s3); }
.section-lead { color: var(--text-dim); font-size: 1.08rem; }

/* ---- Cards ------------------------------------------------------------- */
.grid { display: grid; gap: var(--s4); }
/* ⛔ Explicitly TWO columns, not auto-fit. auto-fit with minmax(340px) resolves to THREE tracks
   at the page's 1131px content width, which is invisible while a grid holds 2 or 3 cards (empty
   tracks collapse) and then strands the 4th card alone on its own row the moment one is added -
   which is exactly what /approach did. */
.grid-2 { grid-template-columns: 1fr; }
@media (min-width: 800px) { .grid-2 { grid-template-columns: repeat(2, 1fr); } }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }

.card {
    position: relative; padding: var(--s5);
    border-radius: var(--radius-lg);
    border: 1px solid var(--border);
    background: var(--surface);
    backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
    transition: transform .3s, border-color .3s, background .3s;
    overflow: hidden;
}
/* Gradient hairline that lights up on hover - cheap, and it makes a flat card
   feel like a surface rather than a box. */
.card::before {
    content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
    background: var(--grad);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor; mask-composite: exclude;
    opacity: 0; transition: opacity .3s;
    pointer-events: none;
}
.card:hover { transform: translateY(-4px); background: var(--surface-hover); }
.card:hover::before { opacity: .85; }

.card-icon {
    width: 46px; height: 46px; border-radius: 13px; margin-bottom: var(--s4);
    display: grid; place-items: center;
    background: linear-gradient(140deg, rgba(34,211,238,.18), rgba(168,85,247,.18));
    border: 1px solid var(--border);
    color: var(--a1);
}
.card-icon svg { width: 22px; height: 22px; }
.card h3 { font-size: 1.25rem; margin-bottom: var(--s2); }
.card p { color: var(--text-dim); font-size: .97rem; }
.card-tag { font-size: .78rem; letter-spacing: .06em; text-transform: uppercase; color: var(--a1); margin-bottom: var(--s2); display: block; }
.card-link { display: inline-flex; align-items: center; gap: .4rem; margin-top: var(--s4); font-weight: 600; font-size: .92rem; color: var(--text); }
.card-link:hover { color: var(--a1); }

.feature-list { list-style: none; padding: 0; margin: var(--s4) 0 0; display: grid; gap: var(--s2); }
.feature-list li { position: relative; padding-left: 1.7rem; color: var(--text-dim); font-size: .97rem; }
.feature-list li::before {
    content: ""; position: absolute; left: 0; top: .55em;
    width: 7px; height: 7px; border-radius: 50%; background: var(--grad);
}

/* ---- Product detail ---------------------------------------------------- */
.product { padding: var(--s8) 0; border-top: 1px solid var(--border); }
.product:first-of-type { border-top: 0; }
.product-layout { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s7); align-items: center; }
.product h2 { font-size: clamp(1.7rem, 3vw, 2.4rem); margin-bottom: var(--s2); }

/* CSS-drawn product visual. No stock photography - it cannot go stale, cannot
   depict a customer we do not have, and costs nothing to load. */
.visual {
    aspect-ratio: 4 / 3; border-radius: var(--radius-lg);
    border: 1px solid var(--border); background: var(--bg-raised);
    position: relative; overflow: hidden;
    display: grid; place-items: center;
}
.visual::after {
    content: ""; position: absolute; inset: 0;
    background:
      radial-gradient(circle at 28% 22%, rgba(34,211,238,.22), transparent 46%),
      radial-gradient(circle at 76% 74%, rgba(168,85,247,.22), transparent 46%);
}
.visual-grid {
    position: absolute; inset: 0;
    background-image:
        linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px);
    background-size: 34px 34px;
}
.visual-mark { position: relative; z-index: 1; font-size: clamp(2.4rem, 6vw, 4rem); font-weight: 700; }

/* ---- Stats / trust ----------------------------------------------------- */
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: var(--s4); }
.stat { padding: var(--s4) 0; }
.stat-value { font-size: clamp(1.8rem, 3.4vw, 2.6rem); font-weight: 700; }
/* --text-faint measures 3.56:1 on this canvas, under WCAG AA - and a stat is meaningless without
   the label saying what it counts. These four rules had no user in any page until the account
   page's spend rollup, so this changes nothing that already renders. */
.stat-label { color: var(--text-dim); font-size: .88rem; margin-top: .2rem; }

/* ---- CTA band ---------------------------------------------------------- */
.cta-band {
    border-radius: var(--radius-lg); padding: clamp(2.4rem, 5vw, 4rem);
    border: 1px solid var(--border); background: var(--bg-raised);
    position: relative; overflow: hidden; text-align: center;
}
.cta-band::before {
    content: ""; position: absolute; inset: -40% -10% auto; height: 220%;
    background: radial-gradient(ellipse at 50% 0%, rgba(99,102,241,.22), transparent 62%);
}
.cta-band > * { position: relative; }
.cta-band h2 { font-size: clamp(1.8rem, 3.4vw, 2.6rem); margin-bottom: var(--s3); }
.cta-band p { color: var(--text-dim); max-width: 52ch; margin: 0 auto var(--s5); }

/* ---- Prose ------------------------------------------------------------- */
.prose { max-width: 68ch; }
.prose p { color: var(--text-dim); margin-bottom: var(--s4); }
.prose p.lead { color: var(--text); font-size: 1.2rem; }

/* ---- Editorial placeholder --------------------------------------------- */
/* Loud on purpose. The failure mode is shipping a placeholder to a prospect,
   and this site is public. */

/* ---- Forms ------------------------------------------------------------- */
.form-card { max-width: 440px; }
/* Pages that borrow the home hero's two-column shape for a decorative side panel (sign-in,
   about). Unlike the home panel, which drops below the copy on narrow screens, these are not
   rendered at all there: the person came for the left column, and decoration below the fold
   on a phone is only scroll. */
.aside-visual { display: none; }
@media (min-width: 1040px) { .aside-visual { display: block; } }

/* About: prose keeps its exact 68ch (the page's original shape); the plates take only the
   column that was empty beside it. Never let this grid narrow the text. */
@media (min-width: 1040px) {
    .about-grid { display: grid; grid-template-columns: minmax(0, 68ch) 1fr; gap: var(--s7); align-items: center; }
}
.about-map svg { width: 100%; max-width: 440px; height: auto; display: block; margin-inline: auto; }
.map-caption { text-align: center; color: var(--text-faint); font-size: .84rem; margin-top: var(--s3); }

/* The quote glyph is decoration, so it hangs outside the text block's box. */
.pull-quote { position: relative; padding-left: var(--s5); }
.pull-quote-mark { position: absolute; left: 0; top: -1.4rem; font-size: 5.5rem; line-height: 1; color: var(--a2); opacity: .4; }
.pull-quote p { font-size: clamp(1.4rem, 1.8vw, 1.8rem); line-height: 1.45; font-weight: 600; color: var(--text); margin: 0; }
.stack { display: grid; gap: var(--s3); margin-top: var(--s5); }
.stack label { display: grid; gap: .4rem; font-size: .88rem; font-weight: 600; color: var(--text-dim); }
.stack select, .stack textarea, .stack input {
    padding: .8rem 1rem; border-radius: 11px; font: inherit; color: var(--text);
    background: var(--surface); border: 1px solid var(--border);
}
.stack select:focus, .stack textarea:focus, .stack input:focus { outline: none; border-color: var(--a1); box-shadow: 0 0 0 3px rgba(34,211,238,.14); }
.error { color: #fca5a5; background: rgba(239, 68, 68, .1); border: 1px solid rgba(239,68,68,.28); border-radius: 11px; padding: .8rem 1rem; }
.muted { color: var(--text-faint); }

/* ---- Native dropdowns (site-wide) --------------------------------------
   A <select>'s option list is NOT part of the page. It is drawn by the OS, in whatever colour
   scheme the document declares - and this file declared none, so on a #06080f canvas every
   popup opened WHITE while its options kept the select's own `color: var(--text)` (#e8ecf4).
   Light grey on white: the wording was there and unreadable.

   This was solved once already, inside the `.editor-card` block below, and ONLY there - so the
   selects that live outside an editor card kept the bug: the two inventory filters on
   /admin/services, the product and confidence pickers on the commitment editor, and the role
   picker on /admin/users.

   Hoisted to bare `select`/`option` deliberately. Nothing here is layout - no width, no shared
   height, no drawn chevron - so it cannot disturb a page the way hoisting the `.editor-card`
   refinements would (see the warning on that section). And there is no page on this site that
   wants a light popup: the canvas is dark everywhere, unconditionally.

   BOTH declarations are needed, not either. `color-scheme` is what darkens the popup chrome and
   the native caret; the explicit `option` colours are the fallback where it is ignored, and they
   also stop an option inheriting the select's text colour onto a surface it was never matched to. */
select { color-scheme: dark; }
option { background: var(--bg-raised); color: var(--text); }

/* ---- Editor forms ------------------------------------------------------
   Back-office pages that ARE a form: the expense editor, the schedule editor and the account
   detail page. All are long - a dozen fields and up - which is a different problem from the
   four-field boxes above, and the reason for the grouping, the paired rows and the card surface.

   NOTE EVERY RULE BELOW IS NESTED UNDER `.editor-card`, deliberately. `.stack` is worn by six
   other forms (sign-in, password, both 2FA steps, commitments, users) and the control
   refinements here - full width, a shared height, a drawn select chevron - are NOT wanted
   on a four-field sign-in box. Nesting them means adding this section cannot change a single
   existing page. Do not hoist any of it up to bare `.stack`.

   This is NOT `.form-card`. That class is only `max-width: 440px` - no surface, no border, no
   padding - and three pages pass it `style="border-color:#c77"` for a border it does not have.
   Giving it a surface would change all of them at once, so the surface lives on a new class that
   the long back-office pages opt into. */
.editor-wrap { max-width: 760px; }

.editor-card {
    padding: clamp(var(--s4), 3vw, var(--s6));
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--bg-raised);
    box-shadow: 0 30px 80px rgba(0, 0, 0, .38);
}
/* "The tables do not exist yet". A warning, without turning the page red. */
.editor-card.is-alarm {
    border-color: rgba(239, 68, 68, .38);
    background: linear-gradient(rgba(239, 68, 68, .06), transparent 40%), var(--bg-raised);
}
.editor-card > h3:first-child, .editor-card .card-head { margin: 0 0 var(--s2); font-size: 1.15rem; }
.editor-card .cmd {
    margin: var(--s3) 0 0; padding: var(--s3); overflow-x: auto;
    border: 1px solid var(--border); border-radius: 11px;
    background: rgba(0, 0, 0, .35); color: var(--text-dim); font-size: .86rem;
}

/* A fieldset, not a div: the group heading becomes the accessible name for the fields inside it,
   so the steps are real structure to a screen reader rather than bold paragraphs.

   MIN-INLINE-SIZE IS NOT COSMETIC. A fieldset's initial `min-content` refuses to shrink below its
   widest child, so one long <option> - the account list is "Provider - DisplayName", typed by a
   human - pushes the grid past the card and gives the page a horizontal scrollbar. */
.editor-card .group { margin: 0; padding: 0; border: 0; min-inline-size: 0; }
.editor-card .group + .group { margin-top: var(--s5); padding-top: var(--s5); border-top: 1px solid var(--border); }
.editor-card .group-head { display: flex; align-items: center; gap: var(--s3); padding: 0; }
.editor-card .group-step {
    display: grid; place-items: center; flex: none;
    width: 30px; height: 30px; border-radius: 50%;
    border: 1px solid var(--border);
    background: linear-gradient(140deg, rgba(34,211,238,.18), rgba(168,85,247,.18));
    color: var(--a1); font-size: .84rem; font-weight: 700;
}
.editor-card .group-title { display: block; font-size: 1.02rem; font-weight: 700; letter-spacing: -.015em; }
/* --text-faint measures 3.56:1 on this surface, under WCAG AA's 4.5:1 at these sizes. The notes and
   hints carry instructions rather than decoration, so they take --text-dim at 7.25:1. */
.editor-card .group-note { display: block; color: var(--text-dim); font-size: .84rem; line-height: 1.4; }

/* app.css gives `.stack` a var(--s5) top margin, sized for a stack that follows a heading block.
   Under a legend it only needs to clear the heading. */
.editor-card .stack { margin-top: var(--s4); }

/* Two short fields side by side. Collapses on a phone - a date and an amount are not worth halving
   the tap target for. */
.editor-card .pair { display: grid; gap: var(--s3); }
@media (min-width: 620px) { .editor-card .pair { grid-template-columns: 1fr 1fr; align-items: start; } }

/* `color-scheme` is set NOWHERE else in this file, so the native date picker, its calendar glyph,
   the select popup and the caret all render in LIGHT mode on a #06080f page. This is the
   declaration that fixes that, and it has to sit on the controls themselves.

   The shared min-height is what makes a paired row line up: left to themselves a select came out at
   47.6px, a text input at 50.8 and a date input at 52.8, so the two halves sat on different
   baselines. `:not([type=checkbox])` keeps the tick box out of it - see .check below. */
/* `:not(.money-input)` is load-bearing, not tidiness. `:not([type="checkbox"])` carries the
   specificity of its argument, so this selector is (0,3,1) and out-ranks
   `.editor-card .stack .money-input` at (0,3,0) - which put the shared 3.4rem min-height back onto
   the amount input, inside a wrapper that already had it, and made the amount field 2px taller
   than the date beside it. Excluding it here keeps the wrapper the single owner of that height. */
.editor-card .stack input:not([type="checkbox"]):not(.money-input),
.editor-card .stack select,
.editor-card .stack textarea {
    color-scheme: dark;
    width: 100%;
    min-height: 3.4rem;
    transition: border-color .18s, background .18s, box-shadow .18s;
}
.editor-card .stack input:not([type="checkbox"]):not(.money-input):hover,
.editor-card .stack select:hover,
.editor-card .stack textarea:hover { border-color: var(--border-bright); }
.editor-card .stack input::placeholder,
.editor-card .stack textarea::placeholder { color: var(--text-faint); }
.editor-card .stack textarea { min-height: 5.5rem; resize: vertical; line-height: 1.55; }

/* Native select arrows do not honour a dark surface consistently across browsers, so the chevron is
   drawn here. Inline SVG data URI - this site makes ZERO external requests and this keeps it so. */
.editor-card .stack select {
    appearance: none; -webkit-appearance: none; padding-right: 2.6rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9' fill='none'%3E%3Cpath d='M1 1.5 7 7.5 13 1.5' stroke='%2393a0b8' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 1rem center;
}
/* The option list is drawn by the OS and will not read var(--surface) off the select. Without these
   the popup is white-on-white in some browsers. */
.editor-card .stack select option { background: #0b0f1a; color: #e8ecf4; }

/* A tick box is a control, not a field: it must escape the padding, border and full width above,
   and it reads as a row rather than a stacked label.

   A <div>, NOT a <label>. The row carries a hint as well as the wording, and a <label> that wraps
   both folds the hint's prose into the tick box's accessible name - see .hint below. The tick box
   carries an id, the wording is a `<label for>` beside it, and those two are the click target:

       <div class="check">
         <input type="checkbox" id="x" aria-describedby="x-hint" />
         <label for="x">This amount is only an estimate</label>
         <span class="hint" id="x-hint">...</span>
       </div>

   `cursor: pointer` therefore sits on the two clickable children rather than on this row, which
   also spans the hint - text that does nothing when clicked should not advertise that it does. */
.editor-card .check { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: .35rem .7rem; }
/* The hint drops to a second row under the wording, never under the tick box. */
.editor-card .check .hint { grid-column: 2; }
.editor-card .check input[type="checkbox"] {
    width: 1.15rem; height: 1.15rem; min-height: 0; margin: 0;
    accent-color: var(--a1); color-scheme: dark; cursor: pointer;
}
/* ⛔ `> label`, NEVER `span`. This was `.editor-card .check span` at (0,2,1), which out-ranked
   `.editor-card .hint` at (0,2,0) - so the hint inside a tick-box row took the WORDING's
   typography, .88rem/600, and rendered as a second line of label rather than as a hint. Any
   selector added here must stay off `.hint`, or that regression comes straight back. */
.editor-card .check > label { font-weight: 600; color: var(--text-dim); font-size: .88rem; cursor: pointer; }

/* ⛔ A HINT LIVES OUTSIDE ITS <label>, TIED TO THE CONTROL BY `aria-describedby`.

   It used to be nested inside the label - which reads well in source and is wrong: everything in a
   <label> is concatenated into the control's ACCESSIBLE NAME, so a screen reader announced forty
   words of prose where a four-word name belongs ("Notes Never put a password, a password hint, a
   security-question answer or anything else that helps someone get in..." on the account page).
   Nesting also buried the two `registry` links inside a label, where a click lands on the control
   instead of the link. The shape every hint-bearing field on this estate now wears:

       <div class="field">
         <label for="x">Notes</label>
         <textarea id="x" aria-describedby="x-hint"></textarea>
         <span class="hint" id="x-hint">...</span>
       </div>

   The name stays short and the hint is announced AFTER it, as a description. `.field` supplies the
   grid the <label> used to, so nothing moves on screen; `.hint` is styled by class alone and never
   by its position, so it renders identically inside a label, a `.field` or a `.check`.

   ⚠️ A field with no hint may still wrap its control in a plain <label> - implicit association is
   correct and there is no prose to swallow. But the moment one GAINS a hint it must take the shape
   above, or the accessible name silently swells again. */
/* ⛔ THE FONT TRIO IS LOAD-BEARING, NOT A COPY OF `.stack label` FOR TIDINESS. `.stack` gives
   every control `font: inherit`, so a control takes its size and weight FROM ITS PARENT - which
   used to be the label at .88rem/600. A bare grid div here would have handed the same inputs
   body's 17px/400, and the restructured fields would have rendered a size larger and lighter than
   the plain-label fields beside them in the same form. `.field` stands in for the label, so it
   must carry the label's font context too. */
.editor-card .field { display: grid; gap: .4rem; font-size: .88rem; font-weight: 600; color: var(--text-dim); }
.editor-card .hint { color: var(--text-dim); font-size: .8rem; font-weight: 400; line-height: 1.45; }

/* The currency lives in the control rather than the label, so the field reads "ZAR | 1234.56" at
   the point of typing. */
.editor-card .money {
    display: flex; align-items: stretch; overflow: hidden; min-height: 3.4rem;
    border: 1px solid var(--border); border-radius: 11px; background: var(--surface);
    transition: border-color .18s, box-shadow .18s;
}
.editor-card .money:hover { border-color: var(--border-bright); }
/* The focus ring belongs on the wrapper: the input inside it has no border of its own. */
.editor-card .money:focus-within { border-color: var(--a1); box-shadow: 0 0 0 3px rgba(34,211,238,.14); }
.editor-card .money-code {
    display: grid; place-items: center; flex: none; padding: 0 .9rem;
    border-right: 1px solid var(--border); background: rgba(255,255,255,.03);
    color: var(--text-dim); font-size: .82rem; font-weight: 600; letter-spacing: .04em;
}
.editor-card .stack .money-input {
    border: 0; border-radius: 0; background: transparent; min-height: 0;
    font-variant-numeric: tabular-nums; font-size: 1.05rem; letter-spacing: .01em;
    /* The amount is a size larger than its neighbours, and body's 1.65 line-height on that size
       makes the content box taller than the wrapper's min-height - which put the amount a pixel
       below the date beside it. Tightening the line box lets the shared height win. */
    line-height: 1.4;
}
.editor-card .stack .money-input:focus { box-shadow: none; }

.editor-card .actions {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3);
    margin-top: var(--s5); padding-top: var(--s5); border-top: 1px solid var(--border);
}
/* Save is a <button> and Cancel is often an <a>, and `.btn` sets no line-height - so one takes the
   UA's `normal` and the other body's 1.65, and the pair renders 46px against 54px side by side. */
.editor-card .actions .btn { min-height: 2.9rem; }
.editor-card .actions .btn-ghost { margin-left: auto; }
.editor-card .actions .btn[disabled] { opacity: .6; cursor: not-allowed; }
/* A disabled button that still lifts on hover reads as clickable. */
.editor-card .actions .btn[disabled]:hover { transform: none; }
@media (max-width: 480px) {
    .editor-card .actions { flex-direction: column; align-items: stretch; }
    .editor-card .actions .btn { justify-content: center; margin-left: 0; }
}

.editor-card .error, .editor-card .notice { margin-bottom: var(--s4); }
.editor-card .origin-note { margin-top: var(--s4); font-size: .88rem; line-height: 1.55; }
.editor-card .origin-note a { color: var(--a1); text-decoration: underline; text-underline-offset: 3px; }

/* `.error` above has no positive counterpart, so every "saved" message on this estate rendered as
   bare body text indistinguishable from a hint. Top level rather than nested: nothing else claims
   the name, and a confirmation is wanted outside an editor card too. */
.notice {
    padding: .8rem 1rem; border-radius: 11px;
    border: 1px solid rgba(34, 211, 238, .28); background: rgba(34, 211, 238, .08);
    color: var(--text); font-size: .92rem;
}


/* ---- Tables ------------------------------------------------------------
   Every back-office table: accounts, account access, users, the expense ledger and its product
   rollup, the schedules board and the estate-truth board. All eight wore `class="table"`, which
   matched NOTHING in this file, so all eight rendered as browser defaults - 14px serif-metric text,
   no padding, no rules, no alignment - on a near-black page.

   The estate board's `.truth-table` is now a modifier on top of this rather than a private copy.
   The services inventory is deliberately NOT included: it has its own responsive table in
   Services.razor.css that stacks into cards on a phone via data-label, and it is better than this. */

/* ⛔ WRAP EVERY TABLE IN THIS. A table is the one element that cannot be made to fit a narrow
   screen by reflowing - six to eight columns of ledger simply are wider than a phone. Without a
   scroll box the overflow lands on <body>, which drags the header, the nav and the footer sideways
   with it and leaves the page looking broken rather than merely wide.

   `tabindex="0"` is not decoration either: a scroll container that cannot be reached by keyboard is
   a region a keyboard-only visitor cannot read the right-hand half of. */
.table-scroll {
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg-raised);
}
.table-scroll:focus-visible { outline: 2px solid var(--a1); outline-offset: 2px; }

.table { width: 100%; border-collapse: collapse; font-size: .92rem; }

.table th {
    padding: var(--s3);
    border-bottom: 1px solid var(--border);
    text-align: left;
    white-space: nowrap;
    /* --text-faint is the obvious choice for a column heading and is what this file used before, but
       it measures 3.56:1 on this surface - under WCAG AA. A column heading is not decoration. */
    color: var(--text-dim);
    font-size: .76rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
}

.table td { padding: var(--s3); border-bottom: 1px solid var(--border); color: var(--text-dim); }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr { transition: background .16s; }
.table tbody tr:hover { background: var(--surface); }
.table a { color: var(--text); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--border-bright); }
.table a:hover { color: var(--a1); text-decoration-color: currentColor; }

/* The identifying column - vendor, provider, name. One cell per row is allowed to be bright. */
.table .strong { color: var(--text); font-weight: 600; }
/* Money and counts right-align on lining figures, so a column can be scanned for magnitude rather
   than read a row at a time. */
.table .num { text-align: right; font-variant-numeric: tabular-nums; }
.table .nowrap { white-space: nowrap; }

/* `.muted` is the faintest tier on the site - --text-faint, 3.56:1 on this surface - and it is fine
   for a decorative aside in prose. Inside a table it was carrying real content: the estimate in the
   ledger's AMOUNT column, the period a row was booked for, the em-dash standing in for a value. A
   cell is not an aside, so the faintest tier is not available here. */
.table .muted { color: var(--text-dim); }

/* ⛔ A DEMOTED ROW IS NOT A FADED ROW. Retired accounts, skipped expenses and paused schedules were
   each dimmed with `opacity:.55`, which drags every cell to roughly 3.4:1 against this surface -
   under WCAG AA. Fading is a cheap way to say "less important" that costs somebody the ability to
   read it at all, and these rows still hold real money and real access.

   The demotion is carried by the identifying cell dropping out of --text, plus the status word the
   row already shows. Every cell stays at --text-dim, 7.25:1. */
.table tr.is-dim .strong { color: var(--text-dim); font-weight: 500; }

/* ---- Status chips ------------------------------------------------------ */
/* States that were rendered as another word in the sentence - "est.", "paused", "posted",
   "Disabled", "Not set up" - so they read as prose and were missed while scanning.

   ⛔ EVERY CHIP CARRIES ITS WORD. Colour is the second signal, never the only one: a red/green
   board is unreadable to the ~8% of men with a colour vision deficiency and useless in a screenshot
   pasted into a chat. Same rule the estate-truth badges follow. */
.tag {
    display: inline-block;
    padding: .1rem .45rem;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text-dim);
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    vertical-align: middle;
    white-space: nowrap;
}
/* Amber, never grey: "paused", "awaiting" and "not set up" are warnings with a reason attached, not
   absences. Grey would read as "nothing here". */
.tag-warn { border-color: rgba(245, 158, 11, .34); background: rgba(245, 158, 11, .12); color: #fcd34d; }
.tag-bad { border-color: rgba(239, 68, 68, .34); background: rgba(239, 68, 68, .12); color: #fca5a5; }

/* A chip sitting BESIDE text in the same cell has to be pushed off it, while a chip that IS the
   whole cell must not be indented. CSS cannot ask "is there a text node before me", so which of the
   two it is has to be said in the markup. */
.tag-inline { margin-left: .35rem; }

/* A second line inside a cell - the timestamp under a "locked" chip. Its own line rather than
   trailing the chip, because the alternative is a status column wide enough to hold a date. */
.table .cell-note { display: block; margin-top: .25rem; font-size: .82rem; }

/* ---- Table row actions -------------------------------------------------- */
/* The quietest button tier on the estate, used by every table that has per-row actions: accounts
   access, staff accounts, the expense ledger and the schedules board.

   Even at the smaller global size, two or three bordered buttons on every row is a grid of boxes
   that fights the data it sits beside - and `.btn`'s hover lift makes the row jump under the
   pointer. These carry no border at all: a soft chip that fills in on hover, so the rows read
   first and their actions second. 28px tall clears the 24x24 target size WCAG 2.2 asks for. */
.row-actions { white-space: nowrap; text-align: right; }
.row-actions .btn {
    padding: .3rem .68rem;
    border-color: transparent;
    border-radius: 8px;
    background: var(--surface);
    color: var(--text-dim);
    font-size: .78rem;
    line-height: 1.35;
}
.row-actions .btn + .btn { margin-left: .3rem; }
.row-actions .btn:hover { background: var(--surface-hover); border-color: transparent; color: var(--text); transform: none; }
.row-actions .btn[disabled] { opacity: .55; cursor: not-allowed; }
.row-actions .btn[disabled]:hover { background: var(--surface); color: var(--text-dim); }
/* ---- Estate truth ------------------------------------------------------ */
/* ⛔ THE BADGE CARRIES THE WORD as well as the colour - the markup puts the status text inside it,
   and these rules must never replace that with a colour swatch. A red/green board is unreadable to
   the ~8% of men with a colour vision deficiency and useless in a screenshot pasted into a chat.

   ⛔ `unchecked` is AMBER, deliberately, and never grey. Grey reads as "nothing here"; the whole
   point of this board is that "we could not check" is a warning with a reason attached, not an
   absence. Three states, three colours, three words. */
.truth-badge {
    display: inline-flex; align-items: center; gap: .35rem;
    padding: .12rem .55rem; border-radius: 999px;
    font-size: .78rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
    border: 1px solid transparent; white-space: nowrap;
}
.truth-ok { color: #86efac; background: rgba(34, 197, 94, .12); border-color: rgba(34, 197, 94, .35); }
.truth-fail { color: #fca5a5; background: rgba(239, 68, 68, .12); border-color: rgba(239, 68, 68, .38); }
.truth-unchecked { color: #fcd34d; background: rgba(245, 158, 11, .12); border-color: rgba(245, 158, 11, .38); }

/* This board used to carry its own private copy of the table rules, with a comment explaining that
   `.table` was left alone because "the registry and staff pages already ship unstyled tables, and
   restyling them from here would be a change nobody asked for". It has since been asked for: every
   one of those tables was a browser default on a #06080f canvas. `.table` below is now shared, and
   what remains here is only what is true of THIS board.

   The evidence column holds a <pre> of raw JSON, and a status reason can run long, so cells here
   align to the top rather than the middle. */
.truth-table th, .truth-table td { vertical-align: top; }
.truth-table pre { font-size: .8rem; color: var(--text-dim); margin: .35rem 0 0; }

/* ---- Circuit failure --------------------------------------------------- */
/* Shown by the framework when a Blazor circuit dies. See the long comment on the element in
   App.razor for why it must exist at all.

   ⛔ `display: none` here is load-bearing: the framework reveals it by setting `display: block`
   INLINE, so this is what keeps it hidden the rest of the time. Do not switch it to flex or grid -
   the inline block would override the layout and the bar would come apart at the moment somebody
   most needs to read it.

   Deliberately NOT the template's yellow bar. On this canvas that reads as a browser warning about
   the site rather than a message from it; the red used here is the same one .error uses, so a
   failure looks like the rest of the product rather than like something broke underneath it. */
#blazor-error-ui {
    display: none;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 1000;
    padding: var(--s3) var(--s4);
    background: #2a0f14;
    border-top: 1px solid rgba(239, 68, 68, .45);
    color: #fecaca;
    font-size: .95rem;
    line-height: 1.5;
    box-shadow: 0 -8px 32px rgba(0, 0, 0, .45);
}
#blazor-error-ui strong { color: #fff; margin-right: .35rem; }
#blazor-error-ui .reload,
#blazor-error-ui .dismiss { color: #fff; text-decoration: underline; cursor: pointer; margin-left: var(--s3); }
#blazor-error-ui .dismiss { text-decoration: none; opacity: .75; }
#blazor-error-ui .dismiss:hover { opacity: 1; }

/* ---- Lost connection --------------------------------------------------- */
/* ⛔ `display: none` is half the mechanism, not decoration - same as #blazor-error-ui above. The
   framework does NOT set an inline style here; it toggles CLASSES on the element, so every visible
   state has to be spelled out below or the bar stays hidden through a real disconnect. Falsify a
   change to this by killing the server with a page open: the bar must appear. */
#components-reconnect-modal { display: none; }
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected,
#components-reconnect-modal.components-reconnect-paused,
#components-reconnect-modal.components-reconnect-resume-failed {
    display: block;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    /* Above #blazor-error-ui's 1000: if a circuit both errors and drops, "you are disconnected"
       is the actionable message and must be the one on top. */
    z-index: 1001;
    padding: var(--s3) var(--s4);
    background: #2a0f14;
    border-top: 1px solid rgba(239, 68, 68, .45);
    color: #fecaca;
    font-size: .95rem;
    line-height: 1.5;
    box-shadow: 0 -8px 32px rgba(0, 0, 0, .45);
}
/* Each state shows only its own sentence. Hidden by default, revealed by the matching state
   class, so the bar never shows "reconnecting" and "could not reconnect" at the same time. */
#components-reconnect-modal .components-reconnect-first-attempt-visible,
#components-reconnect-modal .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal .components-reconnect-failed-visible,
#components-reconnect-modal .components-pause-visible,
#components-reconnect-modal .components-resume-failed-visible { display: none; }
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible,
#components-reconnect-modal.components-reconnect-rejected .components-reconnect-failed-visible,
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible { display: block; }
#components-reconnect-modal button {
    margin-top: var(--s2);
    background: none;
    border: 1px solid rgba(239, 68, 68, .45);
    border-radius: 6px;
    padding: .35rem .9rem;
    color: #fff;
    cursor: pointer;
    font: inherit;
}

/* ---- Footer ------------------------------------------------------------ */
.site-footer { border-top: 1px solid var(--border); padding: var(--s7) 0 var(--s5); margin-top: var(--s8); }
.footer-grid { display: grid; grid-template-columns: 1.6fr 1fr 1fr; gap: var(--s6); }
.footer-blurb { color: var(--text-faint); font-size: .93rem; margin-top: var(--s3); max-width: 40ch; }
.site-footer h4 { font-size: .82rem; letter-spacing: .08em; text-transform: uppercase; color: var(--text-faint); margin-bottom: var(--s3); font-weight: 600; }
.footer-links { list-style: none; padding: 0; margin: 0; display: grid; gap: .6rem; }
.footer-links a { color: var(--text-dim); font-size: .93rem; }
.footer-links a:hover { color: var(--a1); }
.footer-base {
    margin-top: var(--s6); padding-top: var(--s4); border-top: 1px solid var(--border);
    display: flex; justify-content: space-between; gap: var(--s3); flex-wrap: wrap;
    color: var(--text-faint); font-size: .87rem;
}

/* ---- Scroll reveal ----------------------------------------------------- */
/* ⛔ FocusOnNavigate (Routes.razor, Selector="h1") moves focus to the page heading after every
   navigation so a screen reader announces the new page. It stamps tabindex="-1" on the h1, and
   Chrome then paints a focus ring around the whole heading - which rendered as a hairline BOX
   around the hero title on first paint. Suppressing it here costs no keyboard affordance: a
   tabindex="-1" element is not reachable by Tab, so no keyboard user can ever land on it and be
   left without a ring. The focus, and the announcement, still happen.
   ⛔ Do NOT widen this to a blanket :focus { outline: none } - that would strip the ring from
   every real control on the site. */
h1[tabindex="-1"]:focus { outline: none; }

/* ⛔ SCOPED TO .js DELIBERATELY - do not drop the prefix.
   .reveal is the scroll-in animation, and the hidden state is opacity:0. An earlier version applied
   that unconditionally, which made the ENTIRE SITE depend on site.js running: if the script 404'd,
   errored, or was blocked, every visitor got a header on an empty page. Verified by screenshot -
   at 1440x1000 the page rendered blank but for the nav.
   The .js class is set by an inline snippet in App.razor's <head>, so it lands before first paint
   (no flash of hidden content). No script, no class, no hidden state: content is visible by
   default and the animation is what is optional - not the content. */
.js .reveal { opacity: 0; transform: translateY(22px); transition: opacity .7s cubic-bezier(.2,.7,.2,1), transform .7s cubic-bezier(.2,.7,.2,1); }
.js .reveal.in { opacity: 1; transform: none; }

/* Motion is decoration. Anyone who has asked their OS to stop it gets a static
   page, not a slightly-less-animated one. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
    html { scroll-behavior: auto; }
    .js .reveal { opacity: 1; transform: none; }
}

/* ---- Responsive -------------------------------------------------------- */
@media (max-width: 900px) {
    .product-layout { grid-template-columns: 1fr; gap: var(--s5); }
    .footer-grid { grid-template-columns: 1fr; gap: var(--s5); }
    :root { --s8: 4.5rem; --s7: 3rem; }

    /* ⛔ The nav links are NOT hidden here, deliberately. An earlier version did
       `.site-nav a:not(.cta) { display: none }`, which left a phone visitor with exactly one
       header link - "Talk to us" - and no way to reach Products, Approach or About except by
       scrolling to the footer. That is the whole site behind a single call-to-action.
       Instead the header wraps to two rows: the brand, then the links with the CTA at the end of
       them. No JavaScript, no hamburger, nothing to fail. */
    .header-inner { flex-wrap: wrap; row-gap: .5rem; padding-top: .7rem; padding-bottom: .7rem; min-height: 0; }
    .brand-logo { height: 38px; }
    /* Full width on its own row. The negative margin pulls the first link's padding back so the
       row starts flush with the brand above it rather than looking indented by a few pixels. */
    /* Full-bleed to the viewport edges so all four items get the whole 390px rather than the
       ~342px left inside the page gutter - which was 10px short and clipped the CTA. The padding
       puts the first item back in line with the brand above it. */
    .site-nav { width: 100%; gap: .1rem; margin-inline: calc(var(--s4) * -1); padding-inline: calc(var(--s4) - .5rem);
                overflow-x: auto; scrollbar-width: none; }
    .site-nav::-webkit-scrollbar { display: none; }
    /* nowrap is the point: without it "What we build" breaks after "we" and the row becomes two
       ragged lines of half-labels. If the four items ever stop fitting, the row scrolls sideways
       instead - which degrades far better than wrapping. */
    .site-nav a { font-size: .8rem; padding: .38rem .42rem; white-space: nowrap; }
    /* The CTA is the one item that must never be the one scrolled off the edge, so it keeps its
       weight while the plain links give up the millimetres. */
    .site-nav a.cta { padding: .42rem .8rem; }
    .site-nav a.cta { margin-left: auto; }
    /* The four primary items already use the full width of a 390px screen, so a fifth would push
       the CTA off the edge. This is a staff link, not a customer one, and it stays reachable in
       the footer on every screen size - so it is the one that gives way here. Note this is NOT
       the blanket link-hiding removed above: the customer-facing nav stays complete. */
    .nav-signin { display: none; }
}
}

/* Staff door in the footer - deliberately quieter than the links around it. */
.footer-signin { color: var(--text-faint); }
