/* GENERATED by packages/design-tokens/scripts/generate.ts — do not edit.
   Edit src/tokens.json and run `pnpm generate:tokens`. */

:root {
  --color-forest: #16543F;
  --color-forest-deep: #0F3E2E;
  --color-saffron: #E2A32B;
  --color-saffron-text: #8A4708;
  --color-paper: #FBFAF8;
  --color-moss: #E9F3EA;
  --color-moss-light: #FAF0DB;
  --color-ink: #14120F;
  --color-ink-muted: #655F56;
  --color-line: #E6E2DA;
  --color-line-strong: #6E8378;
  --color-amber: #8A5A00;
  --color-danger: #9B1C1C;
  --spacing-0: 0px;
  --spacing-1: 4px;
  --spacing-2: 8px;
  --spacing-3: 12px;
  --spacing-4: 16px;
  --spacing-5: 20px;
  --spacing-6: 24px;
  --spacing-8: 32px;
  --spacing-10: 40px;
  --spacing-12: 48px;
  --spacing-16: 64px;
  --spacing-20: 80px;
  --text-xs: 12px;
  --text-label: 13px;
  --text-sm: 14px;
  --text-base: 16px;
  --text-lg: 18px;
  --text-status: 19px;
  --text-rowLabel: 20px;
  --text-wordmark: 21px;
  --text-xl: 22px;
  --text-date: 26px;
  --text-2xl: 28px;
  --text-roadName: 30px;
  --text-3xl: 34px;
  --text-hero: 38px;
  --text-4xl: 44px;
  --radius-none: 0px;
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-tile: 18px;
  --radius-full: 9999px;
  --leading-tight: 1.1;
  --leading-snug: 1.25;
  --leading-normal: 1.5;
  --leading-relaxed: 1.7;
  --size-touch-target: 44px;
  --size-driver-button: 72px;
  --size-tracking-max-width: 430px;
  --duration-fast: 120ms;
  --duration-micro: 200ms;
  --duration-normal: 200ms;
  --duration-slow: 320ms;
  --duration-standard: 500ms;
  --duration-stagger: 22ms;
  --font-display: "Fraunces", Georgia, serif;
  --font-body: "Public Sans", system-ui, sans-serif;
  --font-sinhala: "Noto Sans Sinhala", sans-serif;
  --font-tamil: "Noto Sans Tamil", sans-serif;
}

/*
 * Base styles for the tracking page.
 *
 * Concatenated with the generated token custom properties into public/theme.css
 * by scripts/build-css.ts. Deliberately tiny: every rule is bytes on a 3G
 * connection the resident pays for (ADR-0003).
 */

/*
 * Fonts are SELF-HOSTED, not fetched from Google (ADR-0018). Both are SIL OFL —
 * see public/fonts/OFL-NOTICE.md.
 *
 * Each file is instanced and subset before shipping: Fraunces is pinned to
 * SOFT 60 / opsz 14 with only the 600-700 weight range kept, and both faces are
 * cut to the Latin characters the interface uses. That took the four files from
 * 324 KB as Google serves them to 68 KB. `font-display: swap` means text is
 * readable in the fallback before the download lands, which on 3G is the
 * difference between a blank hero and a legible one.
 */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 600 700;
  font-display: swap;
  src: url('/fonts/fraunces-latin.woff2') format('woff2');
}

@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 600 700;
  font-display: swap;
  src: url('/fonts/fraunces-latin-italic.woff2') format('woff2');
}

@font-face {
  font-family: 'Public Sans';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('/fonts/public-sans-latin.woff2') format('woff2');
}

:root {
  color-scheme: light;

  /* One easing curve system-wide. */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  /*
   * Safe areas.
   *
   * Installed to the home screen the app runs in `standalone` display, with no
   * browser chrome between it and the hardware — so the dock lands on the
   * home-indicator strip and the map's back button lands under the notch,
   * unless everything fixed to an edge adds these.
   *
   * They read 0 everywhere that has no notch, including every desktop browser,
   * so the same rule is correct on both. They also read 0 WITHOUT
   * `viewport-fit=cover` on the viewport meta — which is the part that is easy
   * to miss, because the CSS looks right and simply has nothing to add.
   */
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --safe-right: env(safe-area-inset-right, 0px);

  /* What a fixed dock costs the bottom of a scrolling column. */
  --dock-clearance: calc(112px + var(--safe-bottom));

  /* Translucent fills that sit over the glass. Kept here rather than in
     tokens.json because the token generator computes contrast on solid hex,
     and an alpha fill has no single ratio to compute. Their opaque fallbacks
     ARE tokens: paper, moss, mossLight. */
  --fill-neutral: rgba(255, 255, 255, 0.74);
  --fill-mint: rgba(233, 243, 234, 0.78);
  --fill-wheat: rgba(250, 240, 219, 0.84);
  --fill-surface: rgba(255, 255, 255, 0.68);

  /* The inset top-edge highlight is what makes a translucent panel read as a
     physical object rather than a washed-out rectangle. It is not optional. */
  --shadow-card: 0 1px 0 rgba(255, 255, 255, 0.9) inset, 0 6px 28px rgba(20, 18, 15, 0.07);
  --shadow-dock: 0 1px 0 rgba(255, 255, 255, 0.9) inset, 0 10px 40px rgba(20, 18, 15, 0.14),
    0 2px 8px rgba(20, 18, 15, 0.06);
  --shadow-action: 0 8px 24px -10px rgba(15, 62, 46, 0.8);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  background: var(--color-paper);
  color: var(--color-ink);
  /* 16px floor. The primary user is 40-70, reading outdoors in daylight. */
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font-body);
  line-height: var(--leading-normal);
}

/* An English reader never downloads a script they cannot read. */
html[lang='si'] body {
  font-family: var(--font-sinhala);
}

html[lang='ta'] body {
  font-family: var(--font-tamil);
}

h1,
h2,
p,
ol,
ul {
  margin: 0;
}

/* Focus must always be visible. Removing the outline without replacing it is
   the most common accessibility regression there is. */
:focus-visible {
  outline: 3px solid var(--color-forest);
  outline-offset: 3px;
}

/*
 * Glass.
 *
 * Blur without saturate renders whatever is behind it grey and dead, so the two
 * always travel together. The -webkit- prefix still matters on Safari.
 *
 * @supports is the fallback path, not a progressive enhancement: a browser with
 * no backdrop-filter gets an OPAQUE fill and keeps the border and shadow, so the
 * surface still reads as a card rather than as a pale smear. Low-end Androids
 * are the target device and some of them land here.
 */
.hz-glass {
  background: var(--fill-surface);
  border: 1px solid rgba(230, 226, 218, 0.8);
  box-shadow: var(--shadow-card);
  -webkit-backdrop-filter: blur(24px) saturate(180%);
  backdrop-filter: blur(24px) saturate(180%);
}

.hz-glass--mint {
  background: var(--fill-mint);
}

.hz-glass--wheat {
  background: var(--fill-wheat);
}

.hz-glass--neutral {
  background: var(--fill-neutral);
}


@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hz-glass {
    background: #ffffff;
  }

  .hz-glass--mint {
    background: var(--color-moss);
  }

  .hz-glass--wheat {
    background: var(--color-moss-light);
  }
}

/*
 * Glass needs colour behind it or it renders flat. Three large, heavily blurred
 * orbs sit at z-index 0 and nothing else may. They are decorative and
 * non-interactive; the canvas must still read as white.
 */
.hz-orbs {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.hz-orb {
  position: absolute;
  border-radius: var(--radius-full);
  filter: blur(84px);
}

/*
 * Available to a screen reader, invisible on screen.
 *
 * This used to live only in the map page's injected stylesheet, so any other
 * page using the class rendered its labels inline — which is exactly what
 * happened to the route stepper the first time it was drawn. A helper class
 * two surfaces rely on belongs in the shared sheet.
 */
.hz-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/*
 * Entrance — one rise on the column, no per-child delay.
 *
 * The handoff specifies a stagger of `index × 22ms`. That was built and then
 * removed, and the reason is worth keeping: a staggered child needs
 * `animation-fill-mode: backwards` to hold opacity 0 during its delay, and any
 * situation where the animation does not tick leaves that content PERMANENTLY
 * INVISIBLE. It is the classic way entrance animations fail — silently, for
 * some people, on some devices. It also made every delayed element fail
 * Playwright's stability check, which is the same fact showing up as a test.
 *
 * Animating the container instead has no delay, so there is no pre-delay state
 * to get stuck in. The motion reads almost identically and cannot hide the
 * page. `prefers-reduced-motion` collapses the duration further down.
 *
 * **It does not touch opacity, and must not.** Fading the column in from
 * `opacity: 0` suppressed Largest Contentful Paint entirely — Chrome does not
 * accept a fully transparent element as an LCP candidate, so Lighthouse
 * reported `LanternError: NO_LCP` and scored performance 0. LCP is the metric
 * `09-PRD` puts a number on. A decorative fade is not worth the product's
 * headline measurement, so this animates transform only.
 */
@keyframes hz-rise {
  from {
    transform: translateY(8px);
  }
  to {
    transform: none;
  }
}

.hz-stagger {
  animation: hz-rise var(--duration-standard) var(--ease);
}

/* The register success ring. Breathes once, slowly — a confirmation, not an alarm. */
@keyframes hz-ring {
  0%,
  100% {
    box-shadow: 0 0 0 8px rgba(22, 84, 63, 0.12);
  }
  50% {
    box-shadow: 0 0 0 14px rgba(22, 84, 63, 0.06);
  }
}

/* Live indicators: the tractor is moving, and the dot should say so. */
@keyframes hz-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.3;
  }
}

/* Touch targets. 44px is the floor everywhere; the dock is larger. */
.hz-tap {
  min-width: var(--size-touch-target);
  min-height: var(--size-touch-target);
}

button {
  font: inherit;
  transition: transform var(--duration-micro) var(--ease);
}

button:active {
  transform: scale(0.965);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/*
 * Print. The council report is taken into a Pradeshiya Sabha meeting on paper,
 * and a report that prints badly does not get taken in at all.
 */
@media print {
  html {
    background: #fff;
    font-size: 11pt;
  }

  .no-print,
  nav,
  header a {
    display: none !important;
  }

  /* Backgrounds do not print by default, so the provenance colours would
     vanish — exactly the information that must survive onto paper. */
  * {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  table {
    page-break-inside: auto;
  }

  tr {
    page-break-inside: avoid;
  }

  thead {
    display: table-header-group;
  }

  a[href]::after {
    content: ' (' attr(href) ')';
    font-size: 9pt;
    color: #555;
  }
}
