/* Smart Mirror — base shell.
 *
 * Hard rules (CLAUDE.md §"The mirror page", PLAN §2):
 *   - Background #000000. Text white/grey. No images.
 *   - Exactly ONE accent colour, reserved for severe weather alerts (§5.5).
 *   - No animation, transition, blur, box-shadow, backdrop-filter, video.
 *     These tip a VideoCore IV into software rasterisation and 100% CPU.
 *   - No `px` except 1px hairline borders. Sizes are rem / em / cq*.
 *   - No @media anywhere in module CSS — @container only.
 */

:root {
  /* --- Canvas geometry. Rewritten per profile by core/grid.js applyCanvas(). --- */
  --cols: 12;
  --rows: 20;

  /* Frame lip (PLAN §2.1 `safeInset`). The profile carries bare NUMBERS meaning
     PERCENT; grid.js appends the `%`. Defaults here carry it explicitly. */
  --safe-top: 2%;
  --safe-right: 2%;
  --safe-bottom: 2%;
  --safe-left: 2%;

  /* Type-scale multiplier. 1 = default; profile/admin UI overrides for taste
     or from canvas.physical (diagonal + view distance). */
  --scale: 1;

  /* THE accent. Severe weather alerts ONLY. Nothing else may use it — not a
     highlight, not a link, not a "today" marker. One red on the whole mirror. */
  --accent: #ff3b30;

  /* The grey ramp. This file is the ONLY place a colour literal is allowed;
     colour is a CLOSED SET and module CSS reaches these as `var(--fg-3)`.
     Roles are defined in contract.js — keep the two in step:

       --fg        primary — the time, the temperature. Read across a hall.
       --fg-2      secondary — labels, day names, hi/lo.
       --fg-3      tertiary — supporting detail.
       --fg-4      de-emphasised — units, captions.
       --fg-5      faintest — stale timestamps. Near the floor of legibility.
       --hairline  the only permissible border colour. Not for text.

     Five steps, not fourteen, and that is a display constraint rather than a
     taste call: behind ~30%-transmission glass at 3-6 feet, adjacent greys stop
     being distinguishable long before the panel stops resolving them.

     The bottom two steps are the ones to re-check against the real glass sample
     before the frame is built — --fg-5 is deliberately close to invisible, which
     is right for a stale marker in a dark hallway and may be too far in daylight. */
  --fg: #ffffff;
  --fg-2: #d0d0d0;
  --fg-3: #a8a8a8;
  --fg-4: #8a8a8a;
  --fg-5: #6e6e6e;
  --hairline: #262626;

  /* PLAN §2.2. vmin base = constant fraction of the display = constant angular
     size at the distance each mirror size is actually viewed from. */
  font-size: calc(var(--scale) * 1.15vmin);
  color-scheme: dark;
}

/* --- Minimal reset --------------------------------------------------------- */

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

/* Kiosk: there is no pointer, and a stray arrow parked mid-screen is forever. */
* {
  cursor: none !important;
}

html,
body {
  height: 100%;
}

/* ---------------------------------------------------------------------------
   Fonts — self-hosted and subset. No CDN, no Google Fonts link: a
   network-loaded font is a blank mirror the first time DNS hiccups.

   TWO FACES, split by `unicode-range`, and the split is what makes the weight
   budget work. CLAUDE.md counts the shell budget PER PROFILE, not per catalog,
   and a browser only downloads a face when a codepoint in its range is actually
   used. So a 6x10 makeup profile mounting clock, date and sun never requests
   mirror-symbols.woff2 at all — 940 B it does not pay, falling out of the CSS
   with no build-step branching. woff2 is brotli-compressed internally, so
   Content-Encoding does nothing and every one of those bytes is on the wire.

   `font-weight: 100 900` ON BOTH FACES IS LOAD-BEARING, NOT DECORATION.
   These are static faces, but a face declared with a weight RANGE satisfies
   every requested weight from the one real design, so synthesis never runs.
   Six modules ask for 300 against a body of 400; declared as `300` instead, a
   future `font-weight: 700` would render as a smeared synthetic bold and
   nobody would know why. This is what makes "one weight, never a family"
   structural rather than a rule someone has to remember.

   `font-display: block` rather than `swap`: a mirror is glanced at, and a
   flash of fallback text that reflows the clock is worse on a wall than a
   few hundred milliseconds of nothing.
   --------------------------------------------------------------------------- */

/* Text: everything except the ten weather glyphs. */
@font-face {
  font-family: MirrorText;
  src: url('../fonts/mirror-text.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: block;
}

/* Symbols: the ten weather glyphs only, one design at one diameter so the four
   circle fills in a forecast strip cannot come from two different typefaces. */
@font-face {
  font-family: MirrorSymbols;
  src: url('../fonts/mirror-symbols.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: block;
  unicode-range: U+21AF, U+2217, U+2234, U+2261, U+22EE,
                 U+25C7, U+25CB, U+25CF, U+25D1, U+25D4;
}

body {
  margin: 0;
  background: #000000;
  color: var(--fg);
  /* MirrorText first: it wins for every codepoint it has, and MirrorSymbols is
     only consulted for the ten in its unicode-range. */
  font-family: MirrorText, MirrorSymbols;
  font-weight: 400;
  line-height: 1.15;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeSpeed;
}

h1, h2, h3, h4, p, figure, ul, ol {
  margin: 0;
  padding: 0;
}

ul, ol {
  list-style: none;
}

/* --- The canvas ------------------------------------------------------------ */

/* One CSS Grid, `1fr` everywhere, so the geometry is resolution-independent by
   construction: the same profile fills 600x1024 and 1080x1920 with no code path
   aware of either number. Absolutely positioned against the initial containing
   block, so the percent insets resolve against the viewport. */
#canvas {
  position: absolute;
  inset: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left);
  display: grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  grid-template-rows: repeat(var(--rows), 1fr);
}

/* --- Module boxes ---------------------------------------------------------- */

/* The three core guarantees modules are allowed to rely on (contract.js):
   grid-area, overflow:hidden, container-type:size. Do not remove any of them.

   `grid-area` is row-start / column-start / row-end / column-end.
   --x and --y arrive here ALREADY 1-BASED: the profile's at.x/at.y are
   0-indexed but CSS grid lines are 1-indexed, and core/grid.js adds the 1.
   (The snippet in PLAN §2.1 omits that; it is wrong as literally written.) */
.module {
  grid-area: var(--y) / var(--x) / span var(--h) / span var(--w);
  overflow: hidden;
  container-type: size;
  /* Grid items default to min-content minimums, which would let a wide child
     push a module past its cells and defeat the clip. */
  min-width: 0;
  min-height: 0;
}

/* --- Shared marks ---------------------------------------------------------- */

/* contract.js CSS rule 5: stale data renders last-known-good plus `.stale-mark`.
   Never blank, never an error string, never `undefined`.

   --fg-3, NOT --fg-5: through glass --fg-5 falls below the 3:1 large-text floor
   and is a night-only colour. Measurements: docs/research/design-references.md
   §5. Not --fg-2 either — the marker must stay QUIET. But quiet must not mean
   absent: this is the mark separating "we know, it is a bit old" from "we could
   not find out", and on severe alerts that distinction is the whole point. */
.stale-mark {
  color: var(--fg-3);
  font-size: 0.7em;
  font-variant-numeric: tabular-nums;
}
