/* ============================================================================
   Vague Presence OS — os/css/shell.css
   Owner: app-frontend.  docs/ARCHITECTURE.md §14, docs/DECISIONS.md D16.

   Layout and composition ONLY: the layer stack, desktop grid, window
   positioning, sheet mode, responsive rules, safe-area insets.

   MAY NOT declare a raw colour, a font-family, a font-size, a border style or
   a shadow. Only var(--vp-*) tokens, or bare structural values that are not in
   that forbidden set (durations, breakpoints, z-index numbers that are not
   already tokens, opacity). Every Platinum primitive lives in system.css;
   this file only says where things sit and how big the box is.
   ============================================================================ */

/* ==========================================================================
   0. VIEWPORT ROOT
   <body> never scrolls at any width (SH9). 100dvh, never 100vh (MO1).
   ========================================================================== */
html, body {
  block-size: 100%;
  overflow: hidden;
}
body { position: relative; }

.vp-app {
  position: fixed;
  inset: 0;
  block-size: 100dvh;
  padding-block-start: env(safe-area-inset-top);
}
.vp-app[hidden] { display: none; }

/* A generic visually-hidden utility: present for assistive tech and native
   keyboard operation, invisible on screen. Layout-only (position, size,
   clipping) — no colour. */
.vp-visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ==========================================================================
   1. STATIC DOCUMENT (CO2) — the pre-boot / no-JS / below-floor / framed-in-
   itself content. Hidden, not removed, once the desktop boots (D10: Read Me
   clones nodes out of it).
   ========================================================================== */
.vp-staticdoc {
  min-block-size: 100dvh;
  padding: var(--vp-space-20);
  background: var(--vp-surface-chrome);
}
.vp-staticdoc[hidden] { display: none; }

.vp-staticdoc__card {
  max-inline-size: var(--vp-measure-max);
  margin-inline: auto;
  padding: var(--vp-space-20);
  background: var(--vp-surface-content);
  border: var(--vp-space-1) solid var(--vp-line);
}
.vp-staticdoc__card > * + * { margin-block-start: var(--vp-space-16); }
.vp-staticdoc__card h1 {
  font-size: var(--vp-text-size-2x);
  line-height: var(--vp-text-line-2x);
}
.vp-staticdoc__card h2 { margin-block-start: var(--vp-space-20); }
.vp-staticdoc__card h3 { margin-block-start: var(--vp-space-16); }
.vp-staticdoc__faq > * + * { margin-block-start: var(--vp-space-16); }
.vp-staticdoc__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--vp-space-16);
  padding-block-start: var(--vp-space-8);
}
.vp-staticdoc__notice { padding-block-start: var(--vp-space-16); }

@media (width < 520px) {
  .vp-staticdoc { padding: var(--vp-space-12); }
  .vp-staticdoc__card { padding: var(--vp-space-12); }
}

@media (hover: none) {
  .vp-staticdoc__links a {
    display: inline-flex;
    align-items: center;
    min-block-size: var(--vp-touch-target);
  }
  .vp-staticdoc__links { gap: var(--vp-space-8); }
}

/* ==========================================================================
   2. BOOT SCREEN (SH1)
   The field itself is the html/body background already (system.css's reset
   paints --vp-surface-chrome there), so no script is needed for it to be
   correct at first paint. The icon and wordmark start invisible and fade in
   under boot.js's control; the Skip button is present and interactive from
   the very first frame the boot layer exists.
   ========================================================================== */
#vp-boot[hidden] { display: none; }

.vp-boot__icon,
.vp-boot__wordmark {
  opacity: 0;
}
.vp-boot__icon--in,
.vp-boot__wordmark--in {
  opacity: 1;
  transition: opacity 300ms var(--vp-ease);
}
.vp-boot--leaving .vp-boot__icon,
.vp-boot--leaving .vp-boot__wordmark {
  transition: none;
}
/* system.css's own .vp-boot--leaving plays vp-boot-out over
   --vp-dur-boot-short (800ms); boot.md §3.1/§3.2 both call for a 150ms
   fade at the very end of the sequence, which is not a token this build
   owns a name for. Duration is on shell.css's allowed list (durations are
   not colour/font/size/border/shadow, docs/ARCHITECTURE.md §14), so this
   is a longhand override of one part of that shorthand, not a new
   declaration of the animation itself. Filed to
   docs/requests/design-lead.md as a request for a token when one exists. */
.vp-boot--leaving {
  animation-duration: 150ms;
}
#vp-boot.vp-boot--still .vp-boot__icon,
#vp-boot.vp-boot--still .vp-boot__wordmark {
  opacity: 1;
  transition: none;
}

.vp-boot__skip {
  position: absolute;
  inset-block-end: calc(var(--vp-space-12) + env(safe-area-inset-bottom));
  inset-inline-end: calc(var(--vp-space-12) + env(safe-area-inset-right));
}

/* ==========================================================================
   3. THE LAYER STACK
   Every layer is a direct, non-stacking-context-competing sibling so a
   window's own 100-8999 z-index compares directly against the dock (9000),
   menus (9500), modals (9800), menu bar (9900) and the drag layer (9999).
   ========================================================================== */
.vp-layer-desktop {
  position: fixed;
  inset: var(--vp-menubar-height) 0 0 0;
  overflow: hidden;
  outline: none;
}

.vp-layer-windows {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.vp-layer-windows > * { pointer-events: auto; }

/* Reserved for API symmetry (ui/shell.js::layerElement('hosts')). Every host
   iframe lives inside its own .vp-window__host for the life of the window
   (docs/API.md §7.3 R1) — never reparented into a shared layer — so this
   stays empty by design. */
.vp-layer-hosts { display: none; }

.vp-layer-menus,
.vp-layer-modal {
  position: fixed;
  inset: 0;
  pointer-events: none;
}
.vp-layer-menus > *,
.vp-layer-modal > * { pointer-events: auto; }

.vp-layer-flash {
  position: fixed;
  inset: 0;
  z-index: var(--vp-z-drag-outline);
  pointer-events: none;
}

/* ==========================================================================
   4. DESKTOP (SH2, FI2)
   Icon cells are free-positioned in desktop mode (drag to anywhere) and flow
   left-to-right in sheet mode (not draggable) — see design/desktop.md §7.
   ========================================================================== */
.vp-desktop-icons {
  position: absolute;
  inset: 0;
}
.vp-desktop-icon {
  position: absolute;
  touch-action: none;
}

@media (width < 1024px) {
  .vp-desktop-icons {
    position: static;
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: var(--vp-space-8);
    padding-block: var(--vp-desktop-inset);
    padding-inline-start: calc(var(--vp-desktop-inset) + env(safe-area-inset-left));
    padding-inline-end: calc(var(--vp-desktop-inset) + env(safe-area-inset-right));
    inline-size: 100%;
    min-block-size: 100%;
  }
  /* CR-11 fallout: `position: static` on the cell made its label's
     `display: -webkit-box` / `-webkit-line-clamp` text (system.css §8.14)
     fail to paint in this flex-wrap flow — measurable (a real
     `getBoundingClientRect()`, `color`, `opacity: 1`) but invisible on
     screen. `position: relative` keeps the cell in normal flex flow
     exactly as `static` does — it is the cell's own base rule
     (system.css:1410) before this media query overrides it to `absolute`
     for desktop mode — and the label paints again. */
  .vp-desktop-icon { position: relative !important; }
}

/* ==========================================================================
   5. WINDOWS (SH4) AND HOSTING (AH1, AH2)
   The window element's position is a transform (no layout read during a
   drag); width/height are explicit so `contain: strict` (system.css) has
   something to contain. Never touch .vp-window__host once created (D5).
   ========================================================================== */
.vp-window {
  inset-block-start: 0;
  inset-inline-start: 0;
}
.vp-window[hidden] { display: none; }

/* system.css's .vp-window__titlebar and .vp-window__body are normal-flow
   siblings of .vp-window, but .vp-window__body's own block-size:100%
   resolves against .vp-window's content box, not "what's left after the
   titlebar" — measured: with a 640px window the body renders 630px tall
   starting below a 19px titlebar, 19px taller than the space actually
   available, and everything anchored to body's own bottom edge (most
   importantly the grow box) is pushed under .vp-window's overflow:hidden
   and becomes unclickable over most of its area. Subtracting the titlebar
   height here is a size/position fix, squarely shell.css's remit, not a
   colour or font one. */
.vp-window__body { block-size: calc(100% - var(--vp-titlebar-height)); }
@media (width < 1024px) {
  .vp-window__body { block-size: calc(100% - var(--vp-touch-titlebar-height)); }
}

.vp-window__body { position: relative; }
.vp-window__body > .vp-growbox {
  position: absolute;
  inset-block-end: 0;
  inset-inline-end: 0;
}

.vp-window__content { position: relative; overflow: auto; }
.vp-window__content--noscroll { overflow: hidden; }

/* The click-swallowing shield (docs/API.md §7.3 R3, D15): present whenever
   the window is not the front window of the active app, or the interaction
   lock is held. */
.vp-window__shield {
  position: absolute;
  inset: 0;
  z-index: 5;
  cursor: default;
}
.vp-window__shield[hidden] { display: none; }

/* The never-reparented host node (docs/API.md §7.1 R1). Fills the content
   region exactly; width/height in px or %, never vh (R9). */
.vp-window__host { position: absolute; inset: 0; }
.vp-window__host iframe {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  border: 0;
}

.vp-window__busy {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--vp-space-8);
}
.vp-window__busy[hidden] { display: none; }
.vp-window__busy .vp-progress { inline-size: var(--vp-window-min-w); }

.vp-window__error {
  position: absolute;
  inset: 0;
  display: flex;
  padding: var(--vp-space-12);
  gap: var(--vp-space-16);
}
.vp-window__error[hidden] { display: none; }
.vp-window__error-icon { flex: none; }
.vp-window__error-body { flex: 1 1 auto; min-inline-size: 0; display: flex; flex-direction: column; }
.vp-window__error-buttons {
  margin-block-start: auto;
  padding-block-start: var(--vp-space-16);
  display: flex;
  justify-content: flex-end;
  gap: var(--vp-button-gap-h);
}

.vp-kbd-indicator {
  position: absolute;
  inset-inline-end: calc(var(--vp-space-4) + var(--vp-control-box) + var(--vp-space-4) + var(--vp-control-box) + var(--vp-space-6));
  inset-block-start: var(--vp-space-3);
}
.vp-kbd-indicator[hidden] { display: none; }

/* Scroll bars scroller.js attaches float inside whatever it is given, always
   leaving the grow-box corner clear. */
.vp-scroll-region { position: relative; inline-size: 100%; block-size: 100%; }
.vp-scroll-region__content {
  position: absolute;
  inset: 0;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}
.vp-scroll-region__content--v { inset-inline-end: var(--vp-scrollbar); }
.vp-scroll-region__content--h { inset-block-end: var(--vp-scrollbar); }
.vp-scrollbar--v.vp-scroll-region__bar {
  position: absolute;
  inset-block: 0 var(--vp-scrollbar);
  inset-inline-end: 0;
}
.vp-scrollbar--h.vp-scroll-region__bar {
  position: absolute;
  inset-inline: 0 var(--vp-scrollbar);
  inset-block-end: 0;
}

@media (width < 1024px) {
  .vp-scroll-region__content--v,
  .vp-scroll-region__content--h { inset: 0; }
}

/* ==========================================================================
   6. MENUS (SH3) AND MODALS (SH6)
   .vp-menu / .vp-modal / .vp-alert are absolute or fixed already
   (system.css); this just gives them a viewport-anchored containing block
   with no stacking-context surprises.
   ========================================================================== */
.vp-menubar__title,
.vp-menubar__app { cursor: default; }

/* CR-10: system.css's `.vp-menubar__title { display: flex; ... }` is an
   author rule, so it beats the UA stylesheet's `[hidden] { display: none }`
   regardless of specificity — the browser's own hiding mechanism never
   actually hid a collapsed title. ui/menubar.js::layoutOverflow() set
   `.hidden = true` on titles it meant to collapse into the overflow menu;
   they kept their flex box and kept taking up room in the bar, which is
   what pushed the Application menu past the right edge of the viewport at
   phone width. `[hidden]` here is one step more specific than the base
   rule, so it wins without needing `!important`. */
.vp-menubar__title[hidden] { display: none; }

/* M-1 (round-2 fix): a 16px icon rail before a command item's label,
   emitted by ui/menu.js only on a menu that carries at least one iconId
   (`.vp-menu--icons`) and blank on the items in that menu that carry none —
   the fixed column is what keeps every label starting at the same x
   regardless (menu-bar.md §5, the "ragged label column" the round-2 blind
   comparison named). A menu with no iconId anywhere never gets the class
   and never gets the column, so this is a no-op until items carry it.
   Sized from the same tokens system.css already uses for `.vp-menu__mark`
   and `.vp-icon--sm`, not a literal. */
.vp-menu--icons .vp-menu__icon {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--vp-icon-sm);
  block-size: var(--vp-menu-item-height);
  margin-inline-end: var(--vp-space-4);
}

/* ui/alerts.js builds its sheet-mode Cancel bar (.vp-sheet__titlebar,
   alerts.md §8.1) unconditionally, once, rather than only below the
   breakpoint — the same movable-modal construction ui/apps/account.js is
   expected to reuse. Desktop mode has no title bar on an alert at all
   (alerts.md §2: "No title bar"), so this is the visibility half of that
   responsive rule; the geometry half is system.css's own
   `@media (width < 1024px)` block. */
.vp-alert > .vp-sheet__titlebar,
.vp-modal > .vp-sheet__titlebar { display: none; }
@media (width < 1024px) {
  .vp-alert > .vp-sheet__titlebar,
  .vp-modal > .vp-sheet__titlebar { display: flex; }
}

/* ==========================================================================
   7. DOCK (SH5) — the primitive (.vp-dock) is already position:fixed and
   self-positioning. dock.md §5.2: a tab's hit area extends UPWARD only
   (downward is off-screen, flush with the viewport bottom) — the generic
   .vp-hit in system.css centers on the control instead, which is right for
   every other control that uses it but not this one, so this narrows it
   for a dock tab specifically. Token-only (--vp-touch-target); no colour,
   font or border, so this stays inside shell.css's remit.
   ========================================================================== */
.vp-dock-tab { position: relative; }
@media (hover: none) {
  .vp-dock-tab .vp-hit {
    inset-block-end: 0;
    inset-block-start: auto;
    inset-inline: 0;
    inline-size: auto;
    block-size: var(--vp-touch-target);
    translate: none;
  }
}

/* ==========================================================================
   8. DRAG OUTLINE AND RUBBER BAND (SH2, SH4)
   Appended directly into the windows/desktop layers, which are already
   viewport-anchored.
   ========================================================================== */
.vp-marchers,
.vp-rubberband { inset-block-start: 0; inset-inline-start: 0; }

/* ==========================================================================
   9. SHEET MODE (MO1) — layout-flow differences beyond what system.css's own
   breakpoint block already restyles.
   ========================================================================== */
@media (width < 1024px) {
  .vp-layer-windows { overflow: visible; }
  .vp-window__body > .vp-growbox { display: none; }
  /* ui/sheet.js prepends its own Done/Cancel bar (.vp-sheet__titlebar) —
     the sheet's real title bar. window-chrome.js's original
     .vp-window__titlebar is still in the DOM underneath it (D5: nothing
     about a window's own chrome nodes is rebuilt on a mode switch), and
     without this it renders a second, widget-less title bar stacked
     directly above the real one. Absent, not just visually collapsed, so
     it never receives a stray tap. */
  .vp-window__titlebar { display: none; }
}

/* ==========================================================================
   10. REDUCED MOTION — layer-level durations this file introduces.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .vp-boot__icon--in,
  .vp-boot__wordmark--in { transition: none; }
}
[data-vp-motion="never"] .vp-boot__icon--in,
[data-vp-motion="never"] .vp-boot__wordmark--in { transition: none; }

/* Cloudflare Turnstile's own iframe sits in this slot. It is hidden until the
   widget renders, so a configured-but-not-yet-loaded key leaves no empty gap. */
.vp-turnstile {
  margin-block: var(--vp-space-8);
  min-block-size: 0;
}
.vp-turnstile iframe { display: block; max-inline-size: 100%; }
