/* ── Solvren design tokens — generated from the CONFIRMED design decision ──
   industry=general tone=approachable style=soft palette=default-blue type=system-sans source=derived
   Every contrast-sensitive value here is COMPUTED from the palette (design-tokens.ts) and
   re-asserted by design-quality-gate.ts. Do not replace one with a literal. */
:root{
  --brand-primary:#1D4ED8;--brand-ink:#ffffff;--brand-on-bg:#1d4ed8;--accent:#0F766E;--accent-ink:#ffffff;--accent-on-bg:#0f766e;--bg:#ffffff;--text:#111827;--muted:#6d7178;--surface:#ffffff;--surface-2:#f5f6f6;--border:#d1d2d4;--border-strong:#939598;
  --danger:#b42318;--danger-bg:#fef3f2;--success:#067647;--success-bg:#ecfdf3;
  --warning:#b54708;--warning-bg:#fffaeb;--info:#175cd3;--info-bg:#eff8ff;
  /* The TEXT colour for each status banner, derived to clear AA on that banner's own tinted
     background. Previously these were four hand-picked hexes (#0b3b8c, #054f34, #7a4a06, #7a271a)
     that happened to pass; derived, they cannot stop passing when a status hue is retuned. */
  --info-ink:#175cd3;
  --success-ink:#067647;
  --warning-ink:#b54708;
  --danger-ink:#b42318;
  --font-heading:system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-body:system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font:system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  /* Type, spacing and geometry are the STYLE's (style-css.ts). The type ladder is computed from the
     style's ratio and the spacing ladder from its density multiplier, so a style is a coherent scale
     rather than a list of numbers that were nudged until they looked right. */
  --step--1:0.8rem;--step-0:1rem;--step-1:1.25rem;--step-2:1.563rem;--step-3:1.953rem;--step-4:2.441rem;
  --leading-tight:1.2;--leading-normal:1.6;
  --heading-weight:650;--heading-tracking:-0.01em;
  --space-1:0.25rem;--space-2:0.5rem;--space-3:0.75rem;--space-4:1rem;--space-5:1.5rem;
  --space-6:2rem;--space-7:3rem;--space-8:4rem;
  --radius-sm:8px;--radius:14px;--radius-lg:20px;
  --rule-width:1px;
  --shadow-1:0 1px 2px rgba(16,24,40,.06),0 2px 6px rgba(16,24,40,.08);
  --shadow-2:0 8px 24px rgba(16,24,40,.12),0 2px 6px rgba(16,24,40,.06);
  --shadow:var(--shadow-1);
  --motion-fast:120ms;--motion-base:200ms;--ease:cubic-bezier(.2,.6,.3,1);
  --focus-ring:2px solid var(--brand-on-bg);--focus-offset:2px;
}
@media (prefers-color-scheme: dark){:root:not([data-theme="light"]){--brand-primary:#1D4ED8;--brand-ink:#ffffff;--brand-on-bg:#5378e1;--accent:#0F766E;--accent-ink:#ffffff;--accent-on-bg:#338b84;--bg:#0f1420;--text:#e6e9f2;--muted:#90949e;--surface:#1c212d;--surface-2:#272b37;--border:#3a3f4a;--border-strong:#5e636d}}
:root[data-theme="dark"]{--brand-primary:#1D4ED8;--brand-ink:#ffffff;--brand-on-bg:#5378e1;--accent:#0F766E;--accent-ink:#ffffff;--accent-on-bg:#338b84;--bg:#0f1420;--text:#e6e9f2;--muted:#90949e;--surface:#1c212d;--surface-2:#272b37;--border:#3a3f4a;--border-strong:#5e636d}
/* ONE reduced-motion rule, driving the motion tokens — rather than a per-component opt-out that the
   next component can forget to write. */
@media (prefers-reduced-motion: reduce){
  :root{--motion-fast:0ms;--motion-base:0ms}
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}
}

*{box-sizing:border-box}
/* DEFECT (human-found, 2026-07-29): THE FOOTER WAS NOT PINNED ON SHORT PAGES.
   There was no sticky-footer layout at all. The main element carried min-height:60vh, which is a guess
   dressed as a rule: on any page whose nav + 60vh main + footer came to less than the viewport, the
   footer floated up the page with dead background beneath it. It was invisible on content-heavy
   pages and obvious on the EMPTY list — which is the day-one state of every generated portal, so the
   most-seen state was the least-tested.
   The fix is the real layout: body is a full-height flex column and main takes the slack. The
   regression assertion is geometric (R1 — footer bottom >= viewport bottom on a minimal-content
   page), because no declaration-level check can see where a box actually lands. */
body{margin:0;font-family:var(--font-body);color:var(--text);background:var(--bg);line-height:var(--leading-normal);font-size:var(--step-0);min-height:100vh;display:flex;flex-direction:column}
h1,h2,h3,h4{font-family:var(--font-heading);line-height:var(--leading-tight);margin:0 0 var(--space-3)}
h1{font-size:var(--step-3)}h2{font-size:var(--step-2)}h3{font-size:var(--step-1)}
/* The skip link uses the ACCENT, not the brand: it is a distinct affordance that should not read
   as another brand button, and the accent is a derived, contrast-gated pair like the brand's. */
.skip-link{position:absolute;left:-999px;top:0;background:var(--accent);color:var(--accent-ink);padding:var(--space-2) var(--space-4);z-index:10}
.skip-link:focus{left:0}
:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset)}
.site-nav{border-bottom:1px solid var(--border)}
.nav-inner,.footer-inner{max-width:1100px;margin:0 auto;padding:var(--space-4);display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}
.brand{font-weight:700;color:var(--brand-on-bg);text-decoration:none}
.brand-logo{max-height:40px;width:auto;display:block}
.site-main{width:100%;max-width:1100px;margin:0 auto;padding:var(--space-5) var(--space-4);flex:1 0 auto}
/* The skip-link target is focused by SCRIPT on every skip. A ring there marks a region the user did
   not navigate to, so it is suppressed for programmatic focus only — a keyboard user still gets the
   ring, because :focus-visible is what fires for them. Never a blanket outline:none. */
.site-main:focus:not(:focus-visible){outline:none}
.hero{padding:var(--space-5) 0}
.list-head{display:flex;flex-wrap:wrap;gap:var(--space-4);align-items:center;justify-content:space-between;margin-bottom:var(--space-2)}
.records{list-style:none;padding:0}
.records li{padding:var(--space-2) 0;border-bottom:1px solid var(--border)}
.empty{color:var(--muted)}
a{color:var(--brand-on-bg)}
.btn{display:inline-block;padding:var(--space-3) var(--space-4);border:1px solid var(--brand-primary);border-radius:var(--radius);background:var(--brand-primary);color:var(--brand-ink);text-decoration:none;cursor:pointer;transition:filter var(--motion-fast) var(--ease)}
.btn:hover{filter:brightness(1.08)}
.nav-toggle{display:none;background:none;border:1px solid var(--brand-on-bg);color:var(--brand-on-bg);border-radius:var(--radius-sm);padding:var(--space-2) var(--space-3);cursor:pointer}
.nav-toggle:hover{background:var(--surface-2)}
@media(max-width:720px){.nav-toggle{display:inline-block}.site-main{padding:var(--space-4)}.site-nav .nav-inner{flex-wrap:wrap}}
/* component: design-tokens */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
/* component: dark-mode-toggle */
.theme-toggle{display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border:1px solid var(--border-strong);border-radius:var(--radius);background:var(--surface);color:var(--text);cursor:pointer;line-height:0}
.theme-toggle:hover{background:var(--surface-2)}
.theme-toggle .icon-sun,.theme-toggle .icon-moon{width:1.1rem;height:1.1rem;display:block}
/* DEFECT (human-found, 2026-07-29): THE ICON OFFERS THE ACTION, IT DOES NOT REPORT THE STATE.
   This block used to show the SUN in light mode and the MOON in dark mode — i.e. it drew the theme
   you were already looking at, so the control read as "you are in light mode" rather than "switch to
   dark mode". Every CSS-declaration check passed it, because nothing here is malformed; the rules
   were simply the wrong way round. That is semantics, and a declaration-level gate cannot see it —
   which is why the regression assertion for this lives in the rendered-DOM harness (R3). */
.theme-toggle .icon-sun{display:none}
:root[data-theme="dark"] .theme-toggle .icon-moon{display:none}
:root[data-theme="dark"] .theme-toggle .icon-sun{display:block}
@media (prefers-color-scheme: dark){:root:not([data-theme="light"]) .theme-toggle .icon-moon{display:none}:root:not([data-theme="light"]) .theme-toggle .icon-sun{display:block}}
/* component: nav-header-footer */
.nav-inner{gap:var(--space-4)}
.nav-actions{display:flex;align-items:center;gap:var(--space-2)}
/* NAV OVERFLOW STRATEGY (scale fixture, 2026-07-29): nav items derive from entities x actions, so
   their COUNT is an input, not a constant. With 8 entities the menu forced a 1084px minimum and
   the whole page scrolled horizontally at every viewport between 721px and 1083px — found by the
   rendered-DOM harness at 768 and 1024, never by any declaration check. min-width:0 lets the
   flex item shrink below its content.

   AMENDED 2026-07-29, second time, because the first fix traded one defect for another. Wrapping
   stopped the page scrolling sideways and made the nav grow DOWNWARD instead — which was fine at
   the label lengths of the day and became a FOUR-ROW block the moment the language fix lengthened
   them ("Applications" -> "My Grant Applications", "Track Grant Applications"). A layout whose
   correctness depends on how long the copy happens to be will break again the next time the copy
   improves, so the strategy no longer depends on it:

     nowrap + overflow-x:auto  ->  the menu is ALWAYS exactly one row, at any item count and any
                                   label length, and overflow becomes a scroll INSIDE the menu
                                   rather than growth in either axis of the document.

   The trade, stated: at high item counts some links are reachable only by scrolling the nav. That
   is worse than a menu that fits and better than a four-row block or a sideways-scrolling page,
   and the honest fix for "sixteen top-level destinations" is an information-architecture decision
   (A13), not a CSS one. R7-nav-rows in the rendered-DOM harness holds this to at most two rows. */
.site-menu{list-style:none;display:flex;flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;min-width:0;gap:var(--space-1);margin:0;padding:0}
.site-menu::-webkit-scrollbar{display:none}
.site-menu a{display:block;white-space:nowrap;padding:var(--space-2) var(--space-3);border-radius:var(--radius);color:var(--text);text-decoration:none}
.site-menu a:hover{background:var(--surface-2)}
.site-menu a[aria-current="page"]{color:var(--brand-on-bg);font-weight:600}
.site-footer{border-top:1px solid var(--border);margin-top:var(--space-6)}
.footer-inner{display:flex;flex-wrap:wrap;gap:var(--space-4);align-items:center;justify-content:space-between}
.footer-nav ul{list-style:none;display:flex;flex-wrap:wrap;gap:var(--space-4);margin:0;padding:0}
.footer-nav a{color:var(--muted);text-decoration:underline}
.footer-nav a:hover{color:var(--text)}
.footer-copy{margin:0;color:var(--muted)}
.account{display:flex;align-items:center;gap:var(--space-2)}
.account-name{color:var(--muted);max-width:14ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* DEFECT (human-found, 2026-07-29): the Sign in control WRAPPED onto two lines. It was sized from
   padding alone with nothing preventing a break inside its own label, so at the widths where the nav
   is tight the two words split and the button grew to two line-heights. white-space:nowrap is the
   fix; the regression assertion is geometric (R2 — no interactive element taller than one line-box),
   because "the label wrapped" is not visible in any CSS declaration. */
.account-link{padding:var(--space-2) var(--space-3);border-radius:var(--radius);color:var(--text);text-decoration:none;border:1px solid var(--border-strong);white-space:nowrap}
.account-link:hover{background:var(--surface-2)}
.account-link.signin{background:var(--brand-primary);border-color:var(--brand-primary);color:var(--brand-ink)}
.error-page,.static-page{max-width:60ch;padding:var(--space-6) 0}
.error-page .btn{margin-top:var(--space-4)}
/* Below 720px the menu stacks behind the Menu button. overflow-x is reset to visible here: a
   column of full-width links has nothing to scroll sideways, and leaving it on clips the focus
   ring of a keyboard-focused link against the menu's own edge. (No backticks in this block — it
   is inside a template literal.) */
@media(max-width:720px){.site-menu{display:none;flex-direction:column;overflow-x:visible;width:100%;order:3}.site-nav.open .site-menu{display:flex}.nav-inner{flex-wrap:wrap}.account-name{display:none}}
/* component: predictive-search */
/* The search row wraps to its own line rather than being crushed. With flex:1 and min-width:0 it
   could shrink below the intrinsic width of its own submit button, so on a narrow viewport a long
   page heading squeezed the form until the Search button hung off the right edge of the document
   — 40 findings at 375px, every one on a track screen, all invisible to a declaration check. A
   flex-basis makes the flex line break instead of over-shrinking. */
.typeahead{display:flex;gap:var(--space-2);align-items:center;flex:1 1 16rem;min-width:0;max-width:28rem}
.typeahead-input{flex:1;min-width:0;padding:var(--space-2) var(--space-3);border:1px solid var(--border-strong);border-radius:var(--radius);background:var(--surface);color:var(--text)}
.typeahead-input:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset)}
.typeahead-submit{padding:var(--space-2) var(--space-3);border:1px solid var(--brand-primary);background:var(--brand-primary);color:var(--brand-ink);border-radius:var(--radius);cursor:pointer}
.typeahead-submit:hover{filter:brightness(1.05)}
.typeahead-status{color:var(--muted);font-size:var(--step--1);min-height:1.2em}
/* component: data-table */
.table-wrap{overflow-x:auto;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface)}
.table-wrap:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset)}
.data-table{width:100%;border-collapse:collapse;font-size:.95rem}
.data-table th,.data-table td{text-align:left;padding:var(--space-3);border-bottom:1px solid var(--border)}
.data-table thead th{background:var(--surface-2);position:sticky;top:0}
.data-table tbody tr:hover{background:var(--surface-2)}
.data-table tbody tr:last-child td{border-bottom:0}
.th-sort{background:none;border:0;font:inherit;font-weight:600;color:var(--text);cursor:pointer;display:inline-flex;gap:var(--space-1);align-items:center;padding:0}
.th-sort:hover{color:var(--brand-on-bg)}
.th-sort::after{content:"";opacity:.4;border:4px solid transparent;border-top-color:currentColor;margin-top:4px}
.th-sort[aria-sort="ascending"]::after{border-top-color:transparent;border-bottom-color:currentColor;margin-top:-4px;opacity:1}
.th-sort[aria-sort="descending"]::after{opacity:1}
@media(max-width:640px){
  .data-table thead{position:absolute;left:-999px}
  .data-table tr{display:block;border-bottom:1px solid var(--border);padding:var(--space-2) 0}
  .data-table td{display:flex;justify-content:space-between;gap:var(--space-4);border:0;padding:var(--space-1) var(--space-3)}
  .data-table td::before{content:attr(data-label);font-weight:600;color:var(--muted)}
}
/* component: card-grid */
.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:var(--space-4);margin:var(--space-5) 0}
.card{display:flex;flex-direction:column;gap:var(--space-1);padding:var(--space-5);border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);color:var(--text);text-decoration:none;box-shadow:var(--shadow);transition:transform var(--motion-fast) var(--ease),box-shadow var(--motion-fast) var(--ease)}
.card:hover{transform:translateY(-2px);box-shadow:var(--shadow-2)}
.card:focus-visible{outline:var(--focus-ring);outline-offset:var(--focus-offset)}
.card-title{margin:0;font-size:var(--step-1);color:var(--brand-on-bg)}
.card-desc,.card-note{margin:0;color:var(--muted);font-size:var(--step--1)}
.card-cta{margin-top:auto;color:var(--accent-on-bg);font-weight:600}
/* component: pagination */
.pagination{display:none;gap:var(--space-2);align-items:center;justify-content:center;margin-top:var(--space-4)}
.pagination.ready{display:flex}
.pagination button{padding:var(--space-2) var(--space-3);border:1px solid var(--border-strong);border-radius:var(--radius);background:var(--surface);color:var(--text);cursor:pointer}
.pagination button:hover:not([disabled]){background:var(--surface-2)}
.pagination button[disabled]{opacity:.5;cursor:default}
.pagination .page-label{color:var(--muted);font-size:var(--step--1)}
/* component: empty-loading */
.empty-state{text-align:center;padding:var(--space-6) var(--space-4);color:var(--muted);border:1px dashed var(--border);border-radius:var(--radius)}
.empty-state .empty-title{color:var(--text);font-weight:600;margin:0 0 var(--space-1)}
.table-empty-live{padding:var(--space-4);color:var(--muted)}
.skeleton{display:grid;gap:var(--space-2)}
.skeleton .sk-row{height:1rem;border-radius:4px;background:linear-gradient(90deg,var(--surface-2),var(--border),var(--surface-2));background-size:200% 100%;animation:sk 1.2s infinite}
@keyframes sk{0%{background-position:200% 0}100%{background-position:-200% 0}}
@media(prefers-reduced-motion:reduce){.skeleton .sk-row{animation:none}}
/* component: alerts */
.alerts{margin:var(--space-4) 0}
.alert{display:flex;gap:var(--space-2);align-items:flex-start;padding:var(--space-3) var(--space-4);border:1px solid var(--border);border-radius:var(--radius);border-left-width:4px}
.alert-info{border-left-color:var(--info);background:var(--info-bg);color:var(--info-ink)}
.alert-success{border-left-color:var(--success);background:var(--success-bg);color:var(--success-ink)}
.alert-warning{border-left-color:var(--warning);background:var(--warning-bg);color:var(--warning-ink)}
.alert-error{border-left-color:var(--danger);background:var(--danger-bg);color:var(--danger-ink)}
:root[data-theme="dark"] .alert{background:var(--surface-2);color:var(--text)}
@media (prefers-color-scheme: dark){:root:not([data-theme="light"]) .alert{background:var(--surface-2);color:var(--text)}}
:root[data-theme="dark"] .alert-info,:root[data-theme="dark"] .alert-success,:root[data-theme="dark"] .alert-warning,:root[data-theme="dark"] .alert-error{color:var(--text)}
/* component: form-validation */
.form-wrap{max-width:640px}
.form-errors{border:1px solid var(--danger);border-radius:var(--radius);background:var(--danger-bg);color:var(--danger-ink);padding:var(--space-3) var(--space-4);margin-bottom:var(--space-4)}
.form-errors ul{margin:var(--space-1) 0 0;padding-left:var(--space-5)}
.form-wrap [aria-invalid="true"]{outline:2px solid var(--danger);outline-offset:var(--focus-offset)}
:root[data-theme="dark"] .form-errors{background:var(--surface-2);color:var(--text)}
.scn-grants .hero{background:linear-gradient(180deg,color-mix(in srgb,var(--brand-primary) 8%,var(--bg)),var(--bg))}
.scn-grants .hero h1{max-width:22ch}

/* ── style: soft — structural rules. Colour comes from tokens only (Q1b). ── */
.style-soft h1,.style-soft h2,.style-soft h3{font-weight:var(--heading-weight);letter-spacing:var(--heading-tracking)}
/* A raised, rounded shell; the head is tinted rather than ruled. */
.style-soft .table-wrap{border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);overflow:hidden}
.style-soft .data-table th{background:var(--surface-2);border-bottom:1px solid var(--border)}
.style-soft .data-table tbody tr:hover{background:var(--surface-2)}
.style-soft .data-table td{border-bottom:1px solid var(--border)}
.style-soft .card{border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1)}
.style-soft .card:hover{box-shadow:var(--shadow-2)}
.style-soft .empty-state{background:var(--surface-2);border:0;border-radius:var(--radius-lg)}
.style-soft .typeahead-input,.style-soft .typeahead-submit{border-radius:var(--radius)}
/* Nav items are pills, which is the affordance a rounded system expects. */
.style-soft .site-menu a{border-radius:var(--radius);padding-left:var(--space-3);padding-right:var(--space-3)}
.style-soft .site-menu a:hover{background:var(--surface-2)}

