/* ── ClickCompliance palette ───────────────────────────────────────────────
   Material Blue. This product and the Khamo compliance build share a codebase
   lineage, so they must not read as the same app with a different accent — the
   blue palette is only half of that separation; the TOP-NAV shell below is the
   other half, and the more important one. Do not reintroduce a left rail here.

   Marketing pages run dark (--night-*); the signed-in app runs light, because
   compliance tables are dense and get read all day. The variable NAMES have
   survived two rebrands (green → violet → blue) precisely so every existing rule
   re-themes without being touched — keep it that way. */
:root {
    /* Material Blue ramp. The four stops the client specified — E3F2FD, 90CAF9,
       2196F3, 0D47A1 — are Blue 50/200/500/900, so the in-between steps are taken
       from the same ramp rather than eyeballed, which keeps the tints consistent
       when a component needs a shade that was never named. */
    --brand-900: #0D47A1;   /* specified — deep: app chrome, headings */
    --brand-800: #1565C0;
    --brand-700: #1976D2;   /* primary-dark: buttons, links */
    --brand-600: #1E88E5;
    --brand-500: #2196F3;   /* specified — primary */
    --brand-300: #64B5F6;
    --brand-200: #90CAF9;   /* specified — light: borders, chart accents */
    --brand-100: #BBDEFB;
    --brand-50:  #E3F2FD;   /* specified — tint: panel backgrounds */

    --accent:    #2196F3;
    --accent-50: #E3F2FD;
    --grad:      linear-gradient(135deg, #0D47A1 0%, #2196F3 100%);

    --ink:       #0F172A;
    --muted:     #64748B;
    --line:      #E2E8F0;
    --surface:   #ffffff;
    --page-bg:   #F6F8FC;

    /* dark marketing surfaces */
    --night-900: #070B18;
    --night-800: #0B1022;
    --night-700: #141B33;
    --night-line: rgba(255,255,255,.08);

    /* ── HUD console ───────────────────────────────────────────────────────
       The signed-in app is a dark SHELL around a light WORKBENCH: rail, topbar
       and the command band run dark and instrument-like, while property tables
       and forms stay on light panels — they are dense and get read all day, and
       that readability is the reason the workspace was light to begin with.
       These tokens exist so the dark chrome is written once, not re-derived
       with ad-hoc rgba() every time a console component is added. */
    --hud-panel:  rgba(255,255,255,.045);
    --hud-raise:  rgba(255,255,255,.07);
    --hud-line:   rgba(255,255,255,.10);
    --hud-text:   #E6EAF5;
    --hud-dim:    #8E9BB8;
    --hud-faint:  #6B7796;
    --hud-ok:     #34D399;
    --hud-warn:   #FBBF24;
    --hud-crit:   #FB7185;

    /* ── material system ───────────────────────────────────────────────────
       Elevation is BLUE-tinted, not neutral grey, so light panels sit in the
       same light as the deep-blue header. Spend it sparingly: page-level
       containers get --e1, the hero score card gets --e2, and .metric /
       .side-card stay bordered-flat. When everything is elevated, nothing is. */
    --e1: 0 1px 2px rgba(15,23,42,.05), 0 6px 16px -10px rgba(13,71,161,.20);
    --e2: 0 1px 2px rgba(15,23,42,.06), 0 14px 30px -16px rgba(13,71,161,.30);

    /* Radius rhythm. Radii ran 9/10/11/12/14/16/18/20/24px with no system. */
    --r-lg: 18px;   /* page-level containers: score card, chart, table, empty */
    --r-md: 14px;   /* cards in a grid: .metric, .side-card, skeleton tiles */
    --r-sm: 10px;   /* controls inside a card: chips, search, filter bar */

    --line-soft: #EDF1F7;   /* inner table rules, one step quieter than --line */

    /* Two-tier spacing. Every dashboard gap was a flat 1.1rem, so a card inside
       a group and a whole separate section sat the same distance apart — the
       mechanical reason nothing on the page read as grouped. */
    --gap-card: 1rem;
    --gap-band: 1.9rem;

    /* ONE status ramp. These hexes already existed at .metric.live-*, .side-when.*
       and .gauge.warn/.crit — this consolidates three drifting copies, it does not
       introduce a colour. Semantics unchanged: green = compliant, amber = warning,
       red = critical, and none of them fire unless a count is non-zero. */
    --ok-500:#10B981;   --ok-700:#047857;   --ok-50:#D1FAE5;   --ok-line:#A7F3D0;
    --warn-500:#F59E0B; --warn-700:#92400E; --warn-50:#FEF3C7; --warn-line:#FDE68A;
    --warn-25:#FFFBEB;
    --crit-500:#DC2626; --crit-700:#B91C1C; --crit-50:#FEE2E2; --crit-line:#FCA5A5;
    --crit-25:#FEF2F2;
}

/* ── tabs ──────────────────────────────────────────────────────────────────
   A tinted pill plus a gradient underline on the active tab. The underline is
   what makes it read as a TAB STRIP rather than a row of buttons — the previous
   flat segmented control was the thing that didn't land. */
.ctabs {
    display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 14px;
    padding: 6px;
    margin-bottom: 1.5rem;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .05);
}
.ctabs::-webkit-scrollbar { display: none; }
.ctabs button {
    position: relative;
    border: 0; background: transparent; cursor: pointer;
    padding: 10px 16px 13px; border-radius: 10px;
    font-weight: 600; font-size: .94rem; color: var(--muted);
    display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
    transition: background .16s ease, color .16s ease;
}
.ctabs button:hover { background: var(--brand-50); color: var(--brand-700); }
.ctabs button.active { background: var(--brand-50); color: var(--brand-700); }
.ctabs button.active::after {
    content: ""; position: absolute; left: 14px; right: 14px; bottom: 5px;
    height: 3px; border-radius: 3px; background: var(--grad);
}
.ctabs .tab-count {
    font-size: .72rem; font-weight: 700; line-height: 1;
    padding: 3px 7px; border-radius: 999px;
    background: var(--line); color: var(--ink);
}
.ctabs .tab-count.urgent { background: #FEE2E2; color: #B91C1C; }
.ctabs .tab-count.warn   { background: #FEF3C7; color: #92400E; }

html, body {
    font-family: 'Segoe UI', 'Inter', Roboto, 'Helvetica Neue', sans-serif;
    background: var(--page-bg);
    color: var(--ink);
}

a, .btn-link { color: var(--brand-600); }

h1, h2, h3 { color: var(--brand-800); font-weight: 700; letter-spacing: -0.01em; }

/* page content animates in */
/* `backwards`, never `both`: a finished `both` animation keeps its final keyframe
   (transform: none) applied forever and outranks any non-!important hover rule —
   it would silently disable every :hover translateY on the page. */
article.content { animation: fadeUp .25s ease-out backwards; }
@keyframes fadeUp {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: none; }
}

/* buttons */
.btn { border-radius: 12px; font-weight: 600; transition: all .18s ease; }
.btn-primary {
    color: #fff;
    background: linear-gradient(135deg, var(--brand-600), var(--brand-800));
    border: none;
    box-shadow: 0 4px 14px rgba(13, 71, 161, .25);
}
.btn-primary:hover:not(:disabled), .btn-primary:focus {
    background: linear-gradient(135deg, var(--brand-500), var(--brand-700));
    transform: translateY(-1px);
    box-shadow: 0 8px 20px rgba(13, 71, 161, .3);
    color: #fff;
}
/* Success stays GREEN even though the brand is blue: on a compliance dashboard
   green/amber/red carry meaning, and a blue "compliant" badge next to a blue
   primary button would make status indistinguishable from chrome. */
.btn-success { background: #059669; border: none; }
.btn-success:hover { background: #047857; transform: translateY(-1px); }
.btn-outline-primary { color: var(--brand-600); border-color: var(--brand-500); }
.btn-outline-primary:hover { background: var(--brand-600); border-color: var(--brand-600); }

/* cards / stat tiles */
.border.rounded, .card {
    background: var(--surface);
    border: 1px solid var(--line) !important;
    border-radius: var(--r-lg) !important;
    box-shadow: var(--e1);
    transition: transform .18s ease, box-shadow .18s ease;
}
/* Nothing carrying `border rounded` in this app is clickable — a static panel
   must not lift under the cursor and promise an interaction that isn't there. */
.border.rounded:hover { transform: none; box-shadow: var(--e2); }

/* tables */
.table {
    background: var(--surface);
    border-radius: var(--r-lg);
    overflow: hidden;
    box-shadow: var(--e1);
}

/* ── table overflow ────────────────────────────────────────────────────────
   Every table in the app was rendered bare, with no scroll container anywhere and
   no overflow guard on <body>. Wide tables therefore pushed the whole PAGE sideways
   on a phone rather than scrolling within their own column — worst on .dash-table,
   which pins 600px of <colgroup> widths under `table-layout: fixed` inside a
   content box that is ~343px at a 375px viewport.

   The card treatment moves up to the wrapper: a scroll container clips its child's
   box-shadow, so leaving the elevation on .table cut the shadow off mid-edge on
   every wrapped table. */
.table-responsive {
    border-radius: var(--r-lg);
    box-shadow: var(--e1);
    background: var(--surface);
    scrollbar-width: thin;
}
.table-responsive > .table {
    border-radius: 0;
    box-shadow: none;
}
/* Fixed layout honours the colgroup regardless of container width, so give the
   table the width it actually wants and let the wrapper scroll — squeezing it
   instead just overlaps the address column into the EPC band. */
.table-responsive > .dash-table { min-width: 720px; }
.table thead th {
    background: var(--brand-50);
    color: var(--brand-800);
    border-bottom: 2px solid var(--brand-100);
    font-size: .82rem;
    text-transform: uppercase;
    letter-spacing: .06em;
}
.table-hover tbody tr { transition: background .15s ease; }
.table-hover tbody tr:hover { background: var(--brand-50); }

/* forms */
.form-control, .form-select, textarea.form-control {
    border-radius: 12px;
    border: 1.5px solid var(--line);
    padding: .6rem .9rem;
    transition: border-color .15s ease, box-shadow .15s ease;
}
.form-control:focus, .form-select:focus {
    border-color: var(--brand-500);
    box-shadow: 0 0 0 .2rem rgba(33, 150, 243, .18);
}

/* badges */
.badge { border-radius: 20px; padding: .45em .85em; font-weight: 600; }
.text-bg-success { background: #D1FAE5 !important; color: #047857 !important; }
.text-bg-dark { background: var(--brand-800) !important; }

/* nav pills (add-property tabs) */
.nav-pills .nav-link { border-radius: 24px; color: var(--brand-600); font-weight: 600; transition: all .15s ease; border: none; background: none; }
.nav-pills .nav-link.active { background: var(--brand-800); color: #fff; box-shadow: 0 4px 12px rgba(13, 71, 161, .3); }
.nav-pills .nav-link:not(.active):hover { background: var(--brand-50); }

/* alerts */
.alert { border-radius: 14px; border: none; box-shadow: 0 4px 14px rgba(15, 23, 42, .06); animation: fadeUp .3s ease-out both; }
.alert-success { background: #D1FAE5; color: #065F46; }

/* auth pages */
.auth-hero {
    max-width: 440px;
    margin: 6vh auto 0;
    background: var(--surface);
    border-radius: 24px;
    padding: 2.2rem 2.4rem;
    box-shadow: 0 25px 50px -15px rgba(15, 23, 42, .35);
    animation: fadeUp .4s ease-out both;
}
.auth-brand {
    background: linear-gradient(135deg, var(--brand-700), var(--brand-900));
    color: #fff;
    border-radius: 18px;
    padding: 1.1rem 1.4rem;
    margin-bottom: 1.5rem;
    font-weight: 700;
    font-size: 1.15rem;
}
.auth-brand small { display: block; font-weight: 400; opacity: .85; margin-top: .2rem; }

h1:focus { outline: none; }

.valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; }
.invalid { outline: 1px solid #e50000; }
.validation-message { color: #e50000; }

#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; }

.blazor-error-boundary {
    background: #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
    border-radius: 12px;
}
.blazor-error-boundary::after { content: "An error has occurred."; }

.darker-border-checkbox.form-check-input { border-color: #929292; }

/* ── marketing pages ─────────────────────────────────────────────────────── */
.mk-shell { min-height: 100vh; display: flex; flex-direction: column; background: var(--page-bg); }
.mk-nav { display: flex; justify-content: space-between; align-items: center; padding: .9rem 2.2rem;
    background: linear-gradient(90deg, var(--brand-800), var(--brand-900)); position: sticky; top: 0; z-index: 10;
    box-shadow: 0 4px 18px rgba(13,43,38,.3); }
.mk-brand { color: #fff; font-weight: 800; font-size: 1.2rem; text-decoration: none; border: none; }
.mk-links { display: flex; align-items: center; gap: 1.2rem; }
.mk-links a { color: #dceee2; font-weight: 600; text-decoration: none; border: none;
    position: relative; transition: color .18s; padding-bottom: 2px; }
.mk-links a:not(.btn)::after { content: ""; position: absolute; left: 0; bottom: -2px;
    width: 100%; height: 2px; background: linear-gradient(90deg, #7fd4a8, #a9e8c4);
    border-radius: 2px; transform: scaleX(0); transform-origin: left;
    transition: transform .28s cubic-bezier(.2,.8,.3,1); }
.mk-links a:hover { color: #fff; }
.mk-links a:not(.btn):hover::after { transform: scaleX(1); }
.mk-main { flex: 1; }

/* ── marketing nav: mobile ─────────────────────────────────────────────────
   .mk-links was a non-wrapping flex row of eight items with no breakpoint, so the
   nav needed ~870px and simply ran off the side of the viewport on every phone.
   Nothing clipped it — no overflow guard on .mk-nav and none on <body> — so the
   whole PAGE scrolled sideways on all twelve public pages, the homepage included.

   The app's own tab strip scrolls rather than collapsing (see NavMenu.razor), but
   that reasoning does not transfer: this nav ends in two CTAs, and a "Get started"
   button that has to be swiped into view is a button nobody presses. So the six
   destinations collapse and the primary CTA stays on the bar. */
.mk-menu-toggle { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; }
.mk-burger { display: none; }

@media (max-width: 900px) {
    .mk-nav { flex-wrap: wrap; padding: .75rem 1.1rem; gap: .75rem; }

    .mk-burger {
        display: inline-grid; place-items: center; gap: 5px; margin-left: auto;
        width: 42px; height: 42px; border-radius: 10px; cursor: pointer;
        background: rgba(255,255,255,.10); border: 1px solid rgba(255,255,255,.20);
    }
    .mk-burger span {
        display: block; width: 18px; height: 2px; border-radius: 2px; background: #fff;
        transition: transform .22s cubic-bezier(.22,.8,.3,1), opacity .16s ease;
    }
    /* Rows sit at 0 / 7 / 14px (2px bar + 5px gap), so ±7 collapses them to centre. */
    .mk-menu-toggle:checked ~ .mk-burger span:nth-child(1) { transform: translateY(7px)  rotate(45deg); }
    .mk-menu-toggle:checked ~ .mk-burger span:nth-child(2) { opacity: 0; }
    .mk-menu-toggle:checked ~ .mk-burger span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
    /* The checkbox is off-screen, so the focus ring has to be drawn on the label. */
    .mk-menu-toggle:focus-visible ~ .mk-burger { outline: 3px solid rgba(255,255,255,.55); outline-offset: 2px; }

    /* display:none while closed keeps the row out of flow, so the bar stays one line. */
    .mk-links {
        display: none; flex-basis: 100%; flex-direction: column; align-items: stretch;
        gap: .15rem; padding-bottom: .35rem;
    }
    .mk-menu-toggle:checked ~ .mk-links { display: flex; }
    .mk-links a { padding: .7rem .15rem; }
    /* The underline sweep is a hover affordance; on a stacked touch menu it just
       draws a stray 2px rule under every row. */
    .mk-links a:not(.btn)::after { content: none; }
    .mk-links a.btn { margin-top: .35rem; text-align: center; }
}

@media (prefers-reduced-motion: reduce) {
    .mk-burger span { transition: none; }
}
.mk-hero { background: linear-gradient(135deg, var(--brand-700), var(--brand-900));
    color: #fff; text-align: center; padding: 5.5rem 1.5rem 4.5rem; }
.mk-hero h1 { color: #fff; font-size: clamp(2rem, 5vw, 3.2rem); font-weight: 800; animation: fadeUp .5s ease-out both; }
.mk-hero p { max-width: 640px; margin: 1.2rem auto 0; font-size: 1.15rem; color: #dceee2; animation: fadeUp .6s ease-out both; }
.mk-hero strong { color: #a9e8c4; }
.mk-cta { margin-top: 2rem; display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; animation: fadeUp .7s ease-out both; }
.mk-stats { display: flex; justify-content: center; gap: 2.5rem; flex-wrap: wrap; padding: 1.6rem;
    background: #fff; box-shadow: 0 8px 24px rgba(20,50,40,.08); }
.mk-stats div { text-align: center; color: var(--muted); font-size: .9rem; }
.mk-stats span { display: block; font-size: 1.9rem; font-weight: 800; color: var(--brand-700); }
.mk-features, .mk-steps, .mk-page { max-width: 1050px; margin: 0 auto; padding: 3.5rem 1.5rem; }
.mk-features h2, .mk-steps h2 { text-align: center; margin-bottom: 2rem; }
.mk-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.4rem; }
.mk-grid3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.4rem; text-align: center; }
.mk-grid3 b { display: inline-flex; width: 2.6rem; height: 2.6rem; border-radius: 50%; background: var(--brand-700);
    color: #fff; align-items: center; justify-content: center; font-size: 1.2rem; margin-bottom: .6rem; }
.mk-card { background: #fff; border-radius: 18px; padding: 1.6rem; box-shadow: 0 8px 22px rgba(20,50,40,.07);
    transition: transform .18s ease, box-shadow .18s ease; }
.mk-card:hover { transform: translateY(-4px); box-shadow: 0 16px 32px rgba(20,50,40,.12); }
.mk-card h3 { font-size: 1.1rem; margin-bottom: .5rem; }
.mk-card p { color: #3d503f; margin: 0; }
.mk-footer { background: var(--brand-900); color: #cfe3d6; padding: 2rem 2.2rem 1.4rem; font-size: .9rem; }
.mk-footer a { color: #a9e8c4; border: none; }
.mk-footer-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1.5rem; max-width: 1050px; margin: 0 auto; }
.mk-legal { max-width: 1050px; margin: 1.2rem auto 0; padding-top: 1rem; border-top: 1px solid #2c5348; font-size: .78rem; opacity: .8; }

/* marketing sub-pages: hero band + staggered animated cards */
.mk-subhero { background: linear-gradient(135deg, var(--brand-700), var(--brand-900));
    color: #fff; text-align: center; padding: 4rem 1.5rem 3.2rem; }
.mk-subhero h1 { color: #fff; font-size: clamp(1.8rem, 4vw, 2.8rem); font-weight: 800; animation: fadeUp .5s ease-out both; }
.mk-subhero p { max-width: 620px; margin: .9rem auto 0; font-size: 1.1rem; color: #dceee2; animation: fadeUp .65s ease-out both; }
.mk-subhero strong { color: #a9e8c4; }
.mk-ico { font-size: 2rem; margin-bottom: .5rem; }
.mk-stagger { animation: fadeUp .5s ease-out both; }
.mk-stagger:nth-child(1) { animation-delay: .05s; }
.mk-stagger:nth-child(2) { animation-delay: .15s; }
.mk-stagger:nth-child(3) { animation-delay: .25s; }
.mk-stagger:nth-child(4) { animation-delay: .35s; }
/* Inline figures band (About, guide footer). NOT the same thing as the
   full-width marketing section `.mk-band` further down — these were both called
   `.mk-band`, and the `span` rule below was silently restyling every <span> in
   every marketing section (the pricing rows rendered 2rem, bold and green).
   Distinct name, distinct component; do not merge them again. */
.mk-statband { display: flex; justify-content: space-around; flex-wrap: wrap; gap: 1.5rem; margin-top: 2.5rem;
    background: linear-gradient(135deg, var(--brand-800), var(--brand-900)); border-radius: 22px;
    padding: 1.8rem 1.2rem; color: #cfe3d6; text-align: center; font-size: .9rem; }
.mk-statband span { display: block; font-size: 2rem; font-weight: 800; color: #a9e8c4; }
.mk-contact-grid { display: grid; grid-template-columns: 1fr 1.2fr; gap: 1.6rem; }
@media (max-width: 800px) { .mk-contact-grid { grid-template-columns: 1fr; } }
.mk-form label { font-weight: 600; color: var(--brand-800); }


/* FAQ accordion */
.mk-faq { background: #fff; border-radius: 14px; padding: 1rem 1.3rem; margin-bottom: .8rem;
    box-shadow: 0 4px 14px rgba(20,50,40,.06); }
.mk-faq summary { font-weight: 700; color: var(--brand-800); cursor: pointer; list-style: none; }
.mk-faq summary::before { content: "+"; display: inline-block; width: 1.4rem; color: var(--brand-500); font-weight: 800; }
.mk-faq[open] summary::before { content: "-"; }
.mk-faq p { margin: .6rem 0 0 1.4rem; color: #3d503f; }

/* skeleton loaders */
/* Was #e8efe9 / #f4f8f5 — green-era leftovers, on the first screen a signed-in
   user sees. A white sweep over brand-50 stays strictly on the blue ramp. */
.skel { background: linear-gradient(90deg, var(--brand-50) 25%, #fff 50%, var(--brand-50) 75%);
    background-size: 200% 100%; animation: skel 1.4s ease-in-out infinite; border-radius: var(--r-md); }
@keyframes skel { to { background-position: -200% 0; } }

/* ── icons ─────────────────────────────────────────────────────────────── */
.ci { flex: 0 0 auto; vertical-align: -.18em; }
.ctabs button .ci { opacity: .75; transition: opacity .16s ease, transform .16s ease; }
.ctabs button:hover .ci { opacity: 1; }
.ctabs button.active .ci { opacity: 1; transform: scale(1.06); }

/* ── motion layer ──────────────────────────────────────────────────────────
   Everything here is decorative. It is all wrapped so that a visitor who has
   asked their OS for reduced motion gets a static page — an accessibility
   requirement, and the animations are pure ornament, so nothing is lost. */

/* animated gradient headline */
.grad-text {
    background: linear-gradient(100deg, #0D47A1, #2196F3, #64B5F6, #0D47A1);
    background-size: 300% 100%;
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
    animation: gradSlide 8s ease-in-out infinite;
}
@keyframes gradSlide {
    0%, 100% { background-position:   0% 50%; }
    50%      { background-position: 100% 50%; }
}

/* staggered entrance — set --i on each child to order the reveal */
.reveal > * { animation: riseIn .6s cubic-bezier(.22,.8,.3,1) both; animation-delay: calc(var(--i, 0) * 90ms); }
@keyframes riseIn {
    from { opacity: 0; transform: translateY(18px); filter: blur(4px); }
    to   { opacity: 1; transform: none; filter: none; }
}

/* word-by-word headline reveal */
.words span { display: inline-block; animation: wordIn .55s cubic-bezier(.22,.8,.3,1) both; animation-delay: calc(var(--w, 0) * 70ms); }
@keyframes wordIn {
    from { opacity: 0; transform: translateY(22px) rotate(2deg); }
    to   { opacity: 1; transform: none; }
}

/* reveal on scroll — JS adds .in when the element enters the viewport */
.on-scroll { opacity: 0; transform: translateY(26px); transition: opacity .7s ease, transform .7s cubic-bezier(.22,.8,.3,1); }
.on-scroll.in { opacity: 1; transform: none; }

/* drifting aurora blobs for dark marketing sections */
.aurora { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
.aurora i {
    position: absolute; display: block; border-radius: 50%;
    filter: blur(70px); opacity: .5; animation: drift 22s ease-in-out infinite;
}
.aurora i:nth-child(1) { width: 460px; height: 460px; background: #0D47A1; top: -120px; left: -80px; }
.aurora i:nth-child(2) { width: 380px; height: 380px; background: #2196F3; bottom: -140px; right: -60px; animation-delay: -7s; }
.aurora i:nth-child(3) { width: 300px; height: 300px; background: #64B5F6; top: 40%; left: 55%; animation-delay: -14s; }
@keyframes drift {
    0%, 100% { transform: translate(0, 0) scale(1); }
    33%      { transform: translate(40px, -30px) scale(1.12); }
    66%      { transform: translate(-30px, 25px) scale(.94); }
}

/* faint tech grid */
.techgrid {
    position: absolute; inset: 0; z-index: 0; pointer-events: none;
    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: 56px 56px;
    mask-image: radial-gradient(ellipse at 50% 0%, #000 40%, transparent 78%);
}

/* dark marketing shell */
.night { position: relative; background: var(--night-900); color: #E6EAF5; overflow: hidden; }
.night h1, .night h2, .night h3 { color: #fff; }
.night .lead, .night p { color: #A9B4CE; }
.night .card, .night .panel {
    background: rgba(255,255,255,.04); border: 1px solid var(--night-line);
    border-radius: 18px; backdrop-filter: blur(8px);
}
.night .panel:hover { border-color: rgba(13,71,161,.45); transform: translateY(-3px); }
.night .panel { transition: transform .25s ease, border-color .25s ease; }

/* gradient CTA */
.btn-grad {
    background: var(--grad); color: #fff; border: 0; font-weight: 700;
    border-radius: 14px; padding: .7rem 1.4rem;
    box-shadow: 0 10px 30px rgba(109, 92, 255, .35);
    transition: transform .18s ease, box-shadow .18s ease, filter .18s ease;
}
.btn-grad:hover { transform: translateY(-2px); box-shadow: 0 14px 38px rgba(13,71,161,.5); filter: brightness(1.06); color:#fff; }

/* counters / stat tiles on dark */
.stat { font-size: 2.2rem; font-weight: 800; line-height: 1; }
.stat.grad-text { display: inline-block; }

@media (prefers-reduced-motion: reduce) {
    .grad-text, .aurora i { animation: none !important; }
    .reveal > *, .words span { animation: none !important; opacity: 1 !important; transform: none !important; filter: none !important; }
    .on-scroll { opacity: 1 !important; transform: none !important; transition: none !important; }
}

/* ── marketing layout ──────────────────────────────────────────────────── */
.mk-wrap { position: relative; z-index: 1; max-width: 1120px; margin: 0 auto; padding: 0 1.25rem; }
.mk-band { padding: 5.5rem 0; border-top: 1px solid var(--night-line); }
.mk-band.alt { background: var(--night-800); }
.mk-band h2 { font-size: clamp(1.7rem, 3.4vw, 2.5rem); margin-bottom: .6rem; letter-spacing: -.02em; }
.mk-band .lead { font-size: 1.08rem; margin-bottom: 2.5rem; max-width: 62ch; }

.mk-hero-x { padding: 6.5rem 0 5rem; }
.hero-inner { position: relative; z-index: 1; max-width: 940px; margin: 0 auto; padding: 0 1.25rem; text-align: center; }
.hero-inner h1 {
    font-size: clamp(2.3rem, 6vw, 4.1rem); line-height: 1.05;
    letter-spacing: -.035em; margin: 1.1rem 0 1.2rem;
}
.hero-inner .lead { font-size: 1.15rem; max-width: 68ch; margin: 0 auto 2rem; color: #D7DEF3; }
.hero-inner .lead strong { color: #FFFFFF; }
.pill {
    display: inline-flex; align-items: center; gap: .5rem;
    padding: .45rem .95rem; border-radius: 999px;
    background: rgba(255,255,255,.06); border: 1px solid var(--night-line);
    font-size: .84rem; color: #C7D0E6;
}
.mk-cta { display: flex; gap: .8rem; justify-content: center; flex-wrap: wrap; }
.mk-cta .btn-grad { display: inline-flex; align-items: center; gap: .5rem; }
.hero-stats {
    display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.25rem;
    max-width: 760px; margin: 3.5rem auto 0;
}
.hero-stats > div { text-align: center; }
.hero-stats small { display: block; color: #C6CFE8; font-size: .85rem; font-weight: 600; margin-top: .4rem; letter-spacing: .01em; }

.mk-grid  { display: grid; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); gap: 1.1rem; }
.mk-grid3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.5rem; }
.mk-grid3 b {
    display: inline-grid; place-items: center; width: 42px; height: 42px;
    border-radius: 12px; background: var(--grad); color: #fff; font-size: 1.05rem; margin-bottom: .7rem;
}
.ico-badge {
    display: inline-grid; place-items: center; width: 44px; height: 44px;
    border-radius: 13px; background: rgba(13,71,161,.16);
    border: 1px solid rgba(13,71,161,.3); color: #90CAF9;
}

/* ── pricing calculator ────────────────────────────────────────────────────
   Deliberately NOT built on `.panel`: that class lifts on hover, which fights a
   control you drag. This card stays still and puts its emphasis on the figure. */
.pr-calc {
    max-width: 620px; margin: 0 auto; padding: 2rem 2rem 1.75rem;
    background: rgba(255,255,255,.04); border: 1px solid var(--night-line);
    border-radius: 20px; text-align: center;
}
.pr-calc-top {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 1rem; margin-bottom: 1.1rem; text-align: left;
}
.pr-calc-top label { font-weight: 600; color: #E6EAF5; font-size: 1rem; }
.pr-count {
    flex: none; min-width: 3.6ch; padding: .3rem .7rem; border-radius: 10px;
    background: rgba(13,71,161,.16); border: 1px solid rgba(13,71,161,.35);
    color: #fff; font-weight: 800; font-size: 1.05rem; font-variant-numeric: tabular-nums;
}

/* Track drawn as a gradient fill up to the thumb, set from JS via --pr-fill. */
.pr-range {
    -webkit-appearance: none; appearance: none; width: 100%; height: 8px;
    border-radius: 999px; outline: none; cursor: pointer;
    background: linear-gradient(90deg, #0D47A1 0%, #2196F3 var(--pr-fill, 1%),
                rgba(255,255,255,.10) var(--pr-fill, 1%), rgba(255,255,255,.10) 100%);
}
.pr-range::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none; width: 26px; height: 26px;
    border-radius: 50%; background: #fff; border: 0; cursor: grab;
    box-shadow: 0 3px 12px rgba(0,0,0,.45), 0 0 0 4px rgba(13,71,161,.30);
    transition: transform .12s ease, box-shadow .12s ease;
}
.pr-range::-moz-range-thumb {
    width: 26px; height: 26px; border-radius: 50%; background: #fff; border: 0; cursor: grab;
    box-shadow: 0 3px 12px rgba(0,0,0,.45), 0 0 0 4px rgba(13,71,161,.30);
    transition: transform .12s ease, box-shadow .12s ease;
}
.pr-range:hover::-webkit-slider-thumb   { transform: scale(1.12); }
.pr-range:hover::-moz-range-thumb       { transform: scale(1.12); }
.pr-range:active::-webkit-slider-thumb  { cursor: grabbing; box-shadow: 0 3px 12px rgba(0,0,0,.45), 0 0 0 7px rgba(13,71,161,.35); }
.pr-range:active::-moz-range-thumb      { cursor: grabbing; box-shadow: 0 3px 12px rgba(0,0,0,.45), 0 0 0 7px rgba(13,71,161,.35); }
.pr-range:focus-visible                 { box-shadow: 0 0 0 3px rgba(33,150,243,.45); }

.pr-scale {
    display: flex; justify-content: space-between; margin-top: .45rem;
    font-size: .78rem; color: #7C88A6;
}

.pr-figures {
    display: flex; align-items: baseline; justify-content: center; gap: 1.5rem;
    flex-wrap: wrap; margin: 1.6rem 0 .35rem;
}
.pr-fig small { display: block; color: #8E9BB8; font-size: .82rem; margin-top: .3rem; }
.pr-total {
    display: inline-block; font-size: clamp(2.6rem, 7vw, 3.6rem);
    font-weight: 800; line-height: 1; letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.pr-fig-sub { padding-left: 1.5rem; border-left: 1px solid var(--night-line); }
.pr-each { font-size: 1.35rem; font-weight: 700; color: #C7D0E6; font-variant-numeric: tabular-nums; }
.pr-cta { margin-top: 1.4rem; display: inline-flex; align-items: center; gap: .5rem; }
.pr-note { margin: .9rem 0 0; font-size: .84rem; color: #7C88A6; }

/* Rate bands — the tier the slider currently sits in lights up. */
.pr-rates {
    display: grid; grid-template-columns: repeat(4, 1fr); gap: .75rem;
    max-width: 760px; margin: 2.25rem auto 0;
}
.pr-rate {
    padding: .95rem .7rem; border-radius: 14px; text-align: center;
    background: rgba(255,255,255,.03); border: 1px solid var(--night-line);
    transition: background .2s ease, border-color .2s ease, transform .2s ease;
}
.pr-rate b { display: block; font-size: .82rem; color: #8E9BB8; font-weight: 600; letter-spacing: .02em; }
.pr-rate-fig { display: block; margin: .3rem 0 .15rem; font-size: 1.5rem; font-weight: 800; color: #fff; }
.pr-rate small { display: block; font-size: .74rem; color: #7C88A6; line-height: 1.3; }
.pr-rate.on {
    background: rgba(13,71,161,.14); border-color: rgba(13,71,161,.5); transform: translateY(-2px);
}
.pr-rate.on b, .pr-rate.on small { color: #BBDEFB; }

.pr-taper { max-width: 620px; margin: 1.1rem auto 0; text-align: center; font-size: .86rem; color: #8E9BB8; }
.pr-taper a { color: #90CAF9; }

.pr-feats {
    list-style: none; padding: 0; max-width: 820px;
    margin: 2.25rem auto 0; display: grid; gap: .5rem 1.4rem;
    grid-template-columns: repeat(3, 1fr);
}
.pr-feats li { display: flex; align-items: center; gap: .5rem; font-size: .9rem; color: #C7D0E6; }
.pr-feats .ci { color: #2196F3; flex: none; }
.pr-allin { text-align: center; font-size: .86rem; color: #7C88A6; margin: 1.1rem 0 0; }

@media (max-width: 720px) {
    .pr-calc { padding: 1.5rem 1.25rem 1.35rem; }
    .pr-calc-top { flex-direction: column; align-items: flex-start; gap: .5rem; }
    .pr-rates { grid-template-columns: repeat(2, 1fr); }
    .pr-feats { grid-template-columns: repeat(2, 1fr); }
    .pr-fig-sub { padding-left: 0; border-left: 0; }
}
@media (max-width: 420px) {
    .pr-feats { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
    .pr-range::-webkit-slider-thumb, .pr-range::-moz-range-thumb,
    .pr-rate { transition: none; }
}

.mk-faq { border-bottom: 1px solid var(--night-line); padding: 1rem 0; }
.mk-faq summary { cursor: pointer; font-weight: 600; color: #fff; }
.mk-faq p { margin: .7rem 0 0; }

@media (max-width: 640px) {
    .hero-stats { grid-template-columns: repeat(2, 1fr); }
    .mk-band { padding: 3.5rem 0; }
}

/* ── logo ──────────────────────────────────────────────────────────────── */
.cc-logo { display: inline-flex; align-items: center; gap: .55rem; }
.cc-logo .cc-word { font-weight: 600; letter-spacing: -.015em; font-size: 1.05rem; white-space: nowrap; }
.cc-logo .cc-word b { font-weight: 800; }
/* The wordmark sits on a dark ground in the marketing shell (the app header sets
   its own white in the top-nav block below). */
.night .cc-logo .cc-word { color: #fff; }

/* ── route-to-C hero (dashboard) ───────────────────────────────────────── */
.route-hero {
    display: flex; align-items: center; justify-content: space-between; gap: 1.5rem;
    background: linear-gradient(120deg, #E3F2FD 0%, #E3F2FD 100%);
    border: 1px solid #BBDEFB; border-radius: 18px;
    padding: 1.1rem 1.4rem; margin-bottom: 1.25rem;
    animation: riseIn .5s cubic-bezier(.22,.8,.3,1) both;
}
.route-hero.muted { background: var(--surface); border-color: var(--line); color: var(--muted); display: block; }
.rc-eyebrow { display: inline-flex; align-items: center; gap: .4rem; font-size: .78rem;
    font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--brand-700); }
.rc-headline { font-size: 1.25rem; font-weight: 600; color: var(--ink); margin: .3rem 0 .6rem; }
.rc-measures { display: flex; flex-wrap: wrap; gap: .4rem; }
.rc-chip {
    display: inline-flex; align-items: center; gap: .35rem;
    background: rgba(255,255,255,.85); border: 1px solid #BBDEFB;
    border-radius: 999px; padding: .3rem .7rem; font-size: .82rem; color: var(--ink);
}
.rc-chip .ci { color: var(--accent); }
.rc-chip em { font-style: normal; color: var(--muted); }
.rc-right { text-align: center; flex: 0 0 auto; }
.rc-band {
    display: grid; place-items: center; width: 62px; height: 62px; margin: 0 auto;
    border-radius: 16px; background: var(--grad); color: #fff;
    font-size: 1.9rem; font-weight: 800; line-height: 1;
    box-shadow: 0 8px 22px rgba(13,71,161,.35);
}
.rc-sap { font-size: .8rem; color: var(--muted); margin-top: .35rem; }
.rc-save { font-size: .8rem; font-weight: 700; color: #047857; }
@media (max-width: 640px) { .route-hero { flex-direction: column; align-items: flex-start; } }

/* ── top-nav shell ─────────────────────────────────────────────────────────
   Two-tier header: a deep-blue utility bar (brand, search, account) over a white
   tab strip, with the light workspace beneath. This replaced a fixed left rail —
   see the palette note at the top of this file for why the silhouette, not just
   the colour, had to change. */
.app-shell { display: block; min-height: 100vh; background: var(--page-bg); }

.app-header { position: sticky; top: 0; z-index: 30; }

/* Was a single 90deg gradient between two adjacent ramp stops — barely a gradient
   at all, and the flattest element on the page. Layered radial light plus the app's
   own .techgrid vocabulary gives the deep blue actual material. Every stop is an
   existing ramp value at low alpha, and all overlays are additive light on an
   already-dark ground, so white-on-blue contrast is unchanged. */
.hdr-top {
    position: relative; z-index: 1;
    display: flex; align-items: center; gap: 1rem;
    padding: .65rem 1.5rem;
    background:
        radial-gradient(120% 190% at 10% -60%, rgba(33,150,243,.55)  0%, rgba(33,150,243,0)  55%),
        radial-gradient( 90% 170% at 88% -45%, rgba(100,181,246,.28) 0%, rgba(100,181,246,0) 62%),
        linear-gradient(100deg, var(--brand-900) 0%, var(--brand-900) 44%, var(--brand-800) 100%);
    box-shadow: inset 0 -1px 0 rgba(255,255,255,.10);
}
/* Fine engineering grid, masked so it stops before the search field. */
.hdr-top::before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background-image: linear-gradient(rgba(255,255,255,.055) 1px, transparent 1px),
                      linear-gradient(90deg, rgba(255,255,255,.055) 1px, transparent 1px);
    background-size: 40px 40px;
    -webkit-mask-image: linear-gradient(90deg, #000 0%, transparent 32%);
            mask-image: linear-gradient(90deg, #000 0%, transparent 32%);
}
.hdr-top > * { position: relative; z-index: 1; }
.hdr-brand { text-decoration: none; flex: 0 0 auto; }
.hdr-brand .cc-word { color: #fff; }

/* min-width:0 on BOTH the flex item and the input is load-bearing, not tidying. A
   flex item defaults to min-width:auto, and an <input> contributes its intrinsic
   ~20-character width (~170px) as its min-content size — `width:100%` is a
   percentage, so it does not override that contribution. The field therefore
   refused to shrink below ~170px and shoved the brand + four action controls past
   the right edge of any viewport under ~430px, which is most phones in portrait. */
.hdr-search {
    display: flex; align-items: center; gap: .55rem; flex: 1 1 auto;
    min-width: 0; max-width: 440px;
    background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.18);
    border-radius: 10px; padding: .45rem .8rem; color: rgba(255,255,255,.7);
    transition: background .15s ease, border-color .15s ease;
}
.hdr-search .ci { flex: none; }
.hdr-search:focus-within { background: rgba(255,255,255,.22); border-color: rgba(255,255,255,.45); }
.hdr-search { border-radius: var(--r-sm); box-shadow: inset 0 1px 2px rgba(4,20,50,.18); }
.hdr-avatar { box-shadow: 0 0 0 1px rgba(255,255,255,.40), 0 2px 8px rgba(4,20,50,.35); }
.hdr-search input {
    border: 0; background: transparent; outline: none; width: 100%; min-width: 0;
    font-size: .92rem; color: #fff;
}
.hdr-search input::placeholder { color: rgba(255,255,255,.65); }

.hdr-actions { display: flex; align-items: center; gap: .5rem; margin-left: auto; }
.hdr-icon {
    display: inline-grid; place-items: center; width: 36px; height: 36px;
    border: 0; border-radius: 9px; background: rgba(255,255,255,.12);
    color: #fff; text-decoration: none; transition: background .15s ease;
}
.hdr-icon:hover { background: rgba(255,255,255,.26); color: #fff; }
.hdr-user { display: flex; align-items: center; gap: .55rem; padding-left: .35rem; }
.hdr-avatar {
    display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%;
    background: #fff; color: var(--brand-900); font-weight: 800; font-size: .8rem;
}
.hdr-name {
    font-size: .88rem; font-weight: 600; color: #fff; max-width: 170px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* White tab strip. The active tab is marked by an underline rather than a filled
   pill so the strip reads as navigation rather than a row of buttons. */
/* A sticky bar ending in a hard 1px cut is the classic unfinished-shell tell. The
   shadow draws the same hairline plus a soft cast onto the workspace below. */
.hdr-nav {
    display: flex; gap: .15rem; padding: 0 1.5rem;
    background: var(--surface); border-bottom: 0;
    box-shadow: 0 1px 0 var(--line), 0 10px 22px -16px rgba(13,71,161,.55);
    overflow-x: auto; scrollbar-width: none;
}
.hdr-nav::-webkit-scrollbar { display: none; }
.hdr-tab {
    position: relative; display: inline-flex; align-items: center; gap: .45rem;
    padding: .8rem .9rem; white-space: nowrap; text-decoration: none;
    font-size: .92rem; font-weight: 600; color: var(--muted);
    transition: color .15s ease;
}
.hdr-tab:hover { color: var(--brand-700); }
.hdr-tab.active { color: var(--brand-700); }

/* The underline now exists on EVERY tab at scaleX(0), so it draws in when NavLink
   toggles .active — the nav element persists across Blazor navigation, so the class
   change fires the transition. One rule gives both the hover preview and the active
   state; no keyframe, no JS, no second source of truth. */
.hdr-tab::after {
    content: ""; position: absolute; left: .55rem; right: .55rem; bottom: 0;
    height: 3px; border-radius: 3px 3px 0 0;
    background: linear-gradient(90deg, var(--brand-900), var(--brand-500));
    transform: scaleX(0); transform-origin: 50% 100%; opacity: 0;
    transition: transform .28s cubic-bezier(.22,.8,.3,1), opacity .2s ease;
}
.hdr-tab:hover::after  { transform: scaleX(1); opacity: .35; }
.hdr-tab.active::after { transform: scaleX(1); opacity: 1; }

.hdr-tab .ci        { opacity: .75; transition: transform .18s cubic-bezier(.22,.8,.3,1), opacity .15s ease; }
.hdr-tab:hover .ci  { opacity: 1; transform: translateY(-1px); }
.hdr-tab.active .ci { opacity: 1; transform: scale(1.08); }

/* .hdr-nav is overflow-x:auto, so a default outline gets clipped at the scroll edge. */
.hdr-tab:focus-visible {
    outline: none; border-radius: 8px 8px 0 0;
    color: var(--brand-700);
    box-shadow: inset 0 0 0 2px var(--brand-200);
}

.app-main .content { padding: 1.6rem 1.75rem 3rem; max-width: 1500px; margin: 0 auto; }

@media (max-width: 900px) {
    .hdr-top { padding: .55rem .9rem; gap: .6rem; }
    .hdr-name { display: none; }
    .hdr-search { max-width: none; }
    .hdr-nav { padding: 0 .6rem; }
    .app-main .content { padding: 1.1rem 1rem 2.5rem; }
}
@media (max-width: 560px) {
    .hdr-brand .cc-word { display: none; }
}

/* Tinted icon badge on light panels. Standalone on purpose: it used to be written
   as `.stat-tile .st-ico`, so the copy on the Landlord duties card silently got no
   styling at all. */
.st-ico {
    display: inline-grid; place-items: center; width: 38px; height: 38px;
    border-radius: 11px; background: var(--brand-50); color: var(--brand-700);
}

/* ── dashboard: hero verdict, filter tiles, chart, side column ─────────────
   Light metric cards under a hero score card, with a standing deadlines column on
   the right. Two rules govern everything below and must survive future edits:

   1. SEVERITY MEANS SOMETHING. Colour, elevation and motion appear ONLY when a
      count is non-zero — the C# helper Live(count, kind) is the single gate. A
      clean portfolio renders flat and silent, so the one real problem is then the
      only thing on screen carrying any weight.
   2. ENTRANCES USE fill-mode `backwards`, NEVER `both`. A finished `both`
      animation keeps transform:none applied forever and outranks non-!important
      hover rules, silently killing every :hover lift on the page. */

/* ── page bar ─────────────────────────────────────────────────────────────── */
.page-bar {
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap;
    padding-bottom: .85rem; margin-bottom: var(--gap-band);
    border-bottom: 1px solid var(--line);
}
.pb-eyebrow {
    display: block; font-size: .72rem; font-weight: 700; letter-spacing: .08em;
    text-transform: uppercase; color: var(--muted); font-variant-numeric: tabular-nums;
}
.pb-title { margin: .18rem 0 0; font-size: 1.45rem; line-height: 1.15; letter-spacing: -.02em; }
.pb-actions { display: flex; align-items: center; gap: .5rem; }
.pb-actions .btn { display: inline-flex; align-items: center; gap: .4rem; }
@media (max-width: 620px) { .pb-actions { width: 100%; } .pb-actions .btn { flex: 1 1 auto; justify-content: center; } }

/* ── section bands ────────────────────────────────────────────────────────── */
.dash-band { margin-bottom: var(--gap-band); }
.content > .dash-band:last-child { margin-bottom: 0; }
.band-head {
    display: flex; align-items: baseline; gap: .7rem; flex-wrap: wrap;
    margin: 0 0 .7rem; padding: 0 .15rem;
}
.band-head h2 {
    margin: 0; font-size: .82rem; font-weight: 800; letter-spacing: .06em;
    text-transform: uppercase; color: var(--brand-900);
}
.band-count { font-size: .78rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ── score card: the page's hero surface ──────────────────────────────────
   Was character-for-character the same recipe as .metric (1px --line, 14px
   radius, white), so the headline object had zero hierarchy over the tiles below
   it. Hierarchy is a material problem before it is a layout one. */
.score-card {
    position: relative; overflow: hidden;
    display: flex; align-items: center; gap: 1.4rem;
    padding: 1.35rem 1.5rem 1.35rem 1.6rem;
    margin-bottom: var(--gap-card);
    border: 1px solid var(--line); border-radius: var(--r-lg);
    background: radial-gradient(85% 150% at 0% 0%, var(--brand-50) 0%, rgba(227,242,253,0) 60%),
                var(--surface);
    box-shadow: var(--e2);
}
/* The one permanent accent on the page. BRAND blue, never a status colour — it
   marks importance, not urgency, so it can never be mistaken for severity. */
.score-card::before {
    content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
    background: var(--grad);
}

.gauge { flex: 0 0 auto; position: relative; width: 104px; height: 104px; }
/* Dial face. inset:14px clears the ring (inner diameter about 81.5px at r=52,
   stroke 10, scaled 104/120) — do not shrink without re-deriving that. */
.gauge::before {
    content: ""; position: absolute; inset: 14px; border-radius: 50%;
    background: #fff;
    box-shadow: inset 0 0 0 1px rgba(13,71,161,.07), 0 1px 3px rgba(13,71,161,.10);
}
.gauge svg { width: 100%; height: 100%; transform: rotate(-90deg); position: relative; z-index: 1; overflow: visible; }
.gauge circle { fill: none; stroke-width: 10; stroke-linecap: round; }
.g-track { stroke: var(--brand-50); }

/* --pct is set inline at DOM-insert time, so the transition below has never once
   fired — the page's hero moment has been invisible. The keyframe draws it from
   327 (the circumference at r=52). The transition is kept so later value changes
   still ease. drop-shadow is non-directional on purpose: the svg is rotated -90deg,
   so an offset shadow would resolve in that rotated space and point left. */
.g-fill {
    stroke: var(--ok-500); stroke-dasharray: 327;
    stroke-dashoffset: calc(327 - 327 * var(--pct, 0) / 100);
    animation: gaugeDraw 1.1s cubic-bezier(.22,.8,.3,1) .15s backwards;
    transition: stroke-dashoffset .9s cubic-bezier(.22,.8,.3,1), stroke .3s ease;
    filter: drop-shadow(0 0 6px rgba(16,185,129,.30));
}
@keyframes gaugeDraw { from { stroke-dashoffset: 327; } }
.gauge.warn .g-fill { stroke: var(--warn-500); filter: drop-shadow(0 0 6px rgba(245,158,11,.30)); }
.gauge.crit .g-fill { stroke: var(--crit-500); filter: drop-shadow(0 0 6px rgba(220,38,38,.30)); }

.g-mid { position: absolute; inset: 0; z-index: 2; display: grid; place-content: center; text-align: center; }
.g-num { font-size: 2.05rem; font-weight: 800; line-height: 1; letter-spacing: -.035em;
         color: var(--brand-900); font-variant-numeric: tabular-nums; }
.g-cap { font-size: .66rem; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); margin-top: .15rem; }

.score-meta { min-width: 0; flex: 1 1 auto; }
.score-status { font-size: 1.15rem; font-weight: 700; letter-spacing: -.012em;
                color: var(--brand-900); margin-bottom: .25rem; }
.score-status.s-ok   { color: var(--ok-700); }
.score-status.s-warn { color: var(--warn-700); }
.score-status.s-crit { color: var(--crit-700); }
.score-next { margin: 0; max-width: 62ch; font-size: .88rem; line-height: 1.45; color: var(--muted); }

/* Penalty exposure lives here rather than in the filter row: it is an outcome of
   the verdict, not a filter, and as a <div> styled like the four <button>s it
   looked clickable while doing nothing. */
.score-foot {
    flex: 0 0 auto; align-self: stretch;
    display: flex; flex-direction: column; justify-content: center; align-items: flex-end;
    gap: .12rem; text-align: right;
    padding-left: 1.4rem; border-left: 1px solid var(--line);
}
.sf-label { font-size: .68rem; font-weight: 700; letter-spacing: .07em;
            text-transform: uppercase; color: var(--muted); }
.sf-num   { font-size: 1.5rem; font-weight: 800; line-height: 1;
            color: var(--brand-900); font-variant-numeric: tabular-nums; }
/* A currency symbol at full display size is optically heavier than the digits and
   shoves the figure off the block's rhythm. No opacity — this is a unit on a legal
   exposure figure, not ornament. */
.sf-num .unit { font-size: .62em; font-weight: 700; vertical-align: .14em; margin-right: .06em; }
.sf-note  { font-size: .7rem; color: var(--muted); }
.score-foot.live-crit .sf-label,
.score-foot.live-crit .sf-num { color: var(--crit-700); }

@media (max-width: 820px) {
    .score-card { flex-wrap: wrap; }
    .score-foot { width: 100%; align-items: flex-start; text-align: left;
                  border-left: 0; border-top: 1px solid var(--line); padding: .8rem 0 0; }
}

/* ── filter tiles ─────────────────────────────────────────────────────────── */
.metric-row {
    display: grid; gap: var(--gap-card); margin-bottom: var(--gap-band);
    /* Explicit counts, never auto-fit: with 4 tiles auto-fit produces a 3+1 orphan
       at intermediate widths, the commonest reason a card grid reads as accidental. */
    grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (max-width: 900px) { .metric-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .metric-row { grid-template-columns: minmax(0, 1fr); } }

.metric {
    position: relative;
    display: block; width: 100%; text-align: left; font: inherit; cursor: pointer;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--r-md); padding: 1rem 1.1rem;
    transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.metric:hover { transform: translateY(-2px); border-color: var(--brand-200); box-shadow: 0 10px 24px rgba(15,23,42,.08); }

/* Every tile is a filter BUTTON and nothing but a 2px lift said so. The accent bar
   borrows the tab strip's underline language, so "this one is on" reads the same
   everywhere. Grows on the VERTICAL axis — a scaleX reveal on a 3px-wide bar is a
   three-pixel animation. Inset from the corners so .metric never needs
   overflow:hidden, which would clip the severity ping. */
.metric::before {
    content: ""; position: absolute; left: 0; top: .85rem; bottom: .85rem; width: 3px;
    border-radius: 0 3px 3px 0; background: var(--brand-500);
    transform: scaleY(0); transform-origin: top;
    transition: transform .18s cubic-bezier(.22,.8,.3,1);
}
.metric:hover::before, .metric:focus-visible::before, .metric.sel::before { transform: scaleY(1); }

.metric-top { display: flex; align-items: flex-start; justify-content: space-between; gap: .5rem; }
.metric-ico {
    flex: 0 0 auto; display: inline-grid; place-items: center;
    width: 30px; height: 30px; border-radius: 9px;
    background: var(--brand-50); color: var(--brand-500);
    transition: background .18s ease, color .18s ease, transform .18s cubic-bezier(.22,.8,.3,1);
}
.metric:hover .metric-ico { transform: scale(1.08); }
.metric.sel .metric-ico       { background: var(--brand-500); color: #fff; }
.metric.live-crit .metric-ico { background: var(--crit-50); color: var(--crit-700); }
.metric.live-warn .metric-ico { background: var(--warn-50); color: var(--warn-700); }
/* `trend` rises up-and-right — on a FAILURE count that reads as "improving". */
.ci.flip { transform: scaleY(-1); }

.metric-label {
    display: block; font-size: .72rem; font-weight: 700; letter-spacing: .07em;
    text-transform: uppercase; color: var(--muted);
}
.metric-num {
    display: block; margin-top: .45rem; font-size: 1.9rem; font-weight: 800;
    line-height: 1; letter-spacing: -.02em; color: var(--brand-900);
    font-variant-numeric: tabular-nums;
}
.metric-note { display: block; margin-top: .3rem; font-size: .78rem; color: var(--muted); }

/* Severity as tint AND depth — a red card literally sits higher off the page,
   adding emphasis without adding a hue. Only ever via Live(). */
.metric.live-crit {
    border-color: var(--crit-line); background: var(--crit-25);
    box-shadow: 0 1px 2px rgba(15,23,42,.05), 0 14px 30px -16px rgba(185,28,28,.45);
}
.metric.live-crit .metric-num, .metric.live-crit .metric-label { color: var(--crit-700); }
.metric.live-warn {
    border-color: var(--warn-line); background: var(--warn-25);
    box-shadow: 0 1px 2px rgba(15,23,42,.05), 0 14px 30px -16px rgba(146,64,14,.35);
}
.metric.live-warn .metric-num, .metric.live-warn .metric-label { color: var(--warn-700); }
.metric.live-crit:hover { box-shadow: 0 1px 2px rgba(15,23,42,.05), 0 20px 40px -18px rgba(185,28,28,.55); }
.metric.live-warn:hover { box-shadow: 0 1px 2px rgba(15,23,42,.05), 0 20px 40px -18px rgba(146,64,14,.45); }

/* Selection uses OUTLINE, not box-shadow: the old .metric.sel box-shadow sat before
   .metric.live-crit at equal specificity, so a selected critical tile silently lost
   its selection ring. Must stay AFTER the .live-* rules. */
.metric.sel { outline: 2px solid var(--brand-500); outline-offset: 1px; }
/* .metric previously had no focus styling at all — keyboard users got a raw UA
   outline on a 14px-radius card. */
.metric:focus-visible { outline: 3px solid rgba(33,150,243,.45); outline-offset: 2px; }
/* A slight compress reads as "button"; a lift reads as "card". */
.metric:active { transform: translateY(0) scale(.985); transition-duration: .08s; }

/* ── one severity beat ────────────────────────────────────────────────────
   After the entrance settles, the only thing still moving on a clean dashboard is
   a real, actionable, non-zero critical count — so motion itself becomes signal.
   ONE iteration: a looping pulse on a tool people keep open all day becomes
   wallpaper within a day. Deliberately not given to .live-warn — if amber also
   beats, the beat stops distinguishing anything. */
.metric.live-crit::after {
    content: ""; position: absolute; inset: -1px; border-radius: 15px; pointer-events: none;
    animation: critPing .9s cubic-bezier(.22,.8,.3,1) .55s 1 backwards;
}
@keyframes critPing {
    0%   { box-shadow: 0 0 0 0    rgba(220,38,38,.32); }
    100% { box-shadow: 0 0 0 12px rgba(220,38,38,0);   }
}

/* ── arrival sequence ─────────────────────────────────────────────────────
   Home.razor is InteractiveServer: its body enters the DOM in ONE SignalR diff
   after OnInitializedAsync resolves, so a plain CSS animation on a fresh node
   fires at exactly the right instant — zero JS. Note reveal.js CANNOT serve this
   page: it binds on DOMContentLoaded / 'enhancedload' / load, none of which an
   interactive render raises, so .on-scroll here would sit at opacity 0 forever.
   Delays read top-down in severity order, so the ORDER is the hierarchy. */
.score-card,
.metric-row .metric,
.dash-side .side-card,
.epc-card,
.dash-band {
    animation: fadeUp .42s cubic-bezier(.22,.8,.3,1) backwards;
}
.score-card                        { animation-delay: .02s; }
.metric-row .metric:nth-child(1)   { animation-delay: .10s; }
.metric-row .metric:nth-child(2)   { animation-delay: .16s; }
.metric-row .metric:nth-child(3)   { animation-delay: .22s; }
.metric-row .metric:nth-child(4)   { animation-delay: .28s; }
.dash-side .side-card:nth-child(1) { animation-delay: .16s; }
.dash-side .side-card:nth-child(2) { animation-delay: .24s; }
.epc-card                          { animation-delay: .30s; }
.dash-band                         { animation-delay: .34s; }

/* ── grid: chart + aside only ─────────────────────────────────────────────── */
.dash-grid {
    display: grid; gap: var(--gap-card); align-items: start;
    grid-template-columns: minmax(0, 1fr) 340px;
    margin-bottom: var(--gap-band);
}
.dash-main { min-width: 0; }
.dash-side { display: grid; gap: var(--gap-card); }

/* ── 2030 chart ───────────────────────────────────────────────────────────
   Was 20 lines of inline style= in the pre-rebrand GREEN palette, and the one
   element whose `transition: width` no media query could reach (it also never
   fired, width being set inline at insert). Not .border.rounded: that carries a
   hover lift, and a chart you cannot click must not move under the cursor. */
.epc-card {
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--r-lg); box-shadow: var(--e1);
    padding: 1.1rem 1.25rem 1.2rem;
}
.epc-head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.epc-head strong { font-size: .95rem; letter-spacing: -.012em; color: var(--brand-900); }
.epc-legend { font-size: .76rem; color: var(--muted); font-variant-numeric: tabular-nums; }
/* Capped: height used to be linear in portfolio size, so the more properties you
   owned the further the table you came for was pushed below the fold. */
.epc-plot {
    margin-top: .8rem; max-height: 19rem; overflow-y: auto;
    scrollbar-gutter: stable; padding-right: .2rem;
}
.epc-row {
    display: grid; grid-template-columns: minmax(0, 180px) minmax(0, 1fr) 78px;
    align-items: center; column-gap: .65rem; padding: .28rem 0;
}
.epc-addr { font-size: .8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.epc-track {
    position: relative; height: 14px; border-radius: 999px; overflow: hidden;
    background: var(--brand-50);
    box-shadow: inset 0 1px 2px rgba(13,71,161,.12), inset 0 0 0 1px rgba(13,71,161,.05);
}
/* Square-ended fill inside a pill track with overflow:hidden — the track supplies
   the left cap, so scaleX cannot distort a radius. width stays declared (it is the
   real value); only the transform animates, on the compositor. */
.epc-fill {
    display: block; height: 100%; width: calc(var(--r, 0) * 1%);
    transform-origin: left center;
    animation: barGrow .5s cubic-bezier(.22,.8,.3,1) backwards;
    animation-delay: calc(.30s + var(--i, 0) * 30ms);
}
@keyframes barGrow { from { transform: scaleX(0); } }
.epc-fill.ok   { background: var(--ok-500); }
.epc-fill.warn { background: var(--warn-500); }
.epc-fill.crit { background: var(--crit-500); }
/* The old marker was left:69% of a wrapper that ALSO spanned the address and value
   columns, so the "2030 line" never pointed at band C on the bars it labels. Drawn
   inside the track, 69% is 69% of the bar scale by construction. Brand-dashed, not
   red: it marks a threshold, not a failure. */
.epc-track::after {
    content: ""; position: absolute; left: 69%; top: 0; bottom: 0; z-index: 2;
    /* brand-500 rather than -300: this is the reference the whole chart is read
       against, and at 1px on a brand-50 track the lighter step was barely there. */
    border-left: 1px dashed var(--brand-500);
}
.epc-val { display: flex; align-items: center; justify-content: flex-end; gap: .3rem;
           font-size: .8rem; font-variant-numeric: tabular-nums; }
.epc-val .sap { font-weight: 600; color: var(--muted); }
@media (max-width: 620px) { .epc-row { grid-template-columns: minmax(0, 110px) minmax(0, 1fr) 68px; } }

/* ── EPC band chip ────────────────────────────────────────────────────────
   `text-bg-dark` rendered band A and band G as the identical blue pill, so the
   colour carried zero information. Narrowed to ONE exception: only F and G (which
   cannot be let at all) redden. A full ramp would duplicate the Status and Score
   columns two cells away and fire green unconditionally on every compliant row. */
.band {
    display: inline-grid; place-items: center; min-width: 26px; height: 22px; padding: 0 .4rem;
    border-radius: 7px; font-size: .78rem; font-weight: 800; line-height: 1;
    font-variant-numeric: tabular-nums;
    background: var(--brand-50); color: var(--brand-800); border: 1px solid var(--brand-100);
}
.band.b-crit { background: var(--crit-50); color: var(--crit-700); border-color: var(--crit-line); }

/* ── side column ──────────────────────────────────────────────────────────── */
.side-card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; }
.side-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: .8rem 1rem; border-bottom: 1px solid var(--line);
    font-size: .82rem; font-weight: 800; letter-spacing: .05em;
    text-transform: uppercase; color: var(--brand-900);
}
.side-head a { font-size: .75rem; font-weight: 600; text-transform: none; letter-spacing: 0; }
.side-list { list-style: none; padding: 0; margin: 0; }
.side-list li { display: flex; gap: .7rem; padding: .65rem 1rem; border-bottom: 1px solid var(--line-soft); }
.side-list li:last-child { border-bottom: 0; }
.side-when {
    flex: 0 0 auto; display: grid; place-items: center; width: 40px; height: 40px;
    border-radius: 9px; background: var(--brand-50); color: var(--brand-700);
    font-size: .7rem; font-weight: 800; line-height: 1.1; text-align: center;
    font-variant-numeric: tabular-nums;
}
.side-when.due  { background: var(--warn-50); color: var(--warn-700); }
.side-when.over { background: var(--crit-50); color: var(--crit-700); }
/* Green lands only on completed steps, so the card gets quieter as onboarding
   finishes — the correct direction of travel. */
.side-when.step      { background: var(--brand-50); color: var(--brand-300); }
.side-when.step.done { background: var(--ok-50);    color: var(--ok-700); }
/* Stacked, not inline — the title and the property it belongs to are two facts,
   and running them together reads as one sentence ("EPC expires 12 Oak Road"). */
.side-body { min-width: 0; display: block; }
.side-title { display: block; font-size: .84rem; font-weight: 600; color: var(--ink); }
.side-sub { display: block; font-size: .76rem; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.side-empty { padding: 1rem; font-size: .84rem; color: var(--muted); }
.side-list a.side-title:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 2px; border-radius: 4px; }

/* ── property table ───────────────────────────────────────────────────────
   Bootstrap 5.3.3 paints an OPAQUE background-color on every CELL plus an
   `inset 0 0 0 9999px` accent shadow, so any background set on a <tr> is covered
   and never appears — which is why the pre-existing `.table-hover tbody tr:hover`
   has never once been visible. Zebra and hover must drive Bootstrap's own custom
   properties on the cells, and the severity rail must be a pseudo-element (a
   box-shadow on the td would replace the accent shadow zebra/hover ride on). */
.dash-table { table-layout: fixed; margin: 0; }
.dash-table td, .dash-table th { padding: .72rem .9rem; }
.dash-table td:first-child, .dash-table th:first-child { padding-left: 1.05rem; }
.dash-table td { font-size: .9rem; font-variant-numeric: tabular-nums; }
.dash-table > :not(caption) > * > * { border-bottom: 1px solid var(--line-soft); }
.dash-table > tbody > tr:last-child > * { border-bottom: 0; }
.dash-table .num { text-align: right; }
.dash-table tbody tr { cursor: pointer; }
.cell-addr { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A 2% brand tint, not grey — grey rows read as disabled on a status table. */
.dash-table > tbody > tr:nth-of-type(even) > * { --bs-table-bg-type: #FBFDFF; }
.dash-table > tbody > tr:hover > *        { --bs-table-bg-state: var(--brand-50); --bs-table-color-state: var(--ink); }
.dash-table > tbody > tr:focus-within > * { --bs-table-bg-state: var(--brand-50); }

/* Severity rail on the left edge, where the eye scans. Transparent by default —
   RowClass returns "" for a compliant row, so a clean table shows no rails at all
   and the problem rows are the only marked things on screen. */
.dash-table > tbody > tr > td:first-child { position: relative; }
.dash-table > tbody > tr.row-crit > td:first-child::before,
.dash-table > tbody > tr.row-warn > td:first-child::before {
    content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
}
.dash-table > tbody > tr.row-crit > td:first-child::before { background: var(--crit-500); }
.dash-table > tbody > tr.row-warn > td:first-child::before { background: var(--warn-500); }

/* A real link gives keyboard focus, Enter, middle-click, open-in-new-tab and a
   status-bar URL for free, with no ARIA at all. */
.row-open { font-weight: 600; color: var(--ink); text-decoration: none; }
.row-open:hover { color: var(--brand-700); text-decoration: underline; }

/* Remove is destructive and rare — it should not carry permanent equal weight with
   the data on 40 rows. .35 not 0: a fully hidden destructive control is
   undiscoverable, and touch devices have no hover to reveal it. */
.row-del { opacity: .35; transition: opacity .15s ease; }
.dash-table tbody tr:hover .row-del,
.dash-table tbody tr:focus-within .row-del { opacity: 1; }
@media (hover: none) { .row-del { opacity: 1; } }

/* ── status chips ─────────────────────────────────────────────────────────
   Own classes, NOT overrides of .text-bg-*. Those Bootstrap classes carry "do not
   use until repaired" and "unsafe — awaiting repair" on PropertyDetail across ~40
   sites; globally pastelling solid red would downgrade every hard-safety alarm in
   the product to fix a dashboard-local complaint. chip-crit keeps a ring and extra
   weight so it still outranks the others in the triage column. */
.chip-ok   { background: var(--ok-50);   color: var(--ok-700); }
.chip-warn { background: var(--warn-50); color: var(--warn-700); }
.chip-crit { background: var(--crit-50); color: var(--crit-700); font-weight: 700;
             box-shadow: inset 0 0 0 1.5px var(--crit-line); }
.chip-mute { background: var(--line);    color: var(--ink); }
.badge .ci { margin-right: .32em; vertical-align: -.16em; opacity: .9; }
.badge { font-variant-numeric: tabular-nums; }

/* ── empty states ─────────────────────────────────────────────────────────
   Both were `text-center p-N border rounded bg-light`, so they inherited the
   global `.border.rounded` treatment — an informational panel that reacts to the
   cursor promises an interaction that does not exist. */
.empty {
    display: grid; justify-items: center; gap: .45rem; text-align: center;
    padding: 3rem 1.5rem;
    background: radial-gradient(120% 130% at 50% 0%, var(--brand-50) 0%, rgba(227,242,253,0) 68%),
                var(--surface);
    /* Dashed = "a slot waiting to be filled". Brand blue, so an empty portfolio
       reads calm rather than like a warning. */
    border: 1px dashed var(--brand-200);
    border-radius: var(--r-lg);
}
/* The no-match state is a FILTER RESULT — nothing gets filled there, so it takes a
   solid border. A cast shadow under a broken outline looks like an artefact, so
   elevation lives only on the solid variant. */
.empty.compact {
    padding: 1.7rem 1.2rem; gap: .4rem;
    border-style: solid; border-color: var(--line);
    box-shadow: var(--e1);
}
.empty-ico {
    position: relative; display: grid; place-items: center; width: 60px; height: 60px;
    border-radius: 18px; margin-bottom: .5rem;
    background: linear-gradient(180deg, #fff 0%, var(--brand-50) 100%);
    border: 1px solid var(--brand-100); color: var(--brand-700);
    box-shadow: 0 8px 18px -10px rgba(13,71,161,.55), inset 0 1px 0 #fff;
}
.empty.compact .empty-ico { width: 44px; height: 44px; border-radius: 14px; margin-bottom: .3rem; }
.empty-badge {
    position: absolute; right: -6px; bottom: -6px; display: grid; place-items: center;
    width: 24px; height: 24px; border-radius: 50%;
    background: var(--brand-700); color: #fff; border: 2px solid var(--surface);
}
.empty-title { margin: 0; font-size: 1.05rem; font-weight: 700; letter-spacing: -.012em; }
.empty-sub   { margin: 0 0 .7rem; max-width: 46ch; font-size: .9rem; color: var(--muted); }

/* ── skeleton geometry ────────────────────────────────────────────────────
   The old placeholder was four tiles plus three bars, which reflowed into a score
   card + four tiles + chart — so the page visibly jumped on every load. A matched
   silhouette plus the entrance above reads as a crossfade without one being coded. */
.dash-skel { display: grid; gap: var(--gap-card); }
.dash-skel .skel-score { height: 148px; border-radius: var(--r-lg); }
.dash-skel .skel-tiles { display: grid; gap: var(--gap-card); grid-template-columns: repeat(4, minmax(0,1fr)); }
.dash-skel .skel-tile  { height: 110px; }
.dash-skel .skel-chart { height: 210px; border-radius: var(--r-lg); }
@media (max-width: 900px) { .dash-skel .skel-tiles { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 520px) { .dash-skel .skel-tiles { grid-template-columns: minmax(0,1fr); } }

@media (max-width: 1100px) {
    .dash-grid { grid-template-columns: minmax(0, 1fr); }
    /* Deadlines are time-critical; the chart is context. At the width most
       letting-agent laptops run, the triage list was landing under a 300px chart. */
    .dash-side { order: -1; }
}

.filter-bar {
    display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
    background: var(--brand-50); border: 1px solid var(--brand-100);
    border-radius: var(--r-sm); padding: .55rem .9rem; margin-bottom: .7rem;
    font-size: .9rem; color: var(--ink);
}
.filter-bar .ci { color: var(--brand-700); }

/* ── EPC Pathway Optimizer ─────────────────────────────────────────────────
   The one surface in this product a competitor cannot ship by reading the EPC
   register, so it is built to look like an instrument rather than a form. It is
   the only DARK console inside an otherwise light workspace, which is the whole
   trick: the page reads as data, this reads as a machine.

   It stays on the ClickCompliance blue ramp — the depth and the glow come from
   layering existing brand values at low alpha over --brand-900, never from a new
   hue. A tech look built by importing cyan would make it a different product's
   widget sitting in this one.

   Everything in one panel on purpose: the selector at the top and the engine's
   own RdSAP sequence at the bottom are the two halves of a single comparison,
   and they used to be separated by three tables. */
.epo {
    position: relative; isolation: isolate;
    border-radius: var(--r-lg); overflow: hidden;
    margin-bottom: var(--gap-band);
    background:
        radial-gradient(90% 120% at 12% -10%, rgba(33,150,243,.28) 0%, rgba(33,150,243,0) 55%),
        radial-gradient(80% 120% at 92% 8%, rgba(100,181,246,.16) 0%, rgba(100,181,246,0) 60%),
        linear-gradient(160deg, #0B1B3A 0%, #071229 60%, #050E1F 100%);
    border: 1px solid rgba(100,181,246,.20);
    box-shadow: 0 1px 2px rgba(5,14,31,.4), 0 26px 60px -30px rgba(13,71,161,.85);
    color: #E6EDFB;
}

/* Engineering grid, masked to the top-left so it never fights the readouts. The
   same vocabulary as .hdr-top and .techgrid — this is the app's own texture. */
.epo::before {
    content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
    background-image: linear-gradient(rgba(144,202,249,.055) 1px, transparent 1px),
                      linear-gradient(90deg, rgba(144,202,249,.055) 1px, transparent 1px);
    background-size: 34px 34px;
    -webkit-mask-image: radial-gradient(120% 100% at 0% 0%, #000 0%, transparent 62%);
            mask-image: radial-gradient(120% 100% at 0% 0%, #000 0%, transparent 62%);
}
.epo > * { position: relative; z-index: 1; }

.epo-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap; padding: 1.05rem 1.35rem;
    border-bottom: 1px solid rgba(144,202,249,.14);
}
.epo-eyebrow {
    display: block; font-size: .68rem; font-weight: 800; letter-spacing: .16em;
    text-transform: uppercase; color: #64B5F6;
}
.epo-title {
    margin: .15rem 0 0; font-size: 1.22rem; font-weight: 800;
    letter-spacing: -.022em; color: #fff;
}

/* Provenance badge. A product claim, not decoration — very few outfits have a
   working RdSAP implementation, and the page should say so exactly where the
   number appears rather than leave a landlord assuming it is a lookup. */
.epo-engine {
    display: inline-flex; align-items: center; gap: .45rem; flex: none;
    padding: .4rem .8rem; border-radius: 999px;
    background: rgba(33,150,243,.14); border: 1px solid rgba(100,181,246,.4);
    font-size: .72rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
    color: #BBDEFB; white-space: nowrap;
}
.epo-dot {
    width: 7px; height: 7px; border-radius: 50%; background: #4FC3F7;
    box-shadow: 0 0 0 0 rgba(79,195,247,.65);
    animation: epoPulse 2.4s ease-out infinite;
}
@keyframes epoPulse {
    0%   { box-shadow: 0 0 0 0 rgba(79,195,247,.6); }
    70%  { box-shadow: 0 0 0 7px rgba(79,195,247,0); }
    100% { box-shadow: 0 0 0 0 rgba(79,195,247,0); }
}

.epo-load {
    display: flex; align-items: center; gap: .85rem;
    padding: 1.5rem 1.35rem; color: #9FB3D9; font-size: .88rem;
}
.epo-load strong { display: block; color: #fff; font-size: .95rem; margin-bottom: .15rem; }

.epo-spin {
    width: 15px; height: 15px; border-radius: 50%; flex: none;
    border: 2px solid rgba(144,202,249,.25); border-top-color: #4FC3F7;
    animation: epoSpin .7s linear infinite;
}
@keyframes epoSpin { to { transform: rotate(360deg); } }

/* minmax(0,…) on both tracks: measure names are long enough to blow out an `auto`
   track and shove the readout panel off the console. */
.epo-body { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); }
.epo-pane { padding: 1.1rem 1.35rem 1.25rem; min-width: 0; }
.epo-measures { border-right: 1px solid rgba(144,202,249,.14); }

.epo-lbl {
    display: flex; align-items: center; gap: .5rem;
    margin: 0 0 .75rem; font-size: .67rem; font-weight: 800;
    letter-spacing: .15em; text-transform: uppercase; color: #7E96C4;
}
.epo-busy { display: inline-flex; align-items: center; gap: .35rem; text-transform: none;
    letter-spacing: 0; font-weight: 600; color: #64B5F6; }

/* ── measure cards ──────────────────────────────────────────────────────── */
.epo-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .4rem; }
.epo-m {
    position: relative; display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center; gap: .75rem; padding: .6rem .75rem;
    border: 1px solid rgba(144,202,249,.16); border-radius: 12px;
    background: rgba(255,255,255,.035); cursor: pointer;
    transition: border-color .16s ease, background .16s ease, box-shadow .16s ease;
}
.epo-m:hover { background: rgba(100,181,246,.10); border-color: rgba(100,181,246,.35); }
.epo-m.on {
    background: rgba(33,150,243,.16); border-color: rgba(79,195,247,.55);
    box-shadow: inset 3px 0 0 #4FC3F7, 0 6px 20px -12px rgba(33,150,243,.9);
}

.epo-m-ico {
    display: inline-grid; place-items: center; width: 36px; height: 36px; flex: none;
    border-radius: 10px; background: rgba(144,202,249,.12); color: #90CAF9;
    transition: background .16s ease, color .16s ease;
}
.epo-m.on .epo-m-ico { background: rgba(79,195,247,.9); color: #06203F; }
.epo-m-txt  { min-width: 0; }
.epo-m-name { display: block; font-size: .88rem; font-weight: 700; color: #EAF1FD; }
.epo-m-cost { display: block; margin-top: .12rem; font-size: .77rem; color: #8FA6D0;
    font-variant-numeric: tabular-nums; }

/* Modelled and rejected on THIS property. Flagged, never hidden — "we ran it and
   it does nothing here" is the useful answer, and dropping the row reads as a bug. */
.epo-m.dead .epo-m-name { color: #8FA6D0; }
.epo-dead {
    display: inline-block; margin-left: .35rem; padding: .05rem .4rem; border-radius: 999px;
    background: rgba(245,158,11,.18); color: #FCD34D;
    font-style: normal; font-size: .67rem; font-weight: 700;
}

/* Toggle switch. The real checkbox stays in the DOM and keeps its label
   association, keyboard operation and :checked state — only its painting changes. */
.epo-sw { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
.epo-sw-ui {
    position: relative; flex: none; width: 42px; height: 24px; border-radius: 999px;
    background: rgba(144,202,249,.18); border: 1px solid rgba(144,202,249,.24);
    transition: background .18s ease, border-color .18s ease;
}
.epo-sw-ui::after {
    content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px;
    border-radius: 50%; background: #C9DCF7; box-shadow: 0 1px 4px rgba(0,0,0,.5);
    transition: transform .2s cubic-bezier(.22,.8,.3,1), background .18s ease;
}
.epo-sw:checked ~ .epo-sw-ui {
    background: rgba(79,195,247,.85); border-color: rgba(79,195,247,.9);
}
.epo-sw:checked ~ .epo-sw-ui::after { transform: translateX(18px); background: #06203F; }
.epo-sw:focus-visible ~ .epo-sw-ui { outline: 2px solid #90CAF9; outline-offset: 3px; }

/* ── live readout ───────────────────────────────────────────────────────── */
.epo-live { display: flex; flex-direction: column; gap: .9rem; }

.epo-dial { position: relative; display: grid; place-items: center; margin: .2rem auto .1rem; width: 168px; height: 168px; }
.epo-ring { width: 100%; height: 100%; transform: rotate(-90deg); }
.epo-ring circle { fill: none; stroke-width: 9; stroke-linecap: round; }
.epo-ring-bg   { stroke: rgba(144,202,249,.14); }
.epo-ring-fill {
    stroke: #4FC3F7;
    filter: drop-shadow(0 0 6px rgba(79,195,247,.55));
    transition: stroke-dasharray .45s cubic-bezier(.22,.8,.3,1);
}
.epo-live.hit .epo-ring-fill { stroke: #34D399; filter: drop-shadow(0 0 7px rgba(52,211,153,.6)); }

.epo-dial-c { position: absolute; inset: 0; display: grid; place-content: center; text-align: center; }
.epo-dial-band {
    display: inline-block; padding: .1rem .5rem; border-radius: 7px; margin-bottom: .15rem;
    background: rgba(79,195,247,.9); color: #06203F; font-size: .8rem; font-weight: 800;
}
.epo-live.hit .epo-dial-band { background: #34D399; color: #04291D; }
.epo-dial-sap {
    display: block; font-size: 2.5rem; font-weight: 800; line-height: 1;
    letter-spacing: -.03em; color: #fff; font-variant-numeric: tabular-nums;
}
.epo-dial-c small { display: block; margin-top: .2rem; font-size: .64rem; font-weight: 700;
    letter-spacing: .12em; text-transform: uppercase; color: #7E96C4; }

/* SAP is already a 0-100 scale, so position == percentage directly. */
.epo-rail { position: relative; height: 8px; border-radius: 999px; background: rgba(144,202,249,.16); }
.epo-rail-fill {
    position: absolute; inset: 0 auto 0 0; border-radius: 999px;
    background: linear-gradient(90deg, #1565C0, #4FC3F7);
    transition: width .4s cubic-bezier(.22,.8,.3,1);
}
.epo-live.hit .epo-rail-fill { background: linear-gradient(90deg, #0E9F6E, #34D399); }
/* Ghost marker for where the property started, so a selection's effect reads as a
   DISTANCE rather than as a number that silently changed. */
.epo-rail-from { position: absolute; top: -3px; width: 2px; height: 14px; border-radius: 2px;
    background: #fff; opacity: .45; }
.epo-rail-tick { position: absolute; top: -4px; width: 2px; height: 16px; background: #90CAF9; opacity: .5; }
.epo-rail-tick i {
    position: absolute; top: -15px; left: 50%; transform: translateX(-50%);
    font-style: normal; font-size: .62rem; font-weight: 800; color: #7E96C4;
}

.epo-figs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .7rem .5rem; }
.epo-figs b {
    display: block; font-size: 1.05rem; font-weight: 800; line-height: 1.15;
    color: #fff; font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.epo-figs small {
    display: block; margin-top: .12rem; font-size: .63rem; font-weight: 700;
    letter-spacing: .1em; text-transform: uppercase; color: #7E96C4;
}

.epo-verdicts { display: flex; flex-direction: column; gap: .32rem; margin-top: auto; }
.epo-v { display: inline-flex; align-items: flex-start; gap: .4rem; font-size: .81rem; color: #9FB3D9; }
.epo-v a { color: inherit; text-decoration: underline; }
.epo-v.ok   { color: #6EE7B7; font-weight: 700; }
.epo-v.miss { color: #9FB3D9; font-weight: 600; }
.epo-v.warn { color: #FCD34D; font-weight: 700; }
.epo-v.crit { color: #FCA5A5; font-weight: 700; }
.epo-v.dim  { color: #7E96C4; font-size: .76rem; }
.epo-v abbr { text-decoration: underline dotted; cursor: help; }

/* ── goals ──────────────────────────────────────────────────────────────── */
.epo-goals {
    display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
    padding: .8rem 1.35rem; background: rgba(3,10,24,.5);
    border-top: 1px solid rgba(144,202,249,.14);
}
.epo-goal {
    border: 1px solid rgba(144,202,249,.22); background: rgba(255,255,255,.04);
    cursor: pointer; padding: .42rem .85rem; border-radius: 999px; white-space: nowrap;
    font-size: .82rem; font-weight: 600; color: #B7C8E6;
    transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.epo-goal:hover:not(:disabled) { border-color: rgba(100,181,246,.55); color: #fff; }
.epo-goal.on {
    background: rgba(79,195,247,.92); border-color: #4FC3F7; color: #06203F; font-weight: 800;
}
.epo-goal:disabled { opacity: .45; cursor: default; }

.epo-cap { display: inline-flex; align-items: center; gap: .42rem; font-size: .82rem;
    color: #B7C8E6; cursor: pointer; white-space: nowrap; }
.epo-cap input { width: 15px; height: 15px; accent-color: #4FC3F7; margin: 0; }

.epo-find {
    margin-left: auto; border: 0; cursor: pointer; white-space: nowrap;
    padding: .5rem 1.05rem; border-radius: 999px;
    background: linear-gradient(100deg, #1565C0, #29B6F6); color: #fff;
    font-size: .83rem; font-weight: 800; letter-spacing: .01em;
    box-shadow: 0 8px 22px -10px rgba(41,182,246,.9);
    transition: filter .15s ease;
}
.epo-find:hover:not(:disabled) { filter: brightness(1.12); }
.epo-find:disabled { opacity: .5; cursor: default; box-shadow: none; }

/* ── the answer ─────────────────────────────────────────────────────────── */
.epo-answer {
    display: flex; align-items: center; gap: .9rem 1.4rem; flex-wrap: wrap;
    padding: .95rem 1.35rem;
    background: linear-gradient(100deg, rgba(21,101,192,.5), rgba(41,182,246,.22));
    border-top: 1px solid rgba(144,202,249,.22); color: #fff;
}
.epo-answer.none { background: rgba(245,158,11,.14); color: #FCD34D; }
.epo-answer.none a { color: inherit; }
.epo-answer-lbl {
    display: block; font-size: .64rem; font-weight: 800; letter-spacing: .13em;
    text-transform: uppercase; opacity: .72;
}
.epo-answer-t { min-width: 0; }
.epo-answer-t strong { font-size: .96rem; font-weight: 800; }
.epo-answer-t span a { color: inherit; }
.epo-answer-figs { display: flex; gap: 1.1rem; flex-wrap: wrap; }
.epo-answer-figs b { display: block; font-size: 1rem; font-variant-numeric: tabular-nums; }
.epo-answer-figs small { display: block; font-size: .62rem; letter-spacing: .1em;
    text-transform: uppercase; opacity: .7; }

.epo-apply {
    margin-left: auto; flex: none; border: 1px solid rgba(255,255,255,.55);
    background: rgba(255,255,255,.14); color: #fff; cursor: pointer;
    padding: .46rem 1rem; border-radius: 999px; font-size: .82rem; font-weight: 800;
    transition: background .15s ease;
}
.epo-apply:hover:not(:disabled) { background: rgba(255,255,255,.26); }
.epo-apply:disabled { opacity: .5; cursor: default; }

.epo-answer-note {
    flex-basis: 100%; margin: 0; font-size: .71rem; opacity: .68;
    font-variant-numeric: tabular-nums;
}

/* ── engine sequence, same console ──────────────────────────────────────── */
.epo-seq { padding: 1.1rem 1.35rem 1.3rem; border-top: 1px solid rgba(144,202,249,.14); }
.epo-seq-head { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.epo-seq-head .epo-lbl { margin-bottom: 0; }
.epo-chip {
    display: inline-flex; align-items: center; padding: .22rem .6rem; border-radius: 999px;
    font-size: .7rem; font-weight: 700; cursor: help;
}
.epo-chip.ok   { background: rgba(52,211,153,.15); color: #6EE7B7; border: 1px solid rgba(52,211,153,.3); }
.epo-chip.warn { background: rgba(245,158,11,.15); color: #FCD34D; border: 1px solid rgba(245,158,11,.3); }
.epo-seq-note { margin: .5rem 0 .7rem; font-size: .78rem; color: #7E96C4; max-width: 74ch; }

/* Own table, not .table — that one is a light card with its own surface and
   elevation, and dropping it into a dark console leaves a white slab. */
.epo-seq-tbl { width: 100%; border-collapse: collapse; font-size: .84rem; }
.epo-seq-tbl th {
    padding: .5rem .7rem; text-align: left; white-space: nowrap;
    font-size: .64rem; font-weight: 800; letter-spacing: .11em; text-transform: uppercase;
    color: #7E96C4; border-bottom: 1px solid rgba(144,202,249,.18);
}
.epo-seq-tbl td {
    padding: .58rem .7rem; color: #D5E1F5; border-bottom: 1px solid rgba(144,202,249,.08);
    font-variant-numeric: tabular-nums;
}
.epo-seq-tbl tbody tr:last-child td { border-bottom: 0; }
.epo-seq-tbl tbody tr { transition: background .16s ease; }

/* The cross-highlight. This is why the two halves share a panel: switching a
   measure on above lights its row here, so "the certificate's order" and "the set
   I actually picked" are legible against each other at a glance. */
.epo-seq-tbl tbody tr.on td { background: rgba(79,195,247,.14); color: #fff; }
.epo-seq-tbl tbody tr.on td:first-child { box-shadow: inset 3px 0 0 #4FC3F7; }
.epo-seq-tbl tbody tr.reaches td { background: rgba(52,211,153,.10); }
.epo-seq-tbl tbody tr.on.reaches td { background: rgba(79,195,247,.16); }

.epo-tag {
    display: inline-block; padding: .1rem .45rem; border-radius: 999px; margin-right: .3rem;
    background: rgba(79,195,247,.22); color: #90CAF9;
    font-size: .66rem; font-weight: 700; white-space: nowrap;
}
.epo-tag.c { background: rgba(52,211,153,.2); color: #6EE7B7; }

.epo-band {
    display: inline-grid; place-items: center; min-width: 1.5em; padding: 0 .3em;
    border-radius: 6px; background: rgba(79,195,247,.9); color: #06203F;
    font-size: .76em; font-weight: 800;
}

/* The console keeps its own scrollbar treatment — the light .table-responsive
   card styling would paint a white slab behind these rows. */
.epo .table-responsive { background: transparent; box-shadow: none; border-radius: 0; }

@media (max-width: 900px) {
    .epo-body { grid-template-columns: minmax(0, 1fr); }
    .epo-measures { border-right: 0; border-bottom: 1px solid rgba(144,202,249,.14); }
    .epo-find, .epo-apply { margin-left: 0; }
}
@media (max-width: 480px) {
    .epo-head, .epo-goals, .epo-seq { padding-left: 1rem; padding-right: 1rem; }
    .epo-pane { padding-left: 1rem; padding-right: 1rem; }
    .epo-title { font-size: 1.05rem; }
    .epo-dial { width: 140px; height: 140px; }
    .epo-dial-sap { font-size: 2.1rem; }
    .epo-figs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .epo-goal { flex: 1 1 auto; text-align: center; }
    .epo-find { width: 100%; }
}

/* ══ reduced motion — the signed-in app ═══════════════════════════════════
   The previous version of this block killed .metric's TRANSITION but left the
   TRANSFORM, so a reduced-motion user still took an instant 2px jump on every
   hover; and the infinite skeleton shimmer, the worst offender in the file, was
   guarded by neither block. Colour, border and shadow all survive here, so no
   state information is lost — only movement. */
@media (prefers-reduced-motion: reduce) {

    /* 1. Entrances land immediately, in place, fully opaque. Everything listed
          uses fill-mode `backwards`, so `animation: none` returns the element to
          its natural state rather than stranding it at opacity 0 — the failure
          mode `both` would have caused. */
    article.content, .alert, .route-hero, .auth-hero,
    .score-card, .metric-row .metric, .dash-side .side-card,
    .epc-card, .dash-band, .epc-fill,
    .g-fill, .metric.live-crit::after, .skel {
        animation: none !important;
    }
    .score-card, .metric-row .metric, .dash-side .side-card,
    .epc-card, .dash-band { opacity: 1 !important; transform: none !important; }
    /* .epc-fill keeps its width: calc(var(--r) * 1%) — only the scaleX goes.
       .g-fill degrades correctly too: with the keyframe off, the declared
       stroke-dashoffset renders the arc already fully drawn. */
    .epc-fill { transform: none !important; }

    /* 2. Hover / press movement. */
    .metric:hover, .metric:active, .metric:hover .metric-ico,
    .border.rounded:hover, .btn-primary:hover, .btn-success:hover,
    .btn-grad:hover, .mk-card:hover, .night .panel:hover,
    .hdr-tab:hover .ci, .hdr-tab.active .ci, .ctabs button.active .ci {
        transform: none !important;
    }

    /* 3. Transitions off across the shell. */
    .metric, .metric::before, .metric-ico, .g-fill, .btn, .card, .border.rounded,
    .row-del, .hdr-search, .hdr-icon, .hdr-tab, .hdr-tab .ci, .hdr-tab::after,
    .epo-m, .epo-m-ico, .epo-goal, .epo-find, .epo-apply,
    .epo-sw-ui, .epo-sw-ui::after, .epo-rail-fill, .epo-ring-fill,
    .epo-seq-tbl tbody tr {
        transition: none !important;
    }

    /* The console's two infinite animations. The spinner is safe to stop outright —
       it sits beside a live "modelling…" label and a running count, so the busy state
       is still stated in words. The engine-badge pulse is pure decoration. */
    .epo-spin { animation: none !important; border-top-color: #4FC3F7; }
    .epo-dot  { animation: none !important; }

    /* 4. The infinite shimmer — previously unguarded, and precisely the animation
          a reduced-motion user most needs stopped. */
    .skel { background: var(--brand-50) !important; }
}
