/* TaskNForce — generated from the design system token files. Do not hand-edit;
   regenerate from the design system if tokens change. */

/* ---- fonts ---- */
/* TaskNForce webfonts — self-hosted, supplied by the client August 2026.
   Three variable fonts cover the whole system. Archivo carries two axes (width
   and weight); the width axis is available but the brand uses the default 100%
   only — see readme.md > Visual foundations > Type before reaching for it.
   Licences ship alongside the binaries in assets/fonts/*-OFL.txt. */

@font-face {
  font-family: "Archivo";
  src: url("font/archivo.ttf") format("truetype-variations");
  font-weight: 100 900;
  font-stretch: 62.5% 125%;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Archivo";
  src: url("font/archivo-italic.ttf") format("truetype-variations");
  font-weight: 100 900;
  font-stretch: 62.5% 125%;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Public Sans";
  src: url("font/sans.ttf") format("truetype-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Public Sans";
  src: url("font/sans-italic.ttf") format("truetype-variations");
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("font/mono.ttf") format("truetype-variations");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
}

/* ---- colors ---- */
/* Base palette, derived from the approved brand pack (assets/brand/brand-spec.json).
   The five brand values are exact and must never be re-derived:
     olive #5A6B37 · amber #D49A22 · warm ivory #F3F0E6 · charcoal #232628 · off-white #FAF7F2
   Raw ramps only — never reference these directly in product code; use the
   semantic aliases in semantic.css. */
:root{
/* Olive — primary brand. 500 is TaskNForce olive, exact from the brand pack. */
--olive-50:#F4F6EE;--olive-100:#E6EBD9;--olive-200:#CCD6B4;--olive-300:#AEBD8C;--olive-400:#8B9C63;--olive-500:#5A6B37;--olive-600:#4C5B2E;--olive-700:#3D4926;--olive-800:#2F381E;--olive-900:#212716;

/* Amber — brand accent AND the Review status. Read the note in semantic.css:
   in UI chrome amber means attention; the amber square in the mark is the one
   exception. 400 is TaskNForce amber, exact from the brand pack. */
--amber-50:#FCF6E8;--amber-100:#F8EBCB;--amber-200:#EFD595;--amber-300:#E3BB58;--amber-400:#D49A22;--amber-500:#B8811A;--amber-600:#966816;--amber-700:#755113;--amber-800:#563B0F;--amber-900:#3A280B;

/* Stone — warm neutrals. Off-white, warm ivory and charcoal are exact brand
   values; the rest of the ramp is interpolated to sit with them. Warm, never
   cool — a grey with a blue cast fights the olive. */
--stone-0:#FFFFFF;--stone-25:#FAF7F2;--stone-50:#F3F0E6;--stone-100:#E9E5D8;--stone-200:#D8D3C3;--stone-300:#BAB4A2;--stone-400:#948E7C;--stone-500:#716C5D;--stone-600:#565247;--stone-700:#403D36;--stone-800:#2F2D28;--stone-900:#232628;--stone-950:#161819;

/* Brick — flagged and destructive. Not in the brand pack; derived to sit with
   olive and amber rather than shout over them. */
--brick-50:#FBF1EF;--brick-100:#F6DEDA;--brick-200:#EAB7AF;--brick-300:#DC8B80;--brick-400:#C75F52;--brick-500:#A93B2E;--brick-600:#8B3026;--brick-700:#6E261E;--brick-800:#511C16;--brick-900:#37130F;

/* Slate blue — informational only, and used sparingly. Never a brand colour. */
--info-50:#EFF3F6;--info-100:#DBE4EA;--info-200:#B4C6D3;--info-300:#87A4B8;--info-400:#577F99;--info-500:#3A5F78;--info-600:#2F4E62;--info-700:#253D4D;--info-800:#1C2D39;--info-900:#131E26;
}

/* ---- typography ---- */
:root{
--font-display:"Archivo","Archivo Fallback",system-ui,sans-serif;
--font-body:"Public Sans","Public Sans Fallback",system-ui,-apple-system,sans-serif;
--font-mono:"JetBrains Mono",ui-monospace,"SF Mono",Menlo,monospace;

/* Dense scale. 14px is the product base, not 16px — the portal is a review
   tool and the extra rows matter. Marketing copy steps up from --text-md. */
--text-3xs:10px;--text-2xs:11px;--text-xs:12px;--text-sm:13px;--text-base:14px;--text-md:15px;--text-lg:17px;--text-xl:20px;--text-2xl:24px;--text-3xl:30px;--text-4xl:38px;--text-5xl:48px;--text-6xl:60px;--text-7xl:76px;

--leading-flat:1;--leading-tight:1.14;--leading-snug:1.3;--leading-normal:1.5;--leading-relaxed:1.62;

--weight-regular:400;--weight-medium:500;--weight-semibold:600;--weight-bold:700;--weight-black:800;

/* Archivo is set tight and negative at display sizes; mono and small caps
   labels are set wide. Nothing in between gets tracking. */
--tracking-tightest:-0.03em;--tracking-tighter:-0.022em;--tracking-tight:-0.014em;--tracking-normal:0;--tracking-wide:0.06em;--tracking-wider:0.1em;--tracking-widest:0.14em;

--measure-narrow:44ch;--measure:66ch;--measure-wide:78ch;
}

/* ---- spacing ---- */
:root{
/* 4px grid with 2px half-steps. Dense UI leans on 6–12px; layout uses 16px+. */
--space-0:0;--space-025:2px;--space-05:4px;--space-1:6px;--space-15:8px;--space-2:10px;--space-25:12px;--space-3:14px;--space-4:16px;--space-5:20px;--space-6:24px;--space-7:28px;--space-8:32px;--space-10:40px;--space-12:48px;--space-16:64px;--space-20:80px;--space-24:96px;--space-32:128px;

/* Control heights — dense product defaults. */
--control-sm:26px;--control-md:32px;--control-lg:38px;
/* Touch targets on the Android field app never go below this. */
--touch-min:48px;--touch-lg:56px;

--row-height:34px;--row-height-lg:44px;

--layout-gutter:20px;--layout-max:1280px;--layout-sidebar:216px;--layout-topbar:52px;
}

/* ---- radius ---- */
:root{
/* Sharp. 2px is the house radius — everything from a checkbox to a modal.
   Only fully-round elements (avatars, status dots, pills) break it. */
--radius-none:0;--radius-1:2px;--radius-2:3px;--radius-3:4px;--radius-full:999px;
--radius-control:var(--radius-1);--radius-card:var(--radius-1);--radius-overlay:var(--radius-2);
--border-hair:1px;--border-thick:2px;
}

/* ---- elevation ---- */
:root{
/* Surfaces are flat and separated by hairline borders. Shadow is reserved for
   things that actually float: dropdowns, dialogs, toasts. Shadow colour is
   warm charcoal, not neutral black, so it sits on the stone neutrals. */
--shadow-none:none;
--shadow-1:0 1px 2px rgba(35,38,40,.06);
--shadow-2:0 2px 4px -1px rgba(35,38,40,.08),0 1px 2px rgba(35,38,40,.05);
--shadow-3:0 10px 24px -8px rgba(35,38,40,.16),0 2px 6px -2px rgba(35,38,40,.08);
--shadow-4:0 24px 56px -16px rgba(35,38,40,.24),0 4px 10px -4px rgba(35,38,40,.1);
--shadow-inset:inset 0 1px 0 rgba(255,255,255,.5);
--ring-width:2px;--ring-offset:2px;
}

/* ---- motion ---- */
:root{
/* Short and unfussy. No bounce, no spring, no scale-in. State changes are
   near-instant; overlays get a brief fade with a 4px rise. */
--dur-instant:60ms/* @kind other */;--dur-1:90ms/* @kind other */;--dur-2:150ms/* @kind other */;--dur-3:240ms/* @kind other */;--dur-4:400ms/* @kind other */;
--ease-out:cubic-bezier(.2,.7,.3,1)/* @kind other */;
--ease-in-out:cubic-bezier(.4,0,.2,1)/* @kind other */;
--ease-linear:linear/* @kind other */;
--transition-control:background-color var(--dur-1) var(--ease-out),border-color var(--dur-1) var(--ease-out),color var(--dur-1) var(--ease-out),box-shadow var(--dur-1) var(--ease-out);
}

/* ---- semantic ---- */
/* Semantic aliases. Product code and components reference ONLY these, so the
   dark scope in dark.css can reassign every one of them.

   ON AMBER — hard rule. Inside product chrome amber is the Review status and
   nothing else. --brand-accent* exists for the mark and for marketing surfaces
   only; never use it as a fill, highlight or button on any screen that can show
   a status. Traffic-light reading (olive verified, amber review, brick flagged)
   is worth more than an accent colour in the UI. */
:root{
/* Surfaces */
--surface-page:var(--stone-50);
--surface-card:var(--stone-0);
--surface-raised:var(--stone-0);
--surface-sunken:var(--stone-100);
--surface-inverse:var(--stone-900);
--surface-overlay:var(--stone-0);
--surface-scrim:rgba(35,38,40,.5);
--surface-hover:var(--stone-25);
--surface-active:var(--stone-100);
--surface-selected:var(--olive-50);

/* Text */
--text-primary:var(--stone-900);
--text-secondary:var(--stone-600);
/* stone-500, not stone-400: stone-400 (#948E7C) measures 2.87:1 on the ivory page tone and
   fails at every size this token is used at. stone-500 is 4.6:1 and still clearly subordinate
   to --text-secondary. stone-400 remains available for non-text use (rules, disabled marks). */
--text-tertiary:var(--stone-500);
--text-inverse:var(--stone-25);
/* Labels and captions on dark grounds. Two inline values were used before and both failed AA
   below 18.66px: rgba(250,247,242,.45) measures 4.28:1 on stone-950, and stone-400 measures
   4.01:1 once a panel's rgba(255,255,255,.05) overlay lightens that ground. stone-300 clears
   4.5:1 on both the plain and the overlaid ground, which is why it is the token. Secondary is
   .9 for the same reason: .82 measures 4.29:1 at 13px over an overlaid panel. Never hand-roll
   an alpha for small text on dark — both of these were arrived at by measuring, not by eye. */
--text-inverse-secondary:rgba(250,247,242,.9);
--text-inverse-tertiary:var(--stone-300);
--text-brand:var(--olive-500);
--text-link:var(--olive-600);
--text-link-hover:var(--olive-700);

/* Lines */
--border-subtle:var(--stone-100);
--border-default:var(--stone-200);
--border-strong:var(--stone-300);
--border-inverse:var(--stone-800);
--border-focus:var(--olive-500);

/* Brand */
--brand-primary:var(--olive-500);
--brand-primary-hover:var(--olive-600);
--brand-primary-active:var(--olive-700);
--brand-primary-soft:var(--olive-50);
--brand-primary-soft-border:var(--olive-100);
--brand-on-primary:var(--stone-25);
--brand-accent:var(--amber-400);
--brand-accent-hover:var(--amber-500);
--brand-accent-active:var(--amber-600);
--brand-accent-soft:var(--amber-50);
--brand-on-accent:var(--stone-900);
--brand-ivory:var(--stone-50);
--brand-charcoal:var(--stone-900);

/* Status — mapped to the confidence bands in specification 6.2 */
--status-verified-fg:var(--olive-600);
--status-verified-bg:var(--olive-50);
--status-verified-border:var(--olive-200);
--status-review-fg:var(--amber-600);
--status-review-bg:var(--amber-50);
--status-review-border:var(--amber-200);
--status-flagged-fg:var(--brick-600);
--status-flagged-bg:var(--brick-50);
--status-flagged-border:var(--brick-200);
--status-neutral-fg:var(--stone-600);
--status-neutral-bg:var(--stone-100);
--status-neutral-border:var(--stone-200);
--status-info-fg:var(--info-600);
--status-info-bg:var(--info-50);
--status-info-border:var(--info-200);

/* Sync — the field app's own vocabulary (spec 5) */
--sync-synced:var(--olive-500);
--sync-pending:var(--amber-400);
--sync-offline:var(--stone-400);
--sync-blocked:var(--brick-500);

/* Data visualisation, in order of use */
--chart-1:var(--olive-500);--chart-2:var(--amber-400);--chart-3:var(--stone-400);--chart-4:var(--olive-300);--chart-5:var(--brick-400);--chart-6:var(--info-400);

/* Type roles */
--type-display:var(--font-display);
--type-body:var(--font-body);
--type-label:var(--font-mono);
}

/* ---- dark ---- */
/* Dark is first-class, not an inversion. Charcoal #232628 is the brand's own
   dark value and anchors the scale; surfaces stay warm, never blue-black. Olive
   lifts to 300/400 to hold contrast, and amber is used more sparingly because
   it glares on dark. Reversed white logo artwork is the only correct mark here. */
:root[data-theme="dark"]{
--surface-page:var(--stone-950);
--surface-card:var(--stone-900);
--surface-raised:var(--stone-800);
--surface-sunken:#0E1011;
--surface-inverse:var(--stone-50);
--surface-overlay:var(--stone-800);
--surface-scrim:rgba(12,13,14,.66);
--surface-hover:#2B2E2F;
--surface-active:var(--stone-800);
--surface-selected:var(--olive-900);

--text-primary:var(--stone-50);
--text-secondary:var(--stone-300);
/* Mirror of the light-theme correction: stone-500 is 3.36:1 on charcoal. stone-400 is 5.4:1
   here — the same hex that fails on ivory passes on dark, and vice versa. */
--text-tertiary:var(--stone-400);
--text-inverse:var(--stone-950);
--text-brand:var(--olive-300);
--text-link:var(--olive-300);
--text-link-hover:var(--olive-200);

--border-subtle:#2E3132;
--border-default:var(--stone-800);
--border-strong:var(--stone-700);
--border-inverse:var(--stone-200);
--border-focus:var(--olive-300);

--brand-primary:var(--olive-400);
--brand-primary-hover:var(--olive-300);
--brand-primary-active:var(--olive-200);
--brand-primary-soft:var(--olive-900);
--brand-primary-soft-border:var(--olive-800);
--brand-on-primary:var(--stone-950);
--brand-accent:var(--amber-300);
--brand-accent-hover:var(--amber-200);
--brand-accent-active:var(--amber-100);
--brand-accent-soft:#3A280B;
--brand-on-accent:var(--stone-950);
--brand-ivory:var(--stone-50);
--brand-charcoal:var(--stone-900);

--status-verified-fg:var(--olive-200);
--status-verified-bg:var(--olive-900);
--status-verified-border:var(--olive-700);
--status-review-fg:var(--amber-200);
--status-review-bg:var(--amber-900);
--status-review-border:var(--amber-700);
--status-flagged-fg:var(--brick-200);
--status-flagged-bg:var(--brick-900);
--status-flagged-border:var(--brick-700);
--status-neutral-fg:var(--stone-300);
--status-neutral-bg:var(--stone-800);
--status-neutral-border:var(--stone-700);
--status-info-fg:var(--info-200);
--status-info-bg:var(--info-900);
--status-info-border:var(--info-700);

--sync-synced:var(--olive-300);
--sync-pending:var(--amber-300);
--sync-offline:var(--stone-500);
--sync-blocked:var(--brick-300);

--chart-1:var(--olive-300);--chart-2:var(--amber-300);--chart-3:var(--stone-400);--chart-4:var(--olive-500);--chart-5:var(--brick-300);--chart-6:var(--info-300);

--shadow-1:0 1px 2px rgba(0,0,0,.4);
--shadow-2:0 2px 4px -1px rgba(0,0,0,.5),0 1px 2px rgba(0,0,0,.4);
--shadow-3:0 10px 24px -8px rgba(0,0,0,.6),0 2px 6px -2px rgba(0,0,0,.5);
--shadow-4:0 24px 56px -16px rgba(0,0,0,.7),0 4px 10px -4px rgba(0,0,0,.5);
--shadow-inset:inset 0 1px 0 rgba(255,255,255,.06);
}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){color-scheme:dark}}

/* ---- base ---- */
/* Minimal element defaults. Everything else is a component's job. */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{margin:0;background:var(--surface-page);color:var(--text-primary);font-family:var(--font-body);font-size:var(--text-base);line-height:var(--leading-relaxed);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
h1,h2,h3,h4,h5,h6{font-family:var(--font-display);font-weight:var(--weight-bold);letter-spacing:var(--tracking-tight);line-height:var(--leading-tight);margin:0;text-wrap:pretty}
p{margin:0;text-wrap:pretty}
a{color:var(--text-link);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:2px;transition:color var(--dur-1) var(--ease-out)}
a:hover{color:var(--text-link-hover)}
a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible{outline:var(--ring-width) solid var(--border-focus);outline-offset:var(--ring-offset)}
code,kbd,samp,pre{font-family:var(--font-mono);font-size:.92em}
hr{border:0;border-top:var(--border-hair) solid var(--border-default);margin:0}
::selection{background:var(--brand-accent);color:var(--brand-on-accent)}
img,svg{display:block;max-width:100%}
button{font:inherit;color:inherit}
table{border-collapse:collapse}
[data-mono]{font-family:var(--font-mono);font-variant-numeric:tabular-nums}
@media (prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important}}

/* Named keyframes referenced by components. Kept in the global closure so a
   consuming page gets them from styles.css alone. */
@keyframes tnf-spin{to{transform:rotate(360deg)}}
@keyframes tnf-fade{from{opacity:0}to{opacity:1}}
@keyframes tnf-rise{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
@keyframes tnf-pulse{0%,100%{opacity:1}50%{opacity:.45}}
