/* tokens.css
 * CORE-02 checkpoint 2A. The shared design token system for Harmelo and AHR.
 *
 * ---------------------------------------------------------------------------
 * NOTHING LINKS THIS FILE YET, AND THAT IS DELIBERATE.
 *
 * 2A establishes the vocabulary. 2B builds components on it and 2C applies it to the three
 * verified opening screens. Linking it from a page during 2A would change rendering on
 * surfaces nobody has re-verified, which is the opposite of what an inventory checkpoint is
 * for. scripts/test-tokens.js asserts that no HTML file references it.
 *
 * ---------------------------------------------------------------------------
 * WHY EVERY NAME IS PREFIXED --hm-
 *
 * The pages already declare about thirty five custom properties of their own: --teal, --ink,
 * --line, --good, --warn and so on. Those names are live. If this file declared --teal at
 * :root, then the first page to link it would silently inherit a different brand colour in
 * every rule that expected its own value, and the change would appear on screens that were
 * never part of the checkpoint.
 *
 * The prefix makes adoption explicit. A rule uses a token only when somebody writes --hm- into
 * it. There is no accidental inheritance, so 2C can migrate one surface at a time and prove
 * each one.
 *
 * ---------------------------------------------------------------------------
 * WHY THE VALUES ARE NOT THE ONES ALREADY IN THE PAGES
 *
 * They mostly are. Where they are not, it is because the existing value fails contrast, and
 * the audited numbers are recorded in notes/core-02/ACCESSIBILITY_RESPONSIVE_DENSITY_RULES.md
 * rather than in a commit message nobody will find again.
 *
 * The three that changed and why:
 *
 *   --mute at #94A0AE measured 2.66:1 on white. That fails even the 3:1 non-text bar, and it
 *   was being used for secondary text. Replaced by --hm-ink-mute at #5A6472, 6.00:1.
 *
 *   --warn ranged across #C77D1A, #B7791F, #E5A53B and #D0432E in different files. The two
 *   light ones measured 3.29:1 and 3.64:1, below the 4.5:1 text bar. Replaced by
 *   --hm-warn-text at #8A5A00, 5.93:1.
 *
 *   --teal at #0E9384 measured 3.80:1. That is fine for a UI element or a large heading and
 *   not fine for body text, so the brand colour is split: --hm-accent for surfaces, borders
 *   and controls, and --hm-accent-text at #0A6F64, 6.05:1, for anything that is read.
 *
 * The split between an accent and an accent-for-text is the general rule here, not a special
 * case for teal. A colour that passes 3:1 may be a chart series, an icon or a border. Only a
 * colour that passes 4.5:1 may carry words.
 *
 * ---------------------------------------------------------------------------
 * STATUS IS NEVER CARRIED BY COLOUR ALONE
 *
 * Every status token comes in three parts: a text colour, a surface tint and a border. None of
 * them is a substitute for a label. The accessibility contract requires a text or shape cue in
 * addition to hue, and a token cannot enforce that on its own, so the component work in 2B has
 * to. What this file guarantees is only that when a status is shown, its colours are legible.
 */

/* ===========================================================================
 * LIGHT, THE DEFAULT
 *
 * :root carries light rather than a neutral base, because [data-theme="light"] is what the
 * consoles already set and a page that fails to set the attribute should still be readable.
 * ======================================================================== */
:root {

  /* ---- TYPOGRAPHY --------------------------------------------------------
   * Three families, matching what the trees already load. Sora and Inter on the professional
   * consoles, Cormorant Garamond and Inter Tight on the AHR homeowner surfaces, IBM Plex Mono
   * for identifiers. The stacks keep their local fallbacks so a blocked font fetch degrades to
   * a system face rather than to Times.
   */
  --hm-font-display: 'Sora', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  --hm-font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  --hm-font-serif: 'Cormorant Garamond', Georgia, serif;
  --hm-font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, monospace;

  /* A modular scale, not an open set. Fourteen sizes invite a fifteenth.
   * 12px is the floor: mobile.css already enforces it and verify_mobile.py measures it,
   * because smaller than that is unreadable outdoors on a phone. */
  --hm-text-xs: 12px;
  --hm-text-sm: 13px;
  --hm-text-base: 15px;
  --hm-text-md: 17px;
  --hm-text-lg: 20px;
  --hm-text-xl: 26px;
  --hm-text-2xl: 34px;

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

  --hm-leading-tight: 1.25;
  --hm-leading-normal: 1.5;
  --hm-leading-relaxed: 1.65;

  /* Identifiers are read character by character and get looser tracking. HMIN, HEIN and HBIN
   * are compared by eye against a plate or an invoice, so an O against a 0 matters. */
  --hm-tracking-identifier: 0.06em;
  --hm-tracking-label: 0.1em;

  /* ---- SPACING -----------------------------------------------------------
   * A 4px base. Every gap is a multiple, so vertical rhythm survives being assembled from
   * components written at different times. */
  --hm-space-1: 4px;
  --hm-space-2: 8px;
  --hm-space-3: 12px;
  --hm-space-4: 16px;
  --hm-space-5: 24px;
  --hm-space-6: 32px;
  --hm-space-7: 48px;
  --hm-space-8: 64px;

  /* ---- RADIUS ------------------------------------------------------------ */
  --hm-radius-sm: 4px;
  --hm-radius-md: 8px;
  --hm-radius-lg: 12px;
  --hm-radius-xl: 16px;
  --hm-radius-pill: 999px;

  /* ---- SURFACES ----------------------------------------------------------
   * Three levels and a page background. More than three and nobody can say which is which. */
  --hm-bg: #F3F5F8;
  --hm-surface-1: #FFFFFF;
  --hm-surface-2: #F8FAFC;
  --hm-surface-3: #EEF2F7;

  /* ---- BORDERS ----------------------------------------------------------- */
  --hm-line: #E7EBF0;
  --hm-line-strong: #DDE3EA;
  --hm-border-width: 1px;
  --hm-border-width-strong: 2px;

  /* ---- TEXT --------------------------------------------------------------
   * All three pass 4.5:1 on both --hm-surface-1 and --hm-bg. There is no "decorative" text
   * colour here on purpose: if it is text, it is legible. */
  --hm-ink: #0E1522;
  --hm-ink-soft: #48505F;
  --hm-ink-mute: #5A6472;

  /* Reversed text, for use on a saturated fill such as a primary button. */
  --hm-ink-on-accent: #FFFFFF;

  /* ---- ACCENT ------------------------------------------------------------
   * accent is the brand colour for controls, borders and fills, at 3.80:1.
   * accent-text is the same hue darkened to 6.05:1 for anything read as words. */
  --hm-accent: #0E9384;
  --hm-accent-text: #0A6F64;
  --hm-accent-surface: rgba(14, 147, 132, .10);
  --hm-accent-border: rgba(14, 147, 132, .32);

  /* ---- STATUS ------------------------------------------------------------
   * Six roles. Safety is separate from danger deliberately: a safety finding is a category of
   * evidence with its own visibility rules under Build 1, not merely the most severe colour,
   * and collapsing the two would let a styling decision imply a lifecycle one.
   *
   * Every -text value passes 4.5:1 on surface-1 and on bg. */
  --hm-ok-text: #1F7A52;
  --hm-ok-surface: rgba(31, 122, 82, .10);
  --hm-ok-border: rgba(31, 122, 82, .30);

  --hm-warn-text: #8A5A00;
  --hm-warn-surface: rgba(138, 90, 0, .10);
  --hm-warn-border: rgba(138, 90, 0, .30);

  --hm-danger-text: #B3261E;
  --hm-danger-surface: rgba(179, 38, 30, .10);
  --hm-danger-border: rgba(179, 38, 30, .30);

  --hm-safety-text: #B4451F;
  --hm-safety-surface: rgba(180, 69, 31, .10);
  --hm-safety-border: rgba(180, 69, 31, .30);

  --hm-info-text: #1B5FA8;
  --hm-info-surface: rgba(27, 95, 168, .10);
  --hm-info-border: rgba(27, 95, 168, .30);

  --hm-neutral-text: #5A6472;
  --hm-neutral-surface: rgba(90, 100, 114, .08);
  --hm-neutral-border: rgba(90, 100, 114, .26);

  /* ---- STATE ROLES -------------------------------------------------------
   * The seven-state envelope from the UI contract, mapped onto the status roles above. These
   * are ALIASES, not new colours, so a component asks for the state it is in rather than
   * picking a hue and hoping it means the right thing.
   *
   * The Build 1 names win. lib/actions.js resultState() already emits exactly these values and
   * the contract says not to introduce parallel vocabulary. So it is evidence_missing rather
   * than missing_evidence, and backend_error rather than backend_failure.
   *
   * loading is listed because the client owns it. It has no server producer by design: the
   * request has not come back yet. */
  --hm-state-loading: var(--hm-neutral-text);
  --hm-state-ready: var(--hm-ok-text);
  --hm-state-empty: var(--hm-neutral-text);
  --hm-state-evidence-missing: var(--hm-info-text);
  --hm-state-locked: var(--hm-neutral-text);
  --hm-state-unauthorized: var(--hm-neutral-text);
  --hm-state-loading-failed: var(--hm-warn-text);
  --hm-state-backend-error: var(--hm-danger-text);

  /* evidence_missing is info rather than warn or danger, and that is a product decision
   * expressed as a token. The requirements say missing evidence must not be styled as poor
   * condition. A red or amber gap in the record tells an owner their building is in trouble
   * when what is actually true is that we do not know. */

  /* ---- SHADOWS ----------------------------------------------------------- */
  --hm-shadow-sm: 0 1px 2px rgba(16, 24, 40, .04);
  --hm-shadow-md: 0 1px 2px rgba(16, 24, 40, .04), 0 10px 26px rgba(16, 24, 40, .06);
  --hm-shadow-lg: 0 2px 5px rgba(16, 24, 40, .05), 0 22px 48px rgba(16, 24, 40, .10);

  /* ---- FOCUS -------------------------------------------------------------
   * A visible focus ring is not optional and is not the browser default outline, which
   * disappears against a dark panel. Offset so the ring reads against a filled control. */
  --hm-focus-ring-color: #1B5FA8;
  --hm-focus-ring-width: 2px;
  --hm-focus-ring-offset: 2px;
  --hm-focus-ring: 0 0 0 var(--hm-focus-ring-offset) var(--hm-surface-1),
                   0 0 0 calc(var(--hm-focus-ring-offset) + var(--hm-focus-ring-width)) var(--hm-focus-ring-color);

  /* ---- TARGETS AND DENSITY ----------------------------------------------
   * Two densities, not one per role. The requirements ask for components that share structure
   * and accessibility while differing in density, so density is a property of the surface and
   * not a fork of the component.
   *
   * comfortable is the AHR homeowner default. compact is the professional console default.
   * Neither goes below the 40px touch target that verify_mobile.py measures, because a
   * dense table on a phone is still operated with a thumb. */
  --hm-target-min: 40px;
  --hm-row-comfortable: 56px;
  --hm-row-compact: 40px;
  --hm-gutter-comfortable: var(--hm-space-5);
  --hm-gutter-compact: var(--hm-space-3);

  /* ---- BREAKPOINTS -------------------------------------------------------
   * Recorded as values so script can read them. A media query cannot use a custom property, so
   * the numbers are repeated in the media query below and asserted equal by
   * scripts/test-tokens.js. That test exists because two sources of truth for a breakpoint
   * drift the moment one of them is edited alone. */
  --hm-bp-mobile-narrow: 360px;
  --hm-bp-mobile: 390px;
  --hm-bp-tablet: 768px;
  --hm-bp-laptop: 1024px;
  --hm-bp-wide: 1440px;
}

/* ===========================================================================
 * DARK
 *
 * Every token declared above that carries a colour is redeclared here. Anything not redeclared
 * is theme independent: spacing, radius, type, breakpoints. test-tokens.js asserts that the
 * set of colour tokens in both themes is identical, so a token added to light without a dark
 * value fails rather than falling back to a light colour on a dark panel.
 *
 * The existing pages set [data-theme] explicitly rather than following prefers-color-scheme,
 * so this matches them. Adding a prefers-color-scheme default is a 2C decision, because it
 * would change the appearance of surfaces on first load.
 * ======================================================================== */
[data-theme="dark"] {
  --hm-bg: #0B0D12;
  --hm-surface-1: #14171F;
  --hm-surface-2: #1A1E28;
  --hm-surface-3: #20252F;

  --hm-line: #232935;
  --hm-line-strong: #2B323F;

  --hm-ink: #F1F4F8;
  --hm-ink-soft: #C4CCD8;
  --hm-ink-mute: #98A2B3;
  --hm-ink-on-accent: #08131A;

  --hm-accent: #18BEA9;
  --hm-accent-text: #18BEA9;
  --hm-accent-surface: rgba(24, 190, 169, .13);
  --hm-accent-border: rgba(24, 190, 169, .38);

  --hm-ok-text: #3DBE7E;
  --hm-ok-surface: rgba(61, 190, 126, .14);
  --hm-ok-border: rgba(61, 190, 126, .34);

  --hm-warn-text: #E5A53B;
  --hm-warn-surface: rgba(229, 165, 59, .14);
  --hm-warn-border: rgba(229, 165, 59, .34);

  --hm-danger-text: #E57069;
  --hm-danger-surface: rgba(229, 112, 105, .14);
  --hm-danger-border: rgba(229, 112, 105, .34);

  --hm-safety-text: #F0916A;
  --hm-safety-surface: rgba(240, 145, 106, .14);
  --hm-safety-border: rgba(240, 145, 106, .34);

  --hm-info-text: #7FB2E8;
  --hm-info-surface: rgba(127, 178, 232, .14);
  --hm-info-border: rgba(127, 178, 232, .34);

  --hm-neutral-text: #98A2B3;
  --hm-neutral-surface: rgba(152, 162, 179, .10);
  --hm-neutral-border: rgba(152, 162, 179, .28);

  --hm-focus-ring-color: #7FB2E8;

  --hm-shadow-sm: 0 1px 2px rgba(0, 0, 0, .35);
  --hm-shadow-md: 0 1px 2px rgba(0, 0, 0, .35), 0 14px 34px rgba(0, 0, 0, .38);
  --hm-shadow-lg: 0 2px 6px rgba(0, 0, 0, .45), 0 28px 56px rgba(0, 0, 0, .55);
}

/* ===========================================================================
 * DENSITY SELECTORS
 *
 * Opt in per surface. A console sets data-density="compact" on a container and the rows inside
 * it tighten without any component knowing which product it is running in.
 * ======================================================================== */
[data-density="comfortable"] {
  --hm-row: var(--hm-row-comfortable);
  --hm-gutter: var(--hm-gutter-comfortable);
}

[data-density="compact"] {
  --hm-row: var(--hm-row-compact);
  --hm-gutter: var(--hm-gutter-compact);
}

/* ===========================================================================
 * RESPONSIVE
 *
 * Below tablet everything relaxes to comfortable regardless of what the surface asked for. A
 * compact 40px row is a desktop affordance driven by a mouse. On a phone the same row is a
 * thumb target, and the requirement that a primary action stay reachable outranks the
 * requirement that a professional surface stay dense.
 * ======================================================================== */
@media (max-width: 767px) {
  [data-density="compact"] {
    --hm-row: var(--hm-row-comfortable);
    --hm-gutter: var(--hm-gutter-comfortable);
  }
}

/* ===========================================================================
 * REDUCED MOTION
 *
 * Declared as a token so components read a duration rather than hard-coding one and then
 * needing their own media query to switch it off.
 * ======================================================================== */
:root {
  --hm-motion-fast: 120ms;
  --hm-motion-base: 200ms;
  --hm-motion-slow: 320ms;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --hm-motion-fast: 0ms;
    --hm-motion-base: 0ms;
    --hm-motion-slow: 0ms;
  }
}
