/*
 * frame.css
 * The Frame and Page tiers of the vocabulary — the application shell and the
 * routed page inside it. These were contract-only through v0.6: eleven of the
 * twenty-nine terms existed as prose with no CSS behind them.
 *
 *   Frame   App <body> · Topbar <header> · Sidebar <nav> · Shell <div>
 *   Page    Screen <main> · Pane <section aria-labelledby> · View <article>
 *
 * Anatomy:
 *
 *   <body class="app">
 *     <a class="skip-link" href="#screen">Skip to content</a>
 *     <div class="shell">
 *       <header class="topbar"> … </header>
 *       <nav class="sidebar" aria-label="Main"> … </nav>
 *       <main class="screen" id="screen" tabindex="-1">
 *         <section class="pane" aria-labelledby="billing-h">
 *           <div class="section-header">
 *             <h2 id="billing-h">Billing</h2>
 *           </div>
 *           <article class="card"> … </article>
 *         </section>
 *       </main>
 *     </div>
 *   </body>
 *
 * Shell is the only one that positions anything; the rest just claim a grid
 * area. That means Topbar, Sidebar and Screen are still usable on their own.
 *
 * ── The frame has its own grounds ────────────────────────────────────
 *
 * --app-bg, --topbar-bg and --sidebar-bg, each falling back to the
 * neutral it used to hardcode. Until they existed the shell could not be
 * themed apart from the content at all: two themes hit it independently,
 * and basecamp.css records in its own header that the prototype's
 * distinct sidebar (#0e1019) and modal (#12151f) surfaces both had to be
 * collapsed into --surface. The Dialog half is --dialog-bg, in
 * dialogs.css.
 *
 * They are use-site fallbacks rather than :root declarations because a
 * `--topbar-bg: var(--surface)` at :root resolves ONCE, against :root's
 * own --surface, and inherits that color past every .theme-* — the
 * alias trap tokens.css rules on.
 *
 * ── What a token cannot do, and what to write instead ────────────────
 *
 * A ground is a background. It is not an ink ramp, so a LIGHT app with a
 * dark sidebar does not follow from --sidebar-bg: the nav labels inside
 * still read --ink-soft, tuned for the light surface, and land dark on
 * dark. The answer is already in the package and costs nothing, because
 * a theme is a class of inheriting tokens and nothing else:
 *
 *   <nav class="sidebar theme-dark" aria-label="Main">
 *
 * Every descendant inherits the whole dark ramp — ink, rules, surfaces,
 * the tone-as-text window. Use --sidebar-bg to separate grounds WITHIN
 * one ramp (basecamp's three darks); use a theme class to invert.
 */

/* ── App — the page surface ──────────────────────────────────────────
 * Goes on <body>. The package has no global element styling by design, so
 * this is opt-in rather than a bare `body` rule that would fight a host app.
 */
.app {
  margin:           0;
  min-block-size:   100vh;
  min-block-size:   100dvh;
  background:       var(--app-bg, var(--surface-sunken));
  color:            var(--ink);
  font-family:      var(--font-primary);
  -webkit-font-smoothing: antialiased;
}

/* ── Shell — the grid that positions the frame ───────────────────────
 * Topbar spans the full width; Sidebar and Screen sit beneath it.
 */
.shell {
  display:               grid;
  grid-template-columns: auto 1fr;
  grid-template-rows:    auto 1fr;
  grid-template-areas:
    "topbar  topbar"
    "sidebar screen";
  min-block-size: 100vh;
  min-block-size: 100dvh;
}

/* Sidebar runs full height with the Topbar beside it instead of above. */
.shell.sidebar-first {
  grid-template-areas:
    "sidebar topbar"
    "sidebar screen";
}

/*
 * Viewport variant — the shell is exactly one viewport and the Screen scrolls
 * inside it, rather than the document scrolling. The app-like mode; costs you
 * document-level scroll restoration, so it is opt-in.
 *
 * Named `.viewport` since v0.10.1. It was `.shell.fixed`, which is a name
 * UnoCSS and Tailwind already own — `fixed` is `position: fixed` there, it is
 * generated unlayered, and unlayered beats every layer, so simply having Uno
 * installed turned the shell into a fixed-positioned element. The package
 * advertises Uno compatibility; squatting on a core utility name is not that.
 */
.shell.viewport {
  block-size:     100vh;
  block-size:     100dvh;
  min-block-size: 0;
  overflow:       hidden;
}
.shell.viewport > .sidebar,
.shell.viewport > .screen {
  overflow-y: auto;
}

/* ── Topbar ──────────────────────────────────────────────────────── */
.topbar {
  grid-area:       topbar;
  display:         flex;
  align-items:     center;
  justify-content: space-between;
  gap:             var(--space-2xl);
  /*
   * A MINIMUM, not a height. A .cluster is `flex-wrap: wrap` and the two are
   * paired in this package's own frame docs, so a bar holding more than fits
   * laid a second row inside a fixed box and centered both — drawing half its
   * contents above the bar and half below, over the page, with no horizontal
   * overflow at any width for the usual smell test to catch.
   *
   * The shell's row is already `auto`, so the bar grows instead and nothing
   * else in the package reads this token.
   */
  min-block-size:  var(--topbar-height);
  padding-block:   var(--space-xs);
  padding-inline:  var(--space-2xl);
  background:      var(--topbar-bg, var(--surface));
  border-block-end: var(--border-width) solid var(--rule);

  /* Sticks when the document scrolls. In .shell.viewport it never needs to. */
  position: sticky;
  inset-block-start: 0;
  z-index:  20;
}

/* ── Sidebar ─────────────────────────────────────────────────────── */
.sidebar {
  grid-area:         sidebar;
  inline-size:       var(--sidebar-width);
  padding:           var(--space-2xl) var(--space-lg);
  background:        var(--sidebar-bg, var(--surface));
  border-inline-end: var(--border-width) solid var(--rule);
}

/* ── Screen — the routed page body ───────────────────────────────── */
.screen {
  grid-area: screen;
  padding:   var(--screen-pad);

  /*
   * Grid items default to min-inline-size:auto, which means a wide child —
   * a table, a long <pre>, an overflowing flex row — can push the whole
   * grid wider than the viewport instead of scrolling inside itself. This
   * one line is what stops the entire app layout blowing out sideways.
   */
  min-inline-size: 0;
}

/*
 * ── Pane — a labeled subdivision of a Screen ─────────────────────
 *
 * States no `display`. A Pane is a semantic subdivision, not a layout mode,
 * and a <section> is already block — so the declaration bought nothing and
 * cost the ability to compose. `components` is a LATER layer than `layout`,
 * and both selectors are (0,1,0), so `display: block` here beat `.stack` and
 * `.cluster` outright: `class="pane stack"` laid out as a block, silently,
 * with the gap doing nothing. Same collision the header of layout.css
 * documents for `.bar.center`, resolved the other way — a Pane that wants
 * to own the space between its children says so with a layout class.
 */
.pane {
  margin-block-end: 2rem;
}
.pane:last-child {
  margin-block-end: 0;
}

/* ── View — one switchable panel inside a Pane ───────────────────── */
.view {
  display: block;
}
/*
 * A [hidden] element is display:none by UA default, but any `display`
 * declaration beats that — so a .view that ever gains a display value would
 * stay visible while claiming to be hidden. Restate it.
 */
.view[hidden] {
  display: none;
}
/* Focus ring: focus.css. A View carries tabindex="0" so a keyboard user
   can reach panel content that holds nothing focusable of its own. */

/*
 * ── Narrow viewports ─────────────────────────────────────────────────
 * Below md the Sidebar column collapses and the Shell becomes one column.
 * The sidebar's contents belong in a <dialog class="drawer"> at this size —
 * that is a behavior (open/close), so it is the app's job, not a class's.
 */
@media (max-width: 767px) {
  .shell,
  .shell.sidebar-first {
    grid-template-columns: 1fr;
    grid-template-areas:
      "topbar"
      "screen";
  }
  .shell > .sidebar {
    display: none;
  }
  .screen {
    padding: var(--space-2xl);
  }
}

/*
 * ── Sidebar toggle — the button that opens the drawer ────────────────
 *
 * The rule above hides the Sidebar below md and hands its contents to a
 * <dialog class="drawer">. That instruction was incomplete: something has
 * to open the drawer, and that something must be hidden *above* md, where
 * the real sidebar is back. The package created the need and then had no
 * way to express it, so the demo wrote the media query by hand — as would
 * every other app using .shell.
 *
 *   <header class="topbar">
 *     <button class="btn ghost square sidebar-toggle"
 *             aria-label="Open navigation" aria-expanded="false">…</button>
 *
 * Opening it is still the app's job (Principle 6) — this only decides
 * when the control exists. It is deliberately one class tied to this one
 * contract rather than a general responsive-visibility set: the
 * breakpoints are literals, and a full `.md-up`/`.md-down` matrix is ten
 * rules of somebody else's design system.
 */
.sidebar-toggle {
  display: none;
}
@media (max-width: 767px) {
  /*
   * `revert` rather than a hardcoded `inline-flex`, so the control keeps
   * whatever display its own class gives it — .btn is inline-flex today,
   * but this rule should not be the thing that decides that.
   */
  .sidebar-toggle {
    display: revert;
  }
}
