/* peekagents landing page — dark, hand-authored, no build step.
   British spelling throughout; comments explain why a rule exists, not what it does.
   Colour comes from tokens.css (Joystick Amber) — see site/colors.md for the rules.
   The canvas backdrop is the one literal colour left; sim.js reads it from layout.json. */

:root {
  --radius: 10px;
  --font-head: 'Bricolage Grotesque', 'Segoe UI', system-ui, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
}

* {
  box-sizing: border-box;
}

html {
  color-scheme: dark;
}

body {
  margin: 0;
  background: var(--background-page);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  font-family: var(--font-head);
  color: var(--text-primary);
  margin: 0 0 0.5em;
}

a {
  color: var(--text-link);
}

a:focus-visible,
.keycap:focus-visible,
.card:focus-visible,
.pane button:focus-visible,
.popover-row button:focus-visible,
.option-card:focus-visible,
.page-banner button:focus-visible,
.btn--explain:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}

.visually-hidden {
  position: absolute;
  left: -9999px;
}

.wrap {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 24px;
}

/* ---- header ---- */

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 64px;
}

/* Brand lockup: three sprite figures standing full-length, no container, then the
   wordmark. Frames are cropped from pixel row 9 down so only the standing figure
   shows, not the empty canopy above its head. */

.brand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
}

.fig {
  position: relative;
  display: inline-block;
  width: 78px;
  height: 44px;
  flex-shrink: 0;
}

.fig i {
  position: absolute;
  top: 0;
  width: 32px;
  height: 44px;
  background-repeat: no-repeat;
  image-rendering: pixelated;
  background-size: 192px 832px;
  background-position: 0 -18px;
}

.fig .claude { left: 0; background-image: url(sim/sprites/claude-body.png); }
.fig .codex  { left: 23px; z-index: 2; background-image: url(sim/sprites/codex-body.png); }
.fig .pi     { left: 46px; background-image: url(sim/sprites/pi-body.png); }

.wordmark {
  font: 800 22px/1 "Bricolage Grotesque", sans-serif;
  letter-spacing: -0.035em;
  text-transform: lowercase;
  color: var(--text-primary);
}

.site-nav {
  display: flex;
  gap: 24px;
  font-size: 0.9rem;
}

.site-nav a {
  color: var(--text-secondary);
  text-decoration: none;
}

.site-nav a:hover {
  color: var(--text-primary);
}

/* ---- hero ---- */
/* Compact on purpose: the room below is the hero's real subject, and it must
   clear the fold. Text earns just enough height to orient, then gets out of
   the way. */

.hero {
  /* padding-top/bottom only: a plain `padding: Y 0 Z` shorthand would zero out
     the left/right padding .wrap sets on this same element, and text would
     run flush to the viewport edge below the ~1168px width where .wrap's
     max-width stops supplying its own margin. */
  padding-top: 62px; /* header to h1: was a cramped 14px, now a full breath */
  padding-bottom: 24px; /* 24px above the app frame, the frame, not the copy, is the hero */
  text-align: center;
}

.hero h1 {
  font-weight: 800;
  font-size: 40px;
  line-height: 1.05;
  margin: 0 auto 20px;
  max-width: 20ch;
}

.hero-subhead {
  font-family: var(--font-mono);
  color: var(--text-secondary);
  font-size: 16px;
  max-width: 56ch;
  margin: 0 auto 16px;
}

/* CTA and its caption share a row: the button never earns a full line of its
   own just to say "coming soon". Wraps to two lines only when the caption
   cannot share the row. Centred as a unit; the caption text inside keeps its
   own left alignment since it reads as a short label, not a headline. */
.hero-cta-row {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  flex-wrap: wrap;
}

/* the CTA is the one primary action on the page — primary-solid is reserved for it */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--primary-solid);
  color: var(--primary-solid-fg);
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 1rem;
  padding: 14px 22px;
  border-radius: var(--radius);
  text-decoration: none;
  border: 1px solid transparent;
}

.btn:hover {
  background: var(--primary-hover);
}

/* Not an action yet, so not the action colour: a coming-soon button in primary
   yellow would spend the page's one CTA on something that does nothing. */
.btn--soon,
.btn--soon:hover {
  background: var(--secondary-base);
  color: var(--secondary-fg);
  border-color: var(--border-default);
  cursor: default;
}

/* The explain-mode toggle: a real secondary button (unlike the disabled-look
   coming-soon pill beside it), same height, family and padding as .btn so
   the two sit level in the CTA row. */
.btn--explain {
  /* The page's one live action while the download is only promised, so it
     takes the one primary fill; Coming soon beside it stays inert grey. When
     the download returns, primary moves back to it and this goes secondary. */
  background: var(--primary-solid);
  color: var(--primary-solid-fg);
  border: 1px solid transparent;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 1rem;
  padding: 14px 22px;
  border-radius: var(--radius);
  cursor: pointer;
}
.btn--explain:hover { background: var(--primary-hover); }


.nav-note {
  color: var(--text-muted);
  font-size: 0.875rem;
}

.hero-caption {
  margin: 0;
  color: var(--text-secondary);
  font-size: 0.75rem;
  max-width: 44ch;
  text-align: center;
}

/* ---- beats ---- */

.beat {
  padding-top: 56px; /* see .hero: a padding shorthand here would zero .wrap's horizontal gutter */
  padding-bottom: 56px;
  border-top: 1px solid var(--border-default);
}

.beat h2 {
  font-weight: 700;
  font-size: 1.5rem;
  margin: 0 0 12px;
}

/* accent purple appears exactly once on the page: a hairline under each h2 */

.beat h2::after {
  content: '';
  display: block;
  width: 32px;
  height: 2px;
  margin: 10px 0 8px;
  background: var(--accent-base);
}

/* ================================================================
   The app frame — strip + room + session pane. A replica of the app's own
   window: no title bar (the strip is the app's own top edge), the room at
   2× on the left, the inspector pane on the right. 576 + 320 = 896, the
   width the brief calls out inside the 1120px wrap.
   ================================================================ */

/* The stage is allowed to run wider than the page's usual 1120px .wrap, so
   the explain panel has somewhere to open up beside the frame without
   squeezing it — hence a full-bleed outer with its own 24px gutter rather
   than nesting inside .wrap. */
.hero-stage-bleed {
  padding: 0 24px;
}

.hero-stage {
  width: min(100%, 1232px);
  margin: 0 auto;
  padding-bottom: 8px;
  display: flex;
  align-items: start;
  justify-content: center;
  gap: 24px;
}

/* ---- explain panel: copy standing beside the frame, no background of its
   own. Collapsed to nothing in room mode; body.is-explaining opens it. The
   width/opacity transition runs on both directions since it is declared
   once, here, rather than split across the two states. ---- */

.explain-panel {
  width: 0;
  flex-shrink: 0;
  opacity: 0;
  overflow: hidden;
  margin-right: -24px;
  transition: width 420ms cubic-bezier(.2, .7, .2, 1), opacity 320ms 120ms;
}

body.is-explaining .explain-panel {
  width: 300px;
  opacity: 1;
  margin-right: 0;
}

.explain-panel-inner {
  width: 300px; /* fixed so text reflows once, not while the panel is opening */
}

.explain-panel h3 {
  font-size: 17px;
  margin-bottom: 8px;
}

.explain-panel-second {
  margin-top: 28px;
}

.explain-panel p {
  margin: 0 0 16px;
  color: var(--text-secondary);
  font-size: 14px;
}

.explain-counts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px 24px;
  margin: 18px 0 0;
}

.for-count {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.for-count-num {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 22px;
  color: var(--text-primary);
}

.for-count-label {
  font-size: 13px;
  color: var(--text-secondary);
}

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

/* The ?explain=1 debug param (see Page.setExplaining) opens the panel and
   overlay before the first paint, with this class briefly suppressing every
   transition below so a screenshot taken right after load never catches a
   half-open state. */
body.no-anim .explain-panel,
body.no-anim .room-explain-dim,
body.no-anim .zone {
  transition: none !important;
}

/* Below 1260px there isn't room for frame-plus-panel side by side even with
   the frame's own 960px stacking break still a way off, so the stage wraps
   and the panel moves above the frame. Its own open/close now reads as a
   height, not a width, change — the standard 0fr/1fr grid-rows trick, since
   height cannot transition to/from an intrinsic value directly. */
@media (max-width: 1259px) {
  .hero-stage {
    flex-wrap: wrap;
  }

  .explain-panel {
    width: 100%;
    max-width: 640px;
    margin-right: 0;
    opacity: 1;
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 420ms cubic-bezier(.2, .7, .2, 1);
  }

  .explain-panel-inner {
    width: auto;
    overflow: hidden;
    min-height: 0;
  }

  /* Restates width/opacity/margin, not just grid-template-rows: the
     unconditional `body.is-explaining .explain-panel` rule above (wide
     layout's 300px open state) matches at any viewport width and carries
     the same specificity, so without repeating them here it would win the
     cascade on source order for every property it sets that isn't
     mentioned in this narrower rule. */
  body.is-explaining .explain-panel {
    width: 100%;
    max-width: 640px;
    margin-right: 0;
    opacity: 1;
    grid-template-rows: 1fr;
  }

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

.app-frame {
  width: 896px;
  max-width: 100%;
  margin: 0 auto;
  background: var(--background-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  overflow: hidden;
}

.strip {
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 0 14px;
  background: var(--background-elevated);
  border-bottom: 1px solid var(--border-default);
}

.strip-left {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.grip {
  color: var(--text-muted);
  font-size: 1rem;
  line-height: 1;
}

.strip-text {
  color: var(--text-secondary);
  font-size: 0.85rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.roster {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

/* ---- avatars: a 16×16 head crop of frame [0,0], shown at 2× inside a ring
   whose colour is the agent's state. Shared by the strip, the popover rows
   and the floors roster so the same face reads the same way everywhere. ---- */

.avatar {
  position: relative;
  display: inline-block;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  overflow: hidden;
  flex-shrink: 0;
  background: var(--background-page);
  border: 2px solid var(--ring, var(--neutral-400));
}

.avatar-img {
  position: absolute;
  left: -2px;
  top: -2px;
  width: 32px;
  height: 32px;
  image-rendering: pixelated;
  background-repeat: no-repeat;
}

.avatar-more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--secondary-base);
  color: var(--text-muted);
  font-size: 0.7rem;
  font-weight: 600;
  flex-shrink: 0;
}

.app-stage {
  display: flex;
  align-items: stretch;
}

.room-wrap {
  position: relative;
  width: 576px;
  height: 576px;
  flex-shrink: 0;
  background: var(--background-page);
  line-height: 0; /* canvas is inline by default and picks up a baseline gap otherwise */
}

#office-canvas {
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  display: block;
  width: 576px;
  height: 576px;
  aspect-ratio: 1 / 1;
}

/* ---- the annotated-floor overlay: a dim layer plus four named zones, drawn
   straight over the hero's own room canvas (no second copy of it any more).
   Both are inert (pointer-events: none) so hover bubbles and clicks on
   bodies keep working underneath, and both stay hidden — opacity and
   visibility, not display, so layout never jumps — until explain mode. ---- */

.room-explain-dim {
  position: absolute;
  inset: 0;
  /* Deep enough that the labels read over the room's brightest floor; the
     annotations are the point of explain mode, the room is context. */
  background: color-mix(in srgb, var(--background-page) 52%, transparent);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  z-index: 2;
  transition: opacity 320ms ease, visibility 0s linear 320ms;
}

body.is-explaining .room-explain-dim {
  opacity: 1;
  visibility: visible;
  transition: opacity 320ms ease, visibility 0s linear 0s;
}

.zone {
  position: absolute;
  border-style: dashed;
  border-width: 1.5px;
  border-radius: 8px;
  pointer-events: none;
  z-index: 3;
  opacity: 0;
  visibility: hidden;
  transform: translateY(6px);
  transition: opacity 260ms ease, transform 260ms ease, visibility 0s linear 260ms;
}

body.is-explaining .zone {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition: opacity 260ms ease, transform 260ms ease, visibility 0s linear 0s;
}

/* Stagger the entrance only — desks first (no delay), then lounge, couch,
   beside you, 70ms apart. Leaving explain mode is not staggered: the base
   .zone rule above (0ms) applies once body.is-explaining is gone. */
body.is-explaining .zone--lounge { transition-delay: 70ms; }
body.is-explaining .zone--couch { transition-delay: 140ms; }
body.is-explaining .zone--beside { transition-delay: 210ms; }

@media (prefers-reduced-motion: reduce) {
  .room-explain-dim,
  .zone {
    transition: none;
  }
}

/* ---- hover bubble: an HTML overlay, positioned in JS from the hovered
   body's room-pixel box, never affecting simulation state. ---- */

.hover-bubble {
  position: absolute;
  /* .room-wrap zeroes line-height for the canvas; the bubble is its child and
     must restore a real one or every row collapses onto the first. */
  line-height: 1.4;
  width: 224px;
  padding: 10px 12px;
  background: var(--background-elevated);
  border: 1px solid var(--border-default);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  font-size: 0.78rem;
  color: var(--text-secondary);
  pointer-events: none;
  z-index: 5;
}

.hover-bubble::after {
  content: '';
  position: absolute;
  left: var(--tail-left, 20px);
  bottom: -6px;
  width: 10px;
  height: 10px;
  background: var(--background-elevated);
  border-right: 1px solid var(--border-default);
  border-bottom: 1px solid var(--border-default);
  transform: translateX(-50%) rotate(45deg);
}

.hover-bubble--below::after {
  bottom: auto;
  top: -6px;
  transform: translateX(-50%) rotate(225deg);
}

.hover-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 4px;
}

.hover-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--dot, var(--neutral-400));
}

.hover-title {
  font-family: var(--font-head);
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hover-state-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 4px;
  flex-wrap: wrap;
}

.hover-meta {
  color: var(--text-muted);
  margin: 0 0 6px;
}

.hover-context-row {
  display: flex;
  justify-content: space-between;
  color: var(--text-muted);
  font-size: 0.72rem;
  margin-bottom: 3px;
}

/* ---- state pill: uppercase, in the state's own soft/soft-fg pair. Working
   and idle are not "status" feedback colours, so they borrow the neutral
   secondary pair instead of inventing a new token. ---- */

.state-pill {
  display: inline-block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 4px;
}

.state-pill--working { background: var(--secondary-base); color: var(--text-secondary); }
.state-pill--idle { background: var(--secondary-base); color: var(--text-muted); }
.state-pill--blocked { background: var(--status-warning-soft); color: var(--status-warning-soft-fg); }
.state-pill--errored { background: var(--status-danger-soft); color: var(--status-danger-soft-fg); }

/* ---- thin meters: context and the hover bubble's context row share this ---- */

.meter {
  height: 4px;
  border-radius: 2px;
  background: var(--border-default);
  overflow: hidden;
}

.meter-fill {
  height: 100%;
  background: var(--status-info-base);
}

.meter-fill--warning {
  background: var(--status-warning-base);
}

/* ================================================================
   Session pane — one subject at a time: pinned, else the front of the
   queue, else whoever moved most recently. Modelled on the app's own
   inspector pane, in the page's dark tokens.
   ================================================================ */

.pane {
  width: 320px;
  flex-shrink: 0;
  border-left: 1px solid var(--border-default);
  background: var(--background-surface);
  display: flex;
  flex-direction: column;
  max-height: 576px;
  overflow-y: auto;
  font-size: 0.82rem;
}

.pane-body {
  padding: 16px 16px 8px;
  flex: 1;
}

.pane-identity {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 14px;
}

.state-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  margin-top: 6px;
  flex-shrink: 0;
  background: var(--dot, var(--neutral-400));
}

.pane-title {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 17px;
  color: var(--text-primary);
  margin: 0 0 2px;
}

.pane-subline {
  color: var(--text-muted);
  font-size: 0.78rem;
}

.pane-subline strong {
  color: var(--text-secondary);
  font-weight: 500;
}

.pane-section {
  margin-bottom: 16px;
}

.pane-row-label {
  color: var(--text-muted);
  font-size: 0.72rem;
  margin-bottom: 3px;
}

.context-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 5px;
}

.context-value {
  color: var(--text-primary);
  font-size: 0.78rem;
}

.kv-label {
  color: var(--text-muted);
  font-size: 0.72rem;
  margin-bottom: 2px;
}

/* ---- metadata grid: the app's own popover-inspector layout (see
   /tmp/popover-inspector-0.png) — a fixed label column so five rows read as
   a table, not five stacked mini-sections, which is what let the old
   .pane-kv layout run the pane past 576px. ---- */

.pane-meta {
  display: grid;
  grid-template-columns: 56px 1fr;
  column-gap: 10px;
  row-gap: 6px;
  align-items: baseline;
  margin-bottom: 16px;
}

.pane-meta-label {
  color: var(--text-muted);
  font-size: 12px;
  text-align: right;
}

.pane-meta-value {
  color: var(--text-primary);
  font-size: 13px;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pane-meta-value--mono {
  font-family: var(--font-mono);
}

/* ---- waiting-on-you / doing / fault blocks ---- */

.pane-block {
  border-top: 1px solid var(--border-default);
  padding-top: 12px;
  margin-bottom: 16px;
}

.pane-block-heading {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0 0 8px;
}

.op-mono {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--text-primary);
  overflow-wrap: anywhere;
  margin: 0 0 6px;
}

.op-mono--destructive {
  color: var(--status-danger-soft-fg);
}

.sub-heading {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 10px 0 6px;
}

.says-text {
  color: var(--text-secondary);
  margin: 0 0 4px;
  overflow-wrap: anywhere;
}

.ran-line {
  font-family: var(--font-mono);
  color: var(--text-secondary);
  margin: 0 0 6px;
  overflow-wrap: anywhere;
}

.waiting-note {
  color: var(--text-muted);
  font-size: 0.78rem;
  margin: 0 0 10px;
}

.warn-note {
  color: var(--status-warning-soft-fg);
  font-size: 0.8rem;
  margin: 0 0 10px;
}

.pane-actions-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.pane-btn {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 13px;
  padding: 6px 12px;
  border-radius: 6px;
  border: 1px solid transparent;
  cursor: pointer;
}

.pane-btn--approve {
  background: var(--status-success-soft);
  border-color: var(--status-success-border);
  color: var(--status-success-soft-fg);
}

.pane-btn--deny {
  background: var(--status-danger-soft);
  border-color: var(--status-danger-border);
  color: var(--status-danger-soft-fg);
}

.pane-btn--answer {
  background: var(--secondary-base);
  border-color: var(--border-default);
  color: var(--text-primary);
}

.option-card {
  border: 1px solid var(--border-default);
  border-radius: 6px;
  padding: 8px 10px;
  margin-bottom: 6px;
  font-size: 0.85rem;
  color: var(--text-primary);
}

.option-num {
  color: var(--status-warning-soft-fg);
  font-weight: 700;
  margin-right: 6px;
}

.fault-detail {
  color: var(--status-danger-soft-fg);
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

/* ---- bottom action band: sticky, always visible, only Close is live ---- */

.pane-actions {
  position: sticky;
  bottom: 0;
  border-top: 1px solid var(--border-default);
  background: var(--background-surface);
  padding: 10px 16px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.pane-actions .keycap {
  font-size: 0.78rem;
  padding: 5px 9px;
}

/* ================================================================
   "Where it lives" — three equal tiles, each a fixed-height visual over an
   h3 and a one-line caption. The visuals are small mock surfaces (a menu
   bar, a floating panel, a Space overlay) rather than screenshots, so they
   stay crisp at any zoom and never go stale against the real app's chrome.
   ================================================================ */

.where-tiles {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px;
}

.where-tile {
  display: flex;
  flex-direction: column;
  background: var(--background-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  overflow: hidden; /* the visual is flush with the tile edge; this clips it to the radius */
}

.where-visual {
  height: 160px;
  flex-shrink: 0;
  background: var(--background-page);
  position: relative;
  overflow: hidden;
}

.where-tile h3 {
  font-size: 1.05rem;
  margin: 16px 20px 6px;
}

.where-tile p {
  margin: 0 20px 20px;
  color: var(--text-secondary);
  font-size: 0.88rem;
}

.where-caption {
  margin: 14px 0 0;
  color: var(--text-muted);
  font-size: 0.85rem;
}

/* ---- tile 1: menu bar mock — a strip plus four small state glyphs ---- */

.where-visual--menubar {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 14px;
}

.menubar-mock {
  height: 28px;
  flex-shrink: 0;
  background: var(--background-elevated);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 0 10px;
}

.menubar-mock canvas {
  display: block;
  image-rendering: pixelated;
}

.menubar-mock-clock {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-secondary);
}

.menubar-mock-glyphs {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 18px;
}

.menubar-mock-glyph-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  font-size: 10px;
  color: var(--text-muted);
}

.menubar-mock-glyph-item canvas {
  image-rendering: pixelated;
  display: block;
}

/* ---- tile 2: floating office — a live crop of the room, floating over a
   dimmed strip of three generic "other windows" mocks (editor / board /
   player) so the panel visibly sits *above* other apps rather than on an
   empty backdrop. ---- */

.appmocks {
  position: absolute;
  inset: 0;
  display: flex;
  opacity: 0.55; /* dimmed so the office panel on top of it reads as the subject */
}

.appmock {
  flex: 1 1 33.333%;
  min-width: 0;
  padding: 10px;
  border-right: 1px solid var(--border-default);
  overflow: hidden;
}

.appmock:last-child {
  border-right: none;
}

/* (a) editor: a narrow bar-sidebar, a gutter and a handful of code lines in
   two token colours — never red/green, those are reserved for status. */

.appmock--editor {
  display: flex;
  gap: 6px;
}

.appmock-editor-sidebar {
  display: flex;
  flex-direction: column;
  gap: 5px;
  flex-shrink: 0;
  padding-top: 2px;
}

.appmock-editor-sidebar span {
  width: 10px;
  height: 4px;
  border-radius: 2px;
  background: var(--neutral-600);
}

.appmock-editor-body {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
  flex: 1;
}

.appmock-editor-line {
  display: flex;
  align-items: center;
  gap: 5px;
}

.appmock-editor-gutter {
  font-family: var(--font-mono);
  font-size: 7px;
  color: var(--neutral-600);
  flex-shrink: 0;
}

.appmock-editor-code {
  display: block;
  height: 4px;
  border-radius: 2px;
  background: var(--neutral-600);
}

.appmock-editor-code--accent {
  background: var(--accent-soft-fg);
  opacity: 0.6;
}

/* (b) kanban: three columns, each a title bar plus a couple of rounded cards */

.appmock--kanban {
  display: flex;
  gap: 6px;
}

.appmock-kanban-col {
  display: flex;
  flex-direction: column;
  gap: 5px;
  flex: 1;
  min-width: 0;
}

.appmock-kanban-title {
  height: 6px;
  border-radius: 3px;
  background: var(--neutral-600);
}

.appmock-kanban-card {
  height: 14px;
  border-radius: 4px;
  background: var(--neutral-800);
}

/* (c) video player: a dark screen with a centred play triangle and a
   part-filled progress bar */

.appmock--video {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
}

.appmock-video-screen {
  flex: 1;
  border-radius: 4px;
  background: var(--neutral-900);
  display: flex;
  align-items: center;
  justify-content: center;
}

.appmock-video-play {
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 7px 0 7px 11px;
  border-color: transparent transparent transparent var(--neutral-400);
}

.appmock-video-progress {
  height: 4px;
  border-radius: 2px;
  background: var(--neutral-800);
  overflow: hidden;
}

.appmock-video-progress span {
  display: block;
  width: 40%;
  height: 100%;
  background: var(--neutral-400);
}

.floating-panel {
  position: absolute;
  right: 34%; /* left of the video mock's own right edge — see below */
  bottom: 12px;
  width: 100px;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  overflow: hidden;
  background: var(--background-elevated);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}

.floating-panel-strip {
  height: 18px;
  display: flex;
  align-items: center;
  padding: 0 6px;
  font-size: 9px;
  color: var(--text-secondary);
  border-bottom: 1px solid var(--border-default);
}

.floating-panel-canvas {
  display: block;
  width: 100px;
  height: 100px;
  image-rendering: pixelated;
  background: var(--background-page);
}

/* ---- tile 3: ⌥ Space — a dimmed editor backdrop with a static queue card,
   standing in for the app's full-screen overlay ---- */

.where-visual--space {
  display: flex;
  align-items: center;
  justify-content: center;
}

.space-mock-backdrop {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 10px;
  padding: 0 24px;
}

.space-mock-backdrop span {
  height: 6px;
  border-radius: 3px;
  background: var(--neutral-800);
}

.space-mock-backdrop span:nth-child(1) { width: 70%; }
.space-mock-backdrop span:nth-child(2) { width: 45%; }
.space-mock-backdrop span:nth-child(3) { width: 85%; }
.space-mock-backdrop span:nth-child(4) { width: 55%; }
.space-mock-backdrop span:nth-child(5) { width: 65%; }
.space-mock-backdrop span:nth-child(6) { width: 40%; }

.space-mock-card {
  position: relative;
  z-index: 1;
  width: 200px;
  background: var(--background-elevated);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  padding: 12px 14px;
}

/* Scoped to .space-mock-card so these beat the generic `.where-tile p`
   margin rule above — a bare single-class selector loses that specificity
   fight and the card's text ends up shoved sideways by a stray 20px margin. */
.space-mock-card .space-mock-row {
  margin: 0 0 6px;
  font-size: 11px;
  color: var(--text-muted);
}

.space-mock-card .space-mock-op {
  margin: 0 0 8px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text-primary);
}

.space-mock-card .space-mock-keys {
  margin: 0;
  display: flex;
  gap: 12px;
  font-size: 11px;
}

/* reused chip colours — shared with the queue-answer copy above */
.chip-approve { color: var(--status-success-soft-fg); font-weight: 600; }
.chip-deny { color: var(--status-danger-soft-fg); font-weight: 600; }

/* ---- repo chips: one pill per repo among the live agents, each carrying
   the heads actually sitting there and a count — built from state, not a
   fixed list, so it can never drift from what the room shows. Reused inside
   the explain panel; see .explain-panel above. ---- */

.repo-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 20px 0 0;
}

.repo-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--secondary-base);
  border: 1px solid var(--border-default);
  border-radius: 999px;
  padding: 5px 10px 5px 12px;
}

.repo-chip-name {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 0.82rem;
  color: var(--text-primary);
}

.repo-chip-head {
  display: inline-block;
  width: 16px;
  height: 12px;
  background-repeat: no-repeat;
  image-rendering: pixelated;
}

.repo-chip-count {
  font-size: 0.78rem;
  color: var(--text-secondary);
}

/* ---- the annotated floor's zone labels — colours per zone follow the
   room's own state palette (stateColorVar in sim.js): neutral for
   desks/lounge, warning for beside-you, danger for the couch. The .zone box
   itself (position, visibility, the explain-mode transition) lives with the
   room-wrap overlay rules above, since it now draws over the hero's own
   canvas rather than a second copy of it. ---- */

.zone-label {
  position: absolute;
  left: 0;
  top: 0;
  transform: translateY(-100%);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 4px 8px;
  border-radius: 5px 5px 0 0;
  white-space: nowrap;
}

.zone-label b {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 13px;
  text-transform: none;
  letter-spacing: 0;
  margin-left: 6px;
}

/* A label that would cross the frame's top edge (narrow viewports shrink the
   frame in pixels while the label's font stays fixed-size) sits inside the
   box's own top-left corner instead of floating above it — see
   updateZoneLabelOverflow in sim.js. */
.zone-label--inside {
  transform: none;
  border-radius: 0 0 4px 0;
}

.zone--desks { border-color: var(--neutral-400); }
.zone--desks .zone-label { background: var(--neutral-800); color: var(--text-primary); }

.zone--lounge { border-color: var(--neutral-600); }
.zone--lounge .zone-label { background: var(--secondary-base); color: var(--text-secondary); }

.zone--couch { border-color: var(--status-danger-base); }
.zone--couch .zone-label { background: var(--status-danger-soft); color: var(--status-danger-soft-fg); }

.zone--beside { border-color: var(--status-warning-base); }
.zone--beside .zone-label { background: var(--status-warning-soft); color: var(--status-warning-soft-fg); }

/* reused keycap chip, shared with the pane's inert action band */

.keycap {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border-default);
  border-radius: 6px;
  padding: 6px 10px;
  font-family: var(--font-mono);
  font-weight: 700;
  color: var(--text-primary);
  cursor: pointer;
}

.keycap span {
  font-weight: 400;
  color: var(--text-muted);
  font-size: 0.875rem; /* keycap hint label; never below 14px for text-muted */
}

.tag {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 999px;
}

.tag--destructive {
  background: var(--status-danger-soft);
  color: var(--status-danger-soft-fg);
  border: 1px solid var(--status-danger-border);
}

/* ---- the three (beat): a desk row cropped live from the room's own atlas,
   not a marketing idiom — see buildDeskLayers/drawDeskVignette in sim.js. ---- */

.deskrow {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
  align-items: start;
}

.desk {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Background comes from sim.js (assets.layout.backdrop, the room's own
   literal black) rather than a token, so the vignette never shows a seam
   against the composed canvas it holds. */
.desk .vignette {
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  overflow: hidden;
  line-height: 0; /* canvas is inline by default and picks up a baseline gap otherwise */
}

.desk .vignette canvas {
  display: block;
  width: 100%;
  height: auto;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

.plate {
  background: var(--background-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  padding: 14px 16px;
}

.plate h3 {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: 17px;
}

.plate .via {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 2px 0 8px;
}

.plate p {
  margin: 0;
  font-size: 14px;
  color: var(--text-secondary);
}

/* The fourth desk names what's not built yet — dashed, not solid, and the
   body-free crop half-faded, so the row reads as "three plus a slot" rather
   than promising a fourth harness today. */
.desk--next .vignette {
  border-style: dashed;
  border-color: var(--border-strong);
}

.desk--next .vignette canvas {
  opacity: 0.5;
}

.desk--next .plate {
  border-style: dashed;
  border-color: var(--border-strong);
  background: transparent;
}

.tag--primary {
  background: var(--primary-soft);
  color: var(--primary-soft-fg);
}

/* ================================================================
   Page banner — a macOS-style notification fixed to the viewport. Always
   present in the DOM (aria-live needs a stable node to announce into);
   visibility is a slide + fade driven by a class, not `hidden`, and the
   transition is dropped entirely under reduced motion. See
   Page.renderBanner / Page.computeBannerRequest in sim/sim.js.
   ================================================================ */

.page-banner {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 30;
  width: 340px;
  max-width: calc(100% - 32px);
  display: flex;
  gap: 10px;
  background: var(--background-elevated);
  border: 1px solid var(--border-default);
  border-radius: 14px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
  padding: 12px 14px;
  transform: translateX(120%);
  opacity: 0;
  pointer-events: none;
  transition: transform 200ms ease, opacity 200ms ease;
}

.page-banner.is-visible {
  transform: translateX(0);
  opacity: 1;
  pointer-events: auto;
}

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

.page-banner-icon {
  width: 40px;
  height: 40px;
  border-radius: 9px;
  flex-shrink: 0;
}

.page-banner-body {
  min-width: 0;
  flex: 1;
  padding-right: 16px; /* clears the dismiss button, which sits above this column */
}

.page-banner-top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
}

.page-banner-title {
  font-family: var(--font-head);
  font-weight: 600;
  font-size: 13px;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.page-banner-now {
  font-size: 11px;
  color: var(--text-muted);
  flex-shrink: 0;
}

.page-banner-text {
  margin: 4px 0 10px;
  font-size: 13px;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}

.page-banner-actions {
  display: flex;
  gap: 8px;
}

.page-banner-btn {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 13px;
  padding: 6px 12px;
  border-radius: 6px;
  border: 1px solid transparent;
  cursor: pointer;
}

.page-banner-btn--approve {
  background: var(--status-success-soft);
  border-color: var(--status-success-border);
  color: var(--status-success-soft-fg);
}

.page-banner-btn--deny {
  background: var(--status-danger-soft);
  border-color: var(--status-danger-border);
  color: var(--status-danger-soft-fg);
}

.page-banner-btn--answer {
  background: var(--secondary-base);
  border-color: var(--border-default);
  color: var(--text-primary);
}

.page-banner-dismiss {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 20px;
  height: 20px;
  border: none;
  border-radius: 4px;
  background: transparent;
  color: var(--text-muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}

.page-banner-dismiss:hover {
  color: var(--text-primary);
}

/* ---- footer ---- */

.site-footer {
  padding-top: 32px; /* see .hero: a padding shorthand here would zero .wrap's horizontal gutter */
  padding-bottom: 56px;
  border-top: 1px solid var(--border-default);
  color: var(--text-muted);
  font-size: 0.875rem; /* never below 14px for text-muted */
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
}

.site-footer a {
  color: var(--text-muted);
}

.site-footer a:hover {
  color: var(--text-primary);
}

/* ---- mobile / narrow frame ---- */
/* Below 960px the pane stacks under the room instead of beside it, and the
   room gives up its fixed 576px for a fluid, integer-scaling-optional fit
   (see fitCanvas in sim.js — non-integer multiples only kick in under
   640px, where the room is sharing a phone-width viewport). */

@media (max-width: 960px) {
  .app-stage {
    flex-direction: column;
  }

  .room-wrap {
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
  }

  #office-canvas {
    width: 100%;
    height: 100%;
  }

  .pane {
    width: 100%;
    max-height: 420px;
    border-left: none;
    border-top: 1px solid var(--border-default);
  }

  .where-tiles {
    grid-template-columns: 1fr;
  }

  .deskrow {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 640px) {
  .wrap {
    padding: 0 16px;
  }

  .hero {
    padding-top: 28px;
    padding-bottom: 20px;
  }

  .hero h1 {
    font-size: 1.6rem;
    max-width: none;
  }

  .beat {
    padding-top: 36px;
    padding-bottom: 36px;
  }

  .deskrow {
    grid-template-columns: 1fr;
  }

  body {
    font-size: 14px;
  }
}
