/* styles.css — TeamUP base layer.
   ==================================================================

   Canonical design spec: /DESIGN.md at the repo root. Tokens (colour ramps,
   radius vocabulary, the single shadow, z-index scale, easing) live in
   partials/head.html as a Tailwind config. THIS file holds only what a utility
   class cannot express: @font-face, element defaults, keyframes, the
   reduced-motion branch, and third-party widget overrides.

   Do not recreate utility styles here. If you find yourself writing the same
   class string twice across two views, it belongs in components/ui.tsx.

   ------------------------------------------------------------------
   TYPOGRAPHY — three self-hosted faces, no CDN.

   Barlow is the default text face; Archivo carries display and every numeral
   in a data position; IBM Plex Mono is scoped to codes and identifiers only
   (room sign-in codes, CSI divisions, DOT pay items, FEIN, unit IDs, audit
   entity ids). See DESIGN.md § Typography for why these three and not Inter.

   Self-hosted rather than linked from fonts.googleapis.com because the app
   ships its own CSP (the same one that blocks the Cloudflare beacon) and
   because a jobsite connection should not need a second DNS lookup to render
   text. Files are in static/fonts/, ~76KB on the latin critical path.
   ------------------------------------------------------------------ */

/* Archivo — variable 400..800, one file covers every display weight. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/static/fonts/archivo-400-800-latin.woff2") format("woff2");
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/static/fonts/archivo-400-800-latin-ext.woff2") format("woff2");
  unicode-range:
    U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Barlow — static weights. 400 body, 500 meta, 600 strong, 700 bold. */
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/barlow-400-latin.woff2") format("woff2");
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/barlow-400-latin-ext.woff2") format("woff2");
  unicode-range:
    U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/barlow-500-latin.woff2") format("woff2");
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/barlow-500-latin-ext.woff2") format("woff2");
  unicode-range:
    U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/static/fonts/barlow-600-latin.woff2") format("woff2");
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/static/fonts/barlow-600-latin-ext.woff2") format("woff2");
  unicode-range:
    U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/barlow-700-latin.woff2") format("woff2");
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/barlow-700-latin-ext.woff2") format("woff2");
  unicode-range:
    U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* IBM Plex Mono 500 — codes and identifiers only. */
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/plexmono-500-latin.woff2") format("woff2");
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/plexmono-500-latin-ext.woff2") format("woff2");
  unicode-range:
    U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ── Motion + layer tokens as CSS variables ────────────────────────────
   Duplicated from the Tailwind config so plain CSS below (keyframes,
   element rules, third-party overrides) can reach them. Keep in sync. */
:root {
  --font-display: "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-text: "Barlow", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --dur-fast: 120ms;
  --dur: 160ms;
  --dur-slow: 220ms;
  --ease: cubic-bezier(0.25, 1, 0.5, 1); /* ease-out-quart */

  --z-dropdown: 100;
  --z-sticky: 200;
  --z-backdrop: 300;
  --z-modal: 400;
  --z-toast: 500;
  --z-tooltip: 600;

  --shadow-float:
    0 8px 24px -8px rgb(8 18 48 / 0.28), 0 2px 6px -2px rgb(8 18 48 / 0.14);
}

/* ── Element defaults ─────────────────────────────────────────────────
   Barlow is the page default. Display type is Archivo, applied by element
   so a view does not have to remember `font-display` on every heading. */
html {
  -webkit-text-size-adjust: 100%;
}
body {
  font-family: var(--font-text);
  font-size: 14px;
  line-height: 1.5;
  color: #0b1f4b;
  background: #ffffff;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
h4,
h5,
h6,
button,
th,
.font-display {
  font-family: var(--font-display);
  letter-spacing: -0.015em;
}

/* Display type gets tighter tracking the larger it is. Floor is -0.025em -
   any tighter and the letters touch, which reads cramped, not designed. */
h1 {
  letter-spacing: -0.025em;
  text-wrap: balance;
}
h2 {
  letter-spacing: -0.02em;
  text-wrap: balance;
}
h3 {
  letter-spacing: -0.018em;
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

code,
kbd,
samp,
pre,
.font-mono {
  font-family: var(--font-mono);
  letter-spacing: 0.02em;
  font-size: 0.925em;
}

/* Every numeral in a data position aligns. Money, hours, quantities, dates,
   percentages, durations, float values. `.tnum` is the opt-in for a container
   whose figures should line up column-to-column. */
.tnum,
table {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* `.label` — the uppercase tracked micro-label. It labels form fields, table
   column headers and definition-list keys. It is NOT a section eyebrow: an
   all-caps kicker sitting above a heading is a named AI tell and is banned
   (DESIGN.md § Anti-patterns). */
.label {
  font-family: var(--font-display);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  color: #4d5e85;
}

/* ── Focus ────────────────────────────────────────────────────────────
   Visible, always. Keyboard-only so a mouse click on a button does not
   leave a ring behind. Never `outline: none` without a replacement. */
:focus-visible {
  outline: 2px solid #f4731f;
  outline-offset: 2px;
  border-radius: 3px;
}
/* On navy chrome the orange ring has poor separation from the fill; use a
   light ring there instead. */
.bg-navy-900 :focus-visible,
.bg-navy-950 :focus-visible,
.bg-navy-800 :focus-visible {
  outline-color: #ff8a3d;
}

/* ── Keyframes ────────────────────────────────────────────────────────
   Only transform/opacity/filter. Never height/width/top — those hit layout
   on every frame. */
@keyframes tu-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes tu-rise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes tu-slide-in {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
@keyframes tu-shimmer {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(100%);
  }
}

.tu-fade {
  animation: tu-fade-in var(--dur) var(--ease) both;
}
.tu-rise {
  animation: tu-rise var(--dur) var(--ease) both;
}
.tu-toast {
  animation: tu-slide-in var(--dur) var(--ease) both;
}

/* Row stagger: capped at 8 so a 200-row table does not ripple for 4 seconds.
   Rows past the cap simply appear, which is correct - the user has scrolled
   past the point where entrance motion tells them anything. */
.tu-stagger > * {
  animation: tu-rise var(--dur) var(--ease) both;
}
.tu-stagger > *:nth-child(1) {
  animation-delay: 0ms;
}
.tu-stagger > *:nth-child(2) {
  animation-delay: 20ms;
}
.tu-stagger > *:nth-child(3) {
  animation-delay: 40ms;
}
.tu-stagger > *:nth-child(4) {
  animation-delay: 60ms;
}
.tu-stagger > *:nth-child(5) {
  animation-delay: 80ms;
}
.tu-stagger > *:nth-child(6) {
  animation-delay: 100ms;
}
.tu-stagger > *:nth-child(7) {
  animation-delay: 120ms;
}
.tu-stagger > *:nth-child(n + 8) {
  animation-delay: 140ms;
}

/* Skeleton sweep. The old version pulsed opacity on a white block on white,
   which was invisible; this is a real left-to-right sheen over a tinted base. */
.tu-skeleton {
  position: relative;
  overflow: hidden;
  background: #eef2fa;
}
.tu-skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    90deg,
    transparent,
    rgb(255 255 255 / 0.75),
    transparent
  );
  animation: tu-shimmer 1.2s var(--ease) infinite;
}

/* ── Reduced motion — not optional ────────────────────────────────────
   Every animation above degrades to an instant change or a plain crossfade.
   The shimmer stops entirely (a repeating sweep is exactly what vestibular
   sensitivity flags), leaving a static tinted block that still reads as
   "loading". */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .tu-fade,
  .tu-rise,
  .tu-toast,
  .tu-stagger > * {
    animation: tu-fade-in 0.01ms both;
  }
  .tu-skeleton::after {
    display: none;
  }
}

/* ── Select arrow ─────────────────────────────────────────────────────
   `components/ui.tsx` sets `appearance-none` on every <select> so the control
   matches the app's inputs, which also removes the platform arrow. This puts one
   back.

   It lives HERE and not as a Tailwind arbitrary value on the element, because an
   inline `[background-image:url('data:image/svg+xml;utf8,<svg…>')]` places raw
   `<` and `>` inside an HTML class attribute — the parser reads them as tags,
   mangles the class string and injects a stray <path> into the DOM. That
   actually shipped for a few minutes on 2026-08-21 before it was moved.

   The SVG is percent-encoded, which is what makes it safe inside a url() and
   inside a stylesheet alike. `currentColor` is not available in a data URI, so
   the stroke is the ink-3 token by value; keep the two in sync. */
.tu-select,
select:not([multiple]):not([size]) {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%234d5e85' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9.5 12 15.5 18 9.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
  background-size: 12px 12px;
}

/* The bare-element half of the rule above is a GUARD, not a convenience.
   The Tailwind forms plugin sets `appearance: none` on every <select> and
   paints its own chevron at `right: .5rem` — but it reserves NO padding for it.
   So a hand-written `<select class="… px-3 …">`, which is how the four ported
   Toolbox tools built every filter, drew that arrow directly on top of the last
   character of the option text ("All trade⌄s"). It shipped that way and was
   invisible in code review because the class list looks perfectly reasonable.

   Matching `select` as an element means the app's own chevron and the room for
   it are the default, so forgetting `selectCls` degrades to "correct" instead of
   "broken". An element selector is specificity 0-0-1, so any utility class at a
   call site still wins — `selectCls`'s own `pr-8` included. Multi-selects and
   list-boxes are excluded: they have no collapsed row to point a chevron at. */
select:not([multiple]):not([size]) {
  padding-right: 2rem;
}

/* ── Scrollbars inside app panels ────────────────────────────────────
   The default macOS overlay bar is fine; on Windows/Linux a 17px grey trough
   inside a 320px panel is not. Scoped to opt-in containers only. */
.tu-scroll {
  scrollbar-width: thin;
  scrollbar-color: #bccdea transparent;
}
.tu-scroll::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
.tu-scroll::-webkit-scrollbar-track {
  background: transparent;
}
.tu-scroll::-webkit-scrollbar-thumb {
  background: #bccdea;
  border: 3px solid #ffffff;
  border-radius: 999px;
}
.tu-scroll::-webkit-scrollbar-thumb:hover {
  background: #93aada;
}

/* ── Framework / third-party overrides ────────────────────────────────

   The HTML "hidden" attribute must beat utility display classes. A node like
   <section class="... flex" hidden> otherwise stays display:flex - the .flex
   utility loads after the UA [hidden]{display:none} rule with equal
   specificity, so it wins and the element never actually hides. That breaks
   the common pattern of toggling the "hidden" attribute to switch between an
   anonymous view and the signed-in app. Keep this rule (or delete it only if
   you never use the hidden attribute alongside a display utility class). */
[hidden] {
  display: none !important;
}

/* Hide the framework "Built with Benmore" attribution badge on this
   client-facing deliverable. The badge is injected with inline styles and no
   id/class, so we target its stable aria-label; a stylesheet !important beats
   the (non-important) inline display rule. */
a[aria-label="Built with Benmore"] {
  display: none !important;
}
