/* ==========================================================================
   EVAL · engine.css — scrim, curtain + band + pull tab, pop-up, nav pills,
   theme crossfade. Owner: engine agent. Curtain agents style only the inside
   of their body; the chrome below is drawn by the engine, every time.
   ========================================================================== */

/* ---------- scrim (always solid; 12px blur, and see the pop-up section) -- */
.scrim {
  position: fixed; inset: 0; z-index: 60;
  background: var(--scrim);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-curtainOut) var(--ease);
}
.scrim.on { opacity: 1; pointer-events: auto; }
/* a pop-up may open over a curtain, so it carries its own scrim above it — and
   over a curtain it blurs less than 12px, for the reason given down in the pop-up
   section: the surface underneath is one window, not the whole page */
.scrim.pop { z-index: 75; transition-duration: var(--dur-popOut); }

/* ---------- curtain ----------------------------------------------------- */
.curtain {
  position: fixed; z-index: 70; display: none;
  flex-direction: column; overflow: hidden;
  background: var(--card); border: 1px solid var(--line3);
  box-shadow: var(--shadowCurtain);
  will-change: transform;
  transition: transform var(--dur-curtain) var(--ease-rise);
}
.curtain.mounted { display: flex; }
.curtain.dragging { transition: none; }
.curtain.closing { transition: transform var(--dur-curtainOut) var(--ease); }

/* Geometry is fixed per frame, not per curtain: INTERACTION-LAWS §1a holds the one
   table (iPad panel full height inset 20px · iPad sheet 86vh · phone sheet 92vh) and
   tests/suites/03-curtain-geometry.cjs asserts these three numbers. Do not "correct"
   one of them here on its own; the law, COMPONENTS.md §4 and the suite move together. */

/* iPad: a right-hand panel (side:'right'), the composition Quinn picked in v3-panel */
@media (min-width: 1000px) {
  .curtain[data-side="right"] {
    top: 20px; right: 20px; bottom: 20px; width: var(--curtain-w);
    border-radius: var(--r-xl); transform: translateX(calc(100% + 40px));
  }
  .curtain[data-side="right"].on { transform: translateX(0); }
  .curtain[data-side="sheet"] {
    left: 50%; bottom: 0; width: min(860px, 92vw); height: 86vh;
    border-radius: var(--r-xl) var(--r-xl) 0 0; border-bottom: 0;
    transform: translate(-50%, 100%);
  }
  .curtain[data-side="sheet"].on { transform: translate(-50%, 0); }
}

/* Phone: everything is a bottom sheet at 92% */
@media (max-width: 999px) {
  .curtain {
    left: 0; right: 0; bottom: 0; height: 92vh;
    border-radius: var(--r-xl) var(--r-xl) 0 0; border-bottom: 0;
    transform: translateY(100%);
  }
  .curtain.on { transform: translateY(0); }
}

/* ---------- the band (40px, purple, one word, the pull tab) ------------- */
.band {
  position: relative; flex: 0 0 auto;
  height: var(--band-h); padding: 0 16px;
  display: flex; align-items: center; gap: 10px;
  background: var(--gradR);
  touch-action: none; cursor: grab; user-select: none; -webkit-user-select: none;
}
.band.grabbing { cursor: grabbing; }
.band .band-title {
  font-family: var(--font-head); font-size: 15px; font-weight: 800;
  letter-spacing: -.2px; color: var(--onGrad);
}
/* the tab reads as the 40px band but IS a 56px control: the box-sizing border box
   keeps the 40px content row aligned with the band and hangs the extra 16px of hit
   area over the top of the body, so the geometry the battery measures is the real
   geometry (no data-hit crutch). z-index puts that overhang above the body. */
.pulltab {
  position: absolute; left: 50%; top: 0; transform: translateX(-50%); z-index: 2;
  width: 72px; height: calc(var(--band-h) + 16px); padding-bottom: 16px;
  display: flex; align-items: center; justify-content: center;
  touch-action: none; cursor: grab;
}
.pulltab > i {
  display: block; width: 48px; height: 5px; border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .70);
}

/* ---------- curtain body ------------------------------------------------ */
.curtain-body {
  flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 18px 22px 24px;
  overscroll-behavior: contain;
}
@media (max-width: 999px) {
  .curtain-body { padding: 16px var(--gutter) calc(var(--sab) + 24px); }
}

/* A window that asked for the slack (def.fill). The body is one column and the
   block the window marked .grow absorbs whatever the content leaves over, so the
   last block sits on the floor of a fixed-height panel instead of floating a
   hundred pixels above it. The curtain's own height never changes — only where
   the space inside it goes. */
.curtain-body.fill { display: flex; flex-direction: column; }
.curtain-body.fill > .grow { flex: 1 1 auto; min-height: 0; }

/* ---------- pop-up ------------------------------------------------------ */
/* A pop-up belongs to the curtain that opened it (COMPONENTS.md §3) and has to
   look like it. Centred on the VIEWPORT it did not: on the iPad the owner is a
   672px panel inset 20px from the right, so a 420px card at 50% of 1180 landed
   two thirds of the way over the scrimmed Home page with only its right edge
   touching its parent — a detached system dialog, not this curtain's question.
   It is centred on the panel instead (below). The two sheets need no rule: a
   46vh card centred on the viewport already sits inside a sheet that runs from
   14vh (iPad) / 8vh (phone) to the bottom edge, and both are centred on x. */
.popup {
  position: fixed; z-index: 80; left: 50%; top: 50%;
  width: min(420px, 88vw); max-height: 46vh;
  display: none; flex-direction: column; overflow: hidden;
  background: var(--card); border: 1px solid var(--line3);
  border-radius: 22px; box-shadow: var(--shadowCurtain);
  transform: translate(-50%, -50%) scale(.96); opacity: 0;
  transition: opacity var(--dur-pop) var(--ease), transform var(--dur-pop) var(--ease);
}
.popup.mounted { display: flex; }
.popup.on { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.popup.closing { transition-duration: var(--dur-popOut); }
.popup-head { flex: 0 0 auto; padding: 18px 20px 0; display: flex; align-items: center; gap: 10px; }
.popup-head h2 {
  font-family: var(--font-head); font-size: 19px; font-weight: 800;
  letter-spacing: var(--ls-head); color: var(--ink);
}
.popup-body { flex: 1; min-height: 0; overflow-y: auto; padding: 12px 20px 2px; }
.popup-body p { font-size: var(--fs-body); line-height: var(--lh-body); color: var(--ink2); }
.popup-actions { flex: 0 0 auto; display: flex; gap: 10px; padding: 16px 20px 18px; }
.popup-actions .btn { flex: 1; }

/* the pop-up rides its owner: the same 20px inset and --curtain-w the panel rule
   further up is built from (03-curtain-geometry.cjs reads that rule's text, so the
   inset stays a literal there and here). 92-popup-owner.cjs measures the pop-up
   centred inside the open panel, so the two numbers move together or go red. */
@media (min-width: 1000px) {
  body:has(> .curtain.on[data-side="right"]) .popup {
    left: calc(100% - 20px - var(--curtain-w) / 2);
  }
}
/* and with the card sitting ON its owner, that owner has to stay readable through
   the pop-up's own scrim: 12px of blur turned the pace rings behind it into blobs,
   in every frame. Over an open curtain the scrim keeps its full solid fill (law §1
   — never see-through, never half-drawn) and softens the blur, because what is
   underneath is one window the GM is still reading, not a whole page being pushed
   away. With no curtain under it, the page-level pop-up keeps the full 12px. */
body:has(> .curtain.on) .scrim.pop {
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
}

/* ---------- nav: iPad pills in the top bar ------------------------------ */
.navpills {
  display: flex; gap: 2px; padding: 3px; border-radius: var(--r-pill);
  background: var(--surf2); border: 1px solid var(--line2);
}
/* Law §2: 44px hit box. The chip reads at 40px, but the control IS 44px — the
   3px of padding belongs to .navpills, not to any one button, so a tap 2px above
   a 40px chip used to land on the container and do nothing. The button owns its
   own reach instead: a 44px border box (2px transparent top/bottom borders) is
   pulled back by an equal negative margin, so the row still lays out at 40px and
   the top bar stays 48px, and the paint is clipped to the padding box so the chip
   is drawn exactly where it was. Real geometry, no data-hit crutch. */
.navpills button {
  height: 44px; margin: -2px 0;
  padding: 0 18px;
  border-top: 2px solid transparent; border-bottom: 2px solid transparent;
  background-clip: padding-box;
  border-radius: var(--r-pill);
  font-size: var(--fs-label); font-weight: 700; color: var(--muted);
  /* transform first: a rule that sets its own transition list replaces the
     family's, and without it EVAL.motion.lift's 2px snaps (laws §3) */
  transition: transform var(--dur-liftUp) var(--ease),
              color var(--dur-page) var(--ease), background-color var(--dur-page) var(--ease);
}
/* background-COLOR, not the background shorthand: the shorthand would reset
   background-clip back to border-box and the chip would paint over its own 2px
   of hit-box border, bulging out of the track. */
.navpills button.on { background-color: var(--card); color: var(--ink); box-shadow: 0 1px 3px rgba(21, 18, 31, .12); }
/* in dark the drop shadow is invisible, so the active chip earns an edge instead */
html[data-theme="dark"] .navpills button.on { box-shadow: inset 0 0 0 1px var(--line3); }

/* ---------- nav: phone floating glass pill ------------------------------ */
/* declared inside the phone frame so it can never leak onto the iPad */
@media (min-width: 1000px) { .navpill { display: none !important; } }

.navpill {
  position: fixed; left: 50%; bottom: calc(var(--sab) + 16px); z-index: 50;
  transform: translateX(-50%);
  display: flex; gap: 4px; padding: 6px; border-radius: var(--r-pill);
  background: var(--glass); border: 1px solid var(--glassLine);
  -webkit-backdrop-filter: blur(18px) saturate(1.5);
  backdrop-filter: blur(18px) saturate(1.5);
  box-shadow: var(--shadowUp);
}
.navpill button {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  height: 46px; min-width: 66px; padding: 0 10px; border-radius: var(--r-pill);
  font-size: var(--fs-pill); font-weight: 700; color: var(--muted);
  transition: transform var(--dur-liftUp) var(--ease), color var(--dur-page) var(--ease);
}
.navpill button svg { opacity: .85; }
/* the active item's lift is the family's one purple glow, `--glow` — the same
   shadow the assistant's own question bubble carries and the same one
   components.css gives `.btn-primary`. It used to be a fourth hand-rolled copy
   (`0 8px 18px -8px rgba(107,78,230,.85)`), close enough to look the same and
   far enough to drift the next time one of them was tuned. One token. */
.navpill button.on {
  background: var(--grad); color: var(--onGrad);
  box-shadow: var(--glow);
}
.navpill button.on svg { opacity: 1; }

/* ---------- theme crossfade (240ms, nothing moves) ---------------------- */
html.theming, html.theming * {
  transition:
    background-color var(--dur-theme) linear,
    border-color var(--dur-theme) linear,
    color var(--dur-theme) linear,
    box-shadow var(--dur-theme) linear !important;
}
/* The law says reduced motion turns every one of these into an instant switch,
   and base.css says so for the whole document — but it says it through `*`,
   which this class selector outranks even though both carry !important. So the
   crossfade switches ITSELF off, at its own specificity and after itself in
   source order. Asserted by tests/suites/93-motion.cjs (themeDistinct <= 3). */
@media (prefers-reduced-motion: reduce) {
  html.theming, html.theming * {
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
  }
}

/* ---------- entrance (EVAL.motion.stagger) ------------------------------ */
.g-enter { animation: gridEnter var(--dur-enter) var(--ease) both; }

/* ---------- placeholder home (replaced by home.js) ---------------------- */
.boot-note { display: flex; flex-direction: column; gap: 14px; max-width: 520px; margin: 0 auto; }
@media (min-width: 1000px) { .boot-note { padding-top: 12vh; } }
@media (max-width: 999px) { .boot-note { padding-top: 24px; } }
