@layer theme, base, components, sections, template, utilities;

/* @homepages/template-kit — the base stylesheet every template's page loads.
 *
 * Exported as `@homepages/template-kit/base.css`. It is NOT part of the Tailwind
 * entry (that is styles.css); it ships as its own stylesheet on the page.
 *
 * Contains:
 *   - The cascade-layer order declaration above — the one place it is stated
 *   - The three reset gaps Tailwind's preflight leaves (below)
 *   - The section box model (.tr-section, emitted by the <Section> primitive)
 *   - The frame shell's static half (.tr-frame, emitted by the <FrameShell> primitive)
 *     and the window witness that holds every host to the geometry it owes that shell
 *   - The <Image>/<Video>/<Tour> box's geometry default (.tr-image-block), layered so
 *     an author's own width utility beats it
 *   - The <Image>/<Video> frame's placeholder fill (.tr-image-frame)
 *   - Universal tokens: motion
 *
 * REQUIRES A TAILWIND ENTRY ON THE PAGE. Your entry's `@import "tailwindcss"` brings
 * Tailwind's preflight, and preflight does the reset: box-sizing, margin/padding,
 * list-style, the button and anchor resets, display:block on replaced elements. This
 * file only fills the three gaps preflight leaves. Ship base.css without a Tailwind
 * entry and you get an unreset page.
 *
 * NOT here: anything a template owns. Colors, fonts, type, radii, shadows and layout
 * — and the `body` document defaults that apply them — all come from your theme.ts,
 * compiled by compileThemeToCss. This file references no theme token at all.
 */

/* ----- Reset gaps ------------------------------------------------------- *
 * The four declarations Tailwind's preflight does NOT make. Each is load-bearing:
 * delete one and your page changes.
 *
 * Wrapped in `@layer base` so Tailwind's utilities (which compile into
 * `@layer utilities`) win over these element defaults. Unlayered, `button { cursor:
 * pointer }` would beat a layered `.cursor-default` utility outright — unlayered
 * always wins over layered in the cascade.
 */

@layer base {
  /* preflight carries no font-smoothing rule at all */
  body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
  /* Tailwind v4 deliberately leaves buttons at the UA default `cursor: default` */
  button { cursor: pointer; }
  /* preflight caps max-width on img and video, but not on svg */
  svg { max-width: 100%; }
  /* A <source> carries no content and never renders, but nothing says so: the UA sheet
   * leaves it an ordinary box and preflight does not touch it. That is inert until a
   * `display: contents` <picture> puts its children in someone else's formatting
   * context — which is exactly what <Image> does so the <img> is the caller's own flex
   * or grid child. Without this, every alternate format in the ladder is a real item in
   * that caller's box: a two-column card gets a phantom first row and its row gap, and
   * only on a page whose assets have a multi-format ladder to begin with. */
  source { display: none; }
}

/* ----- Primitives ------------------------------------------------------- *
 * Layered like the reset gaps and for the same reason: these are the primitives'
 * DEFAULTS, and an author's utility on the same element must win over them. What an
 * author may not override is a contract question, and `template-kit check` grades it
 * (section-root-margin) — never cascade position, which would discard the author's
 * declaration with no signal.
 */

@layer base {
  /* The full-bleed section root, emitted by the <Section> primitive. Zero margin is
   * the contract that lets adjacent sections butt edge-to-edge, so a page composes as
   * a vertical stack with no framework-injected whitespace; padding is the author's —
   * on this root or a child, the background still paints edge-to-edge under it. */
  .tr-section {
    display: block;
    width: 100%;
    padding: 0;
    margin: 0;
  }

  /* The frame shell's static half, emitted by the <FrameShell> primitive. The frame
   * is a viewport: every host owes a frame document a window that is exactly the
   * format's BLEED box, and the root fills it — viewport units and geometry @media in
   * sections read the window and get frame geometry by construction. No size
   * container here: containers inside sections are ordinary component styling. The
   * root clips at its own edge to match capture, and stacks its direct children —
   * the page's sections — as a rigid flex column.
   *
   * position: relative is for the witness below and nothing else. It positions
   * against a box identical to the one it would otherwise use (the root fills the
   * window exactly), so it moves no author content; what it buys is a stacked print
   * document, where each shell then anchors its own witness on its own sheet. */
  .tr-frame {
    position: relative;
    width: 100vw;
    height: 100vh;
    overflow: clip;
    display: flex;
    flex-direction: column;

    /* The window the host owes this document: the bleed box, i.e. the trim box
     * extended symmetrically by the bleed. */
    --tr-frame-window-w: calc(var(--frame-w) + 2 * var(--frame-bleed));
    --tr-frame-window-h: calc(var(--frame-h) + 2 * var(--frame-bleed));

    /* 1 when the real window misses the owed one on either axis, 0 when it does not.
     * max(d, -d) is an absolute difference and `/ 1px` makes it a plain number. The
     * -1 is the tolerance: a browser window is whole CSS px while a physical format's
     * box is fractional, so a capturer rounds and is right to. Multiplying past the
     * tolerance saturates the clamp, so the witness is on or off, never faded. */
    --tr-frame-window-mismatch: clamp(
      0,
      (
        max(
          (100vw - var(--tr-frame-window-w)) / 1px,
          (var(--tr-frame-window-w) - 100vw) / 1px,
          (100vh - var(--tr-frame-window-h)) / 1px,
          (var(--tr-frame-window-h) - 100vh) / 1px
        ) - 1
      ) * 10000,
      1
    );
  }

  /* The window witness. A frame document is the only place that holds both halves of
   * the host's obligation — the geometry the format declares (the tokens) and the
   * window the host actually gave (100vw/100vh) — so it is the only place the
   * obligation can be checked rather than trusted. Stated in CSS, in the base
   * stylesheet every frame document links, so no host opts in and none can forget.
   *
   * Silent when the two agree: fully transparent, absolutely positioned (never a flex
   * item in the rigid stack), so a correctly windowed document renders byte-identical
   * pixels and identical layout to one with no witness at all. On a mismatch it covers
   * the artifact and names both geometries, because a frame measured against the wrong
   * box is not a preview of anything and must not read as one. */
  .tr-frame::after {
    counter-reset:
      tr-frame-owed-w calc(var(--tr-frame-window-w) / 1px)
      tr-frame-owed-h calc(var(--tr-frame-window-h) / 1px)
      tr-frame-got-w calc(100vw / 1px)
      tr-frame-got-h calc(100vh / 1px);
    content: "FRAME WINDOW MISMATCH — this format declares a "
      counter(tr-frame-owed-w) "×" counter(tr-frame-owed-h)
      " window; the host gave " counter(tr-frame-got-w) "×" counter(tr-frame-got-h)
      ". Every viewport unit and geometry query in this frame is resolving against the wrong box.";
    position: absolute;
    inset: 0;
    z-index: 2147483647;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 6%;
    background: #b3261e;
    color: #ffffff;
    font: 700 clamp(11px, 2.2vw, 26px)/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
    opacity: var(--tr-frame-window-mismatch);
    pointer-events: none;
  }

  /* Imposed from OUTSIDE <Section>, which stays byte-identical across output classes:
   * without it flexbox would compress an overflowing stack to fit, hiding the overflow
   * the author must be able to see. */
  .tr-frame > * {
    flex-shrink: 0;
  }

  /* The geometry DEFAULT of every <Image>/<Video>/<Tour> box — the unframed element
   * itself, and the framed wrapper. It is a class here rather than the `block w-full` the
   * primitives used to prepend to the caller's className, because a utility cannot serve
   * as a default: both would compile into `@layer utilities` and tie on layer AND on
   * specificity, leaving sheet order to decide — and Tailwind emits `.w-full` after
   * `w-auto`, `size-*` and every arbitrary `w-[…]`, so the author's width lost and the
   * element silently drew at its parent's full width. Three shipped incidents in a month
   * were each fixed by hand with an `!`. Layered, an authored width wins outright and
   * needs none.
   *
   * Deliberately separate from .tr-image-frame below, which templates apply by hand to
   * boxes of their own — flex items, a rail card, a button. Those want the fill and not
   * this geometry, so the two must not be one class.
   *
   * Nothing else belongs here: the box is the caller's. `relative` and `overflow-hidden`
   * stay utilities on the framed wrapper, where the <img> absolutely fills it — those are
   * the frame's contract, not a default an author may replace. */
  .tr-image-block {
    display: block;
    width: 100%;
  }

  /* The <Image>/<Video> frame's placeholder fill, showing while an asset is absent or
   * loading. It is a plain class rather than a `bg-*` utility on the primitive because a
   * template names its own tokens — there is no colour token these primitives may assume
   * exists. Both frames share this one class (and one token) deliberately: the fill is the
   * same neutral rect, so a template retints it once. Templates also apply it by hand to
   * their own gallery boxes for that fill alone, so it carries no geometry.
   * Retint it by setting --tr-image-frame-bg in your theme's layout tokens. */
  .tr-image-frame {
    background-color: var(--tr-image-frame-bg, oklch(0.97 0 0));
  }
}

/* ----- Universal tokens ------------------------------------------------- */

:root {
  /* Motion */
  --tr-duration-fast: 150ms;
  --tr-duration-base: 250ms;
  --tr-ease: cubic-bezier(0.2, 0.8, 0.2, 1);

  /* The height of the window this page is being viewed in — what `100svh` means
   * on a published page, which is what this resolves to there and in a frame
   * document, whose window is the format's own bleed box.
   *
   * It is a token rather than the unit written in place because an editing
   * canvas has no window to speak of: it lays the page out at a real width but
   * measures the rendered result for its height, so `100svh` inside one names a
   * box the section's own height is what determines, and a section asking for it
   * grows on every pass without ever settling. The canvas kernel redeclares this
   * token as the screen its card stands for — a constant, so one pass settles it
   * — and a box sized from the window therefore writes ONE thing and is honest on
   * both surfaces. Spell it as a floor (`min-height`), never a fixed height, so
   * content taller than a screen still grows the box instead of overflowing it. */
  --tr-window-h: 100svh;
}
