/* Version: 2026.08.16.11 | My Work gets its own tab router; the mode stops being called asana */
/* Version: 2026.08.16.02 | The scales that never existed: spacing, radius, elevation, type */
/*
 * DESIGN TOKENS — the values a theme changes, in the one place a theme changes them.
 *
 * This file loads FIRST, before core.css and before every page stylesheet, so anything below can
 * read a token and the admin console's `css_variables` block (emitted last in <head>) still wins
 * over all of it. Order is the whole mechanism: every one of these is a `:root` declaration of
 * equal specificity, so the last one written is the one that applies.
 *
 * ── What is here and what is NOT ─────────────────────────────────────────────────────────────
 * Here: the SCALES — spacing, radius, elevation, type, motion. They did not exist. Every padding,
 * every corner and every shadow in this app is a hardcoded pixel value chosen one rule at a time,
 * which is why the tree holds ~3,000 hex and dimension literals against ~1,300 `var()` uses, and
 * why "make the interface a little denser" is a thousand-line diff rather than one.
 *
 * NOT here: the COLOUR palette. That already exists, in core.css's `:root`, and it is not a
 * first draft — each tone carries its measured contrast ratio in a comment, and several exist as
 * pairs precisely because the obvious single value failed at 4.5:1 (`--gold` vs `--gold-ink`,
 * `--text-muted` vs `--text-subtle`, `--theme-color` vs `--theme-color-text`). Moving that here
 * would be a large diff that changes nothing and risks losing the reasoning attached to it. The
 * colours move when there is a reason to move them; the scales are new, so they start here.
 *
 * ── Naming ───────────────────────────────────────────────────────────────────────────────────
 * `--portal-*`, like every other identifier in this codebase (see CLAUDE.md). Where a `--portal-*`
 * token already existed for one of these ideas, it is DEFINED here and keeps its name, so nothing
 * that reads it has to change and there is still exactly one place it is set.
 */

:root {
  /* ── Spacing ────────────────────────────────────────────────────────────────────────────────
     A 4px base, which is what the existing layout already rounds to: the gaps written by hand
     across these stylesheets are overwhelmingly 4, 6, 8, 10, 12, 16, 20, 24 and 28. The scale is
     named by size rather than by purpose, because a purpose name ("--card-padding") multiplies
     into one token per component and stops being a scale. */
  --portal-space-1: 4px;
  --portal-space-2: 8px;
  --portal-space-3: 12px;
  --portal-space-4: 16px;
  --portal-space-5: 20px;
  --portal-space-6: 24px;
  --portal-space-8: 32px;
  --portal-space-10: 40px;
  --portal-space-12: 48px;

  /* ── Radius ─────────────────────────────────────────────────────────────────────────────────
     --portal-radius (8px) and --portal-radius-sm (6px) already existed in portal.css and are the
     two most-used corners in the app. They keep those exact values and move here, so the set is
     one scale rather than two tokens plus a hundred literals — nothing re-renders, there is just
     somewhere to put the next one. --portal-radius-pill is the fully-round control
     (buttons in the compose picker, the toggle pills, the reaction pills): 999px rather than 50%,
     because 50% on a non-square element gives an ellipse. */
  --portal-radius-xs: 4px;
  --portal-radius-sm: 6px;
  --portal-radius: 8px;
  --portal-radius-lg: 12px;
  --portal-radius-xl: 16px;
  --portal-radius-pill: 999px;

  /* ── Elevation ──────────────────────────────────────────────────────────────────────────────
     Four steps, and the alpha rises with the blur because a large soft shadow at the same opacity
     as a small tight one reads as dirt rather than as height. Tinted with the brand navy rather
     than pure black, which is what the hand-written shadows in this tree already do. */
  --portal-shadow-sm: 0 1px 3px rgba(13, 20, 48, 0.06);
  --portal-shadow: 0 4px 12px rgba(13, 20, 48, 0.08);
  --portal-shadow-lg: 0 10px 30px rgba(13, 20, 48, 0.10);
  --portal-shadow-xl: 0 20px 50px rgba(13, 20, 48, 0.15);

  /* ── Type ───────────────────────────────────────────────────────────────────────────────────
     The families are declared ONCE here, which they never were: 'Montserrat', sans-serif is
     written out in ~40 rules and 'Roboto' in a handful, and one of those handful asked for a face
     the page had never fetched, so it had been silently rendering as Arial since it was written.
     A theme that wants a different face changes these two lines.

     The size scale is the set already in use — 11 through 24 — named by role rather than by
     number, because a size in this app is always chosen for a role (a metadata line, a body
     paragraph, a card title) and naming it that way is what makes a density change one edit. */
  --portal-font: 'Montserrat', sans-serif;
  --portal-font-body: 'Roboto', Arial, sans-serif;
  --portal-font-display: 'Lora', Georgia, serif;

  /* ── The three faces are fetched from Google, and that is a decision, not an accident ────────
   * All three shells <link> Montserrat, Lora and Roboto from fonts.googleapis.com on the render
   * path of every page. It is the ONLY external origin in a CSP that otherwise has none, and it
   * carries the reader's IP and Referer to Google on every page view of every instance, whether or
   * not that organization uses a single Google service.
   *
   * Naming the families here is the prerequisite for changing that, and is what 8.5 owes: an org
   * that wants a system stack now edits three lines instead of the ~40 rules that wrote
   * "'Montserrat', sans-serif" out by hand. The remaining choice is the user's, because it costs
   * something either way:
   *   SELF-HOST   ~500 KB of woff2 committed to the repo, served from /fonts, CSP drops both
   *               Google origins. Identical rendering, no third party, larger repo.
   *   SYSTEM      set these three to a system stack, delete the <link>s. Free, instant, and the
   *               app stops looking the way it was designed to look.
   *   AS IS       one third-party request per page, on the render path.
   * Not decided here. What is decided is that it is now one edit rather than forty. */

  --portal-text-xs: 11px;     /* timestamps, counts, badge numerals */
  --portal-text-sm: 12px;     /* metadata lines, chips, help text */
  --portal-text-base: 13px;   /* the app's body size — denser than the web default by design */
  --portal-text-md: 14px;     /* list rows, form controls */
  --portal-text-lg: 16px;     /* card titles, dialog headings */
  --portal-text-xl: 20px;     /* page titles */
  --portal-text-2xl: 24px;    /* the one-per-page hero heading */

  --portal-weight-normal: 400;
  --portal-weight-medium: 600;
  --portal-weight-bold: 700;
  --portal-weight-heavy: 800;

  --portal-leading-tight: 1.25;
  --portal-leading: 1.45;
  --portal-leading-loose: 1.6;

  /* ── Motion ─────────────────────────────────────────────────────────────────────────────────
     --portal-transition already existed in portal.css as `all 0.2s cubic-bezier(0.4, 0, 0.2, 1)`
     and is reassembled here from its two parts, unchanged, so a theme can retime the app without
     restating the easing. The scale around it is new. Every one is short: this is an app people
     work in all day, and the animation budget is "confirm that something happened", not
     "perform".

     prefers-reduced-motion is honored globally below rather than per-component, so a rule that
     reads a token gets it for free and cannot forget. */
  --portal-motion-fast: 0.12s;
  --portal-motion: 0.2s;
  --portal-motion-slow: 0.3s;
  --portal-ease: cubic-bezier(0.4, 0, 0.2, 1);
  --portal-transition: all var(--portal-motion) var(--portal-ease);

  /* ── Layout ─────────────────────────────────────────────────────────────────────────────────
     Widths that more than one stylesheet needs to agree about. --portal-sidebar-width already
     existed; the content measure is new and is the one number that decides how wide a column of
     prose gets before it becomes hard to read. */
  --portal-sidebar-width: 280px;
  --portal-measure: 68ch;
}

/* Every animated token, neutralized in one place. A component that times itself through
   --portal-transition or --portal-motion is covered without knowing this rule exists (WCAG 2.3.3).
   It belongs here rather than in core.css because these are the tokens it neutralizes.

   Deliberately NOT the blanket `*, *::before, *::after { animation-duration: 0.01ms !important }`
   that usually accompanies this: several things in this app animate because the animation IS the
   information — the loading spinner, the sync indicator — and stopping a spinner one frame in
   reads as broken rather than as calm. The components that should stop already say so themselves
   (see the prefers-reduced-motion blocks in my-work.css and core.css). This makes the token-driven
   ones free; it does not overrule a component that thought about it. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --portal-motion-fast: 0.01ms;
    --portal-motion: 0.01ms;
    --portal-motion-slow: 0.01ms;
    --portal-transition: none;
  }
}
