/* ==========================================================================
   layout.css — PAGE LAYOUT + THE DOCUMENT SHELL
   --------------------------------------------------------------------------
   P1-06 (app shells): the staff bottom tab-bar (mobile, <1024px), the admin
   desktop sidebar (>=1024px), the letterbox slot, the bare auth shell, and a
   kiosk shell scaffold. This file PLACES the frozen tab-bar/tabs components
   from components.css (positioning only — their own styling is untouched)
   and builds the sidebar as SHELL LAYOUT (no `.sidebar` component exists;
   Prereq 0.3 — a sidebar is app-shell layout, like .page-header, not a new
   named component).

   Colour is tokens only (DL04, no exceptions — `transparent`/`initial` count).
   A few raw lengths are layout facts rather than design tokens (the sidebar
   column width, the narrow auth-shell max-width) — same convention the drawer
   component already uses (components.css, `width: min(360px, 100%)`).
   ========================================================================== */

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

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-body);
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
}

/* --- The document shell ---------------------------------------------------
   Mobile-first: these rules ARE the 390px layout. The only breakpoint below
   caps the line length on desktop; it does not rearrange anything. */
.page-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target-primary);
  padding: var(--space-3);
  border-bottom: var(--border-width) solid var(--color-line);
  background: var(--color-surface);
}

/* Header brand lockup (Charles-directed extension, 2026-07-19, same session as
   the login wordmark ruling): replaces the plain-text "Fire & Fly Portal"
   placeholder with the real brand-corpus wordmark asset
   (static/img/wordmark-red.png, docs/design-references/fireandfly-brand.md:73).
   `.page-header__org`'s old placeholder-text role (D02 white-label future: this
   becomes the logged-in organisation's own name/mark) is carried forward as a
   Charles-overridable For-the-Inbox note, not resolved here — single-tenant
   Fire & Fly today, same as the text it replaces.

   TWO variants were built and rendered live for Charles to choose between
   (docs/status/fix-design-vocabulary-overhaul.md records both screenshots):
   (a) wordmark alone; (b) wordmark + a quiet "PORTAL" descriptor. This is
   variant (b), shipped — see the card for the rationale. `.page-header__brand`
   is a link to Home for an authenticated session (base.html only renders it as
   an <a> when current_user.is_authenticated; anonymous/auth-shell pages get a
   plain non-interactive <img>, matching the same authenticated-only pattern
   the nav slot already uses) — D116 interaction treatment applies since it IS
   a link: a hover lift + the same :focus-visible ring every other interactive
   shell control uses (app-sidebar__item above, DL-consistent). */
.page-header__brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: inherit;
  text-decoration: none;
  border-radius: var(--radius-input);
  transition: transform var(--transition-quick);
}
a.page-header__brand:hover {
  transform: translateY(-1px);
}
a.page-header__brand:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* Height-fitted to the touch-target scale the nav itself uses (optical
   alignment, Charles's brief) rather than the old text's line-height; width
   is intentionally unset (auto) so the asset's own aspect ratio governs it —
   same convention as `.auth-mark` above. */
.page-header__mark {
  display: block;
  height: var(--touch-target-compact);
  width: auto;
}

/* Quiet, clearly-subordinate descriptor next to the mark — small caps, muted,
   never competing with the wordmark's own red for attention (D116 ruling 2:
   Flame is the mark's fixed brand content, not routine chrome, so it is exempt
   from the "no Flame at rest" sweep the same way `.card--now` is; the
   descriptor text itself carries no accent colour at all). */
.page-header__brand-label {
  color: var(--color-muted);
  font-size: var(--font-size-caption);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* The nav slot is empty only for an anonymous visitor (base.html's else branch) —
   an authenticated request always populates it with the tab-bar or the sidebar.
   Kept so an unexpected empty render (an authenticated role with no rank in
   ROLE_RANKS — modules/middleware.py fails that closed, never "treat as staff")
   still collapses cleanly instead of leaving a hairline box with nothing in it. */
.page-nav:empty {
  display: none;
}

/* --- Letterbox (messages) — placement only; Phase 5 wires the real surface.
   Inert today (Decision 1 applies to it too: no Messages route exists yet), so
   it is a plain <span>, never a link/button — see base.html. `position: relative`
   anchors a future unread `.badge` in the same top-right spot the tab-bar's own
   badge uses (components.css `.tab__icon .badge`), so wiring the count later is
   a markup-only change, not a CSS one.

   Colour is `--color-muted` at FULL strength, not dimmed further — the 4-lens
   accessibility review (R1) found that stacking an opacity multiplier on TOP
   of an already-muted token drops a meaningful icon below the 3:1 UI-component
   floor in both themes (a blocker). `--color-muted` alone is the design
   system's own certified "quiet but legible" tier (design-system.css) — that
   is de-emphasis enough; pointer-events:none (inert items, below) is what
   actually communicates "does nothing", not a fainter colour. */
.letterbox {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: auto;
  width: var(--touch-target-row);
  height: var(--touch-target-row);
  border-radius: var(--radius-pill);
  color: var(--color-muted);
  font-size: var(--font-size-lg);
  pointer-events: none;
}
.letterbox .badge {
  position: absolute;
  top: calc(var(--space-1) * -1);
  right: calc(var(--space-1) * -1);
}

.page-main {
  max-width: var(--content-max-width);
  margin: 0 auto;
  padding: var(--space-3);
}

/* --- Staff bottom tab-bar placement (mobile, <1024px) ----------------------
   The .tab-bar/.tab/.tab__icon/etc. STYLING lives in components.css (P1-04,
   frozen) — this only pins the placed component to the viewport bottom.
   safe-area-inset-bottom clears notch/home-indicator devices; .page-main's
   padding-bottom (below) reserves the same space so content never sits under it.

   Scoped to `.page-nav.tab-bar` (the real shell nav, base.html:72), NOT the
   bare `.tab-bar` class (P1-08 finding A1 / DL14): `.tab-bar` is a reusable
   components.css class, and styleguide.html's own component-inventory demo
   legitimately reuses it for an in-page, non-fixed preview. A bare-class
   placement rule fixed BOTH instances to the identical viewport position,
   and the demo — later in DOM order, same z-index — silently occluded and
   functionally replaced the real shell's only live nav link. The compound
   selector below only ever matches the one real shell instance. */
.page-nav.tab-bar {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 50;
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* Inert (unbuilt) tab-bar items — the component's own default (non-.on) look
   (components.css: muted colour, normal weight) is already the de-emphasised
   state relative to `.tab.on`'s seated pill + bold ink label, so no extra
   dimming is layered on here (R1 accessibility review: an opacity multiplier
   on top of the already-muted token fails the 3:1 UI floor). pointer-events:none
   stops components.css's `:not(.on):hover` rule from painting interactive
   feedback on something that does nothing. */
.tab[aria-disabled="true"] {
  pointer-events: none;
}

/* Reserve room at the bottom of the page for the fixed tab-bar — only when one
   is actually rendered (owner/manager sessions have no tab-bar and would gain
   dead space for nothing). Scoped to <1024px: the tab-bar is display:none at
   desktop (below), so the padding would otherwise outlive its own reason.
   Scoped to `.page-nav.tab-bar` (Codex round 1, same DL14 pattern as the
   placement rules above): a bare `.tab-bar` check also matches styleguide.html's
   demo section, so an anonymous/owner/manager session on /styleguide — which
   never renders the real fixed nav — was still reserving bottom padding for a
   shell tab-bar that does not exist on that page, purely because the unrelated
   demo instance shares the class name. */
@media (max-width: 1023.98px) {
  body:has(.page-nav.tab-bar) .page-main {
    padding-bottom: calc(var(--nav-height) + env(safe-area-inset-bottom, 0px));
  }
}

/* --- Admin sidebar (desktop, >=1024px) — SHELL LAYOUT, not a component ------
   Prereq 0.3 / the prompt's GAP: no `.sidebar` component exists in
   components.css: P1-04 styled the tab-bar only. A sidebar is app-shell
   layout (like .page-header), so it is built here, reusing the token
   vocabulary — never a new components.css entry (that would collide with
   P1-05's parallel track and needs its own style-guide prompt). */
.app-sidebar {
  display: none;                      /* mobile/tablet: staff & supervisor use the tab-bar */
}

.app-sidebar__item,
.app-sidebar__group {
  display: block;
  min-height: var(--touch-target-row);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-input);
  color: var(--color-body);
  font-size: var(--font-size-small);
  font-weight: var(--weight-bold);
  text-decoration: none;
  transition: background var(--transition-quick), color var(--transition-quick),
              border-color var(--transition-quick);
}
/* Section label ("Venues") — a quiet divider, not a nav target.
   D116 Step 7 sweep finding: this selector shares `.app-sidebar__item`'s
   border-radius (both rules above), and a plain `border-top` on ONE side of a
   radiused box is exactly the mixed-border corner-hook pattern (critic §B) —
   the top border tapers into nothing at the two rounded top corners instead of
   meeting them cleanly. Fixed the same way as `.composer--announcement`: an
   inset box-shadow line, which follows no corner and does not arc. */
.app-sidebar__group {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  box-shadow: inset 0 var(--border-width) 0 var(--color-line);
  color: var(--color-muted);
  font-size: var(--font-size-caption);
  font-weight: var(--weight-normal);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
a.app-sidebar__item:not(.on):not([aria-disabled]):hover {
  background: var(--color-hover);
  color: var(--color-ink);
}
/* Current item — same family as the tab-bar's seated active state: a quiet
   fill + an ink label (never accent-coloured TEXT — DL08/D108).
   D116 ruling 5: the unified ink-edge selection idiom (was --color-accent —
   a sidebar row is routine, not a NOW moment, ruling 2). D116 Step 7 sweep
   finding: a real `border-left` on ONE side of this radiused item (shares
   --radius-input with `.app-sidebar__group` above) is the same corner-hook
   pattern — swapped for an inset box-shadow line, which clips cleanly to the
   radius instead of tapering. box-shadow takes no box-model space, so the old
   `padding-left: calc(...) - 2px` compensation is no longer needed. */
.app-sidebar__item.on {
  box-shadow: inset 2px 0 0 0 var(--color-ink);
  background: var(--color-hover);
  color: var(--color-ink);
}
/* Outward offset (not inset): the focus ring must never sit on top of `.on`'s
   own inset selection line, so it rings OUTSIDE the row instead. `gap:
   var(--space-1)` between sidebar rows leaves room for it without touching a
   neighbouring item. */
.app-sidebar__item:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}
/* Inert (unbuilt) items — de-emphasised via `--color-muted` + normal weight
   (both already AA-legible on their own — design-system.css), never an
   opacity multiplier stacked on top of a token (R1: that combination fails
   the 3:1 UI floor in both themes). pointer-events:none is what actually
   communicates "does nothing"; a <span> is never in the tab order by default. */
.app-sidebar__item[aria-disabled="true"] {
  color: var(--color-muted);
  font-weight: var(--weight-normal);
  pointer-events: none;
}
/* Settings (owner only) pins to the bottom of the column — the common
   admin-sidebar shape: primary nav at the top, the account-level item
   separated below. Targeted by ITS OWN class, not by DOM position: an
   earlier version used `.app-sidebar__group ~ .app-sidebar__item:last-child`
   ("whatever app-sidebar__item comes after Venues"), which worked today but
   was accidental, not invariant — Codex round 1 correctly flagged that any
   FUTURE sidebar item added after Venues would be silently caught by the
   same rule and mispinned to the bottom, exactly as an earlier draft
   mispinned "Venues" itself for a manager (R1 hierarchy review) before that
   was fixed. A dedicated class has no such trap: only Settings ever carries it. */
.app-sidebar__item--settings {
  margin-top: auto;
}

@media (prefers-reduced-motion: reduce) {
  .app-sidebar__item {
    transition: none;
  }
}

/* --- Page bodies ----------------------------------------------------------
   Shared by the shell page and the three error pages: a heading, a sentence,
   and (on errors) the way back home. */
.page-title {
  margin: 0 0 var(--space-2);
  color: var(--color-ink);
  font-size: var(--font-size-heading);
  font-weight: var(--weight-bold);
}

.page-lead {
  margin: 0 0 var(--space-4);
  max-width: 60ch;
}

.page-note {
  margin: 0;
  color: var(--color-muted);
  font-size: var(--font-size-small);
}

/* --- Error pages ----------------------------------------------------------
   Same shell, same voice as any other page — never a browser default surface
   (P1-02 blueprint, line 310). The status code is shown small and muted: it is
   diagnostic detail, not the message. */
.error-code {
  margin: 0 0 var(--space-1);
  color: var(--color-muted);
  font-size: var(--font-size-small);
  font-weight: var(--weight-bold);
  letter-spacing: 0.08em;
}

.error-actions {
  margin-top: var(--space-4);
}

/* --- Desktop (>= 1024px) --------------------------------------------------
   Admin views are desktop-first surfaces from Phase 1 onward. This is also
   the ONE breakpoint the whole shell uses (Prereq 0.7 / the prompt's Visual
   Spec): the 834-1023px tablet-portrait band stays on the mobile shell. */
@media (min-width: 1024px) {
  .page-header {
    padding: var(--space-3) var(--space-4);
  }

  .page-main {
    padding: var(--space-5) var(--space-4);
  }

  /* The tab-bar is a mobile control — desktop uses the sidebar instead
     (owner/manager) or no nav at all (staff/supervisor: mobile is their
     primary surface, per CLAUDE.md's mobile-first house floor rule).
     Scoped to `.page-nav.tab-bar` — same DL14/A1 reasoning as the
     placement rule above: the bare `.tab-bar` class also matches
     styleguide.html's demo section, which should keep rendering as a
     normal in-flow component demo at desktop widths too, not disappear
     because it happens to reuse the real shell nav's class name. */
  .page-nav.tab-bar {
    display: none;
  }

  /* The sidebar becomes a fixed full-height left column. Width is a layout
     fact (not a colour, not reused elsewhere) — same convention as the
     drawer's `min(360px, 100%)` in components.css. */
  .app-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 40;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    width: 260px;
    padding: var(--space-4) var(--space-3);
    overflow-y: auto;
    background: var(--color-surface);
    border-right: var(--border-width) solid var(--color-line);
  }

  /* Everything else shifts right by the sidebar's width, ONLY when a sidebar
     is actually present (an owner/manager session) — a staff/supervisor
     desktop render has no sidebar and no tab-bar (see above) and keeps the
     plain centred shell untouched. */
  body:has(.app-sidebar) .page-header {
    margin-left: 260px;
  }
  body:has(.app-sidebar) .page-main {
    margin-left: 260px;
    margin-right: 0;
  }
}

/* --- Auth shell -------------------------------------------------------------
   Login / forgot / reset — and, as a consequence of the same rule, any page
   rendered to an anonymous visitor (a 404/403/500 hit with no session). Brand
   only in the header (no nav, no letterbox — base.html renders neither when
   `current_user.is_authenticated` is false, so this class only ever needs to
   narrow the content column). max-width is a layout fact, not a token — same
   convention as the sidebar's 260px above.

   D116 ruling 6 / critic C2: the auth page used to top-align its content,
   leaving a full-height empty void below the fold ("login is dead on
   arrival"). min-height + flex vertical-centring gives the form real
   composition instead of a top-hung fragment; 60vh is a layout fact (a
   viewport fraction, not a colour/spacing token) that reads as "roughly
   centred" without hard-coding the header's own height. Content still flows
   top-to-bottom on a very short viewport (this is centring, not a fixed
   position), so nothing about the mobile-first reading order changes. */
.shell--auth .page-main {
  max-width: min(480px, 100%);
  min-height: 60vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Auth brand mark — the real "FIRE + FLY" brushstroke wordmark, rendered
   large above the page title (critic C2: "no logo/mark"). Charles's ruling
   2026-07-19: the GAP this comment used to carry (favicon.svg — an INVENTED
   flame mark P1-06 hand-drew without consulting the brand corpus) is closed;
   this is the real owner-sourced asset (`Company/Branding/Logo/New
   branding/F&F_Wordmk-Red…`, docs/design-references/fireandfly-brand.md:73),
   an approved standalone lockup (…:66, "wordmark alone" is sanctioned).
   120px matches the brand guidelines' own minimum digital wordmark width
   (…:77) — calc() off --space-5 (40px) keeps the value tokens-derived rather
   than a bare literal; height is intentionally unset so the image's own
   aspect ratio (transparent PNG, no reflow risk once loaded) governs it. */
.auth-mark {
  display: block;
  width: calc(var(--space-5) * 3);
  height: auto;
  margin: 0 auto var(--space-3);
}

/* --- Kiosk shell scaffold ----------------------------------------------------
   PLACEHOLDER ONLY (Prereq/Visual Spec §A) — no kiosk session exists yet (2C
   fills the PIN UI in a later phase). Nothing sets `.shell--kiosk` today; the
   hook is `{% block shell_modifier %}` on <body> in base.html, so a future
   kiosk template can opt in without this file changing. Restricted surface:
   no app nav, no letterbox. */
.shell--kiosk .page-nav,
.shell--kiosk .letterbox {
  display: none;
}
