/* fx-shell.css: Shell surface motion (sign-in, topbar, sidebar, toasts, billing, launch strip, profile). Owner: C. Loads after fx.css; override base rules on specificity, never !important. */
/* ========================================================================
   What lives here (DESIGN.md sections 2-5, owner C):
   1. sign-in gate: card arrival, per-view row cascade, input focus glow,
      busy sweep on a submitting button, the gate's exit
   2. topbar: backend badge and contest chip (no dots), theme toggle turn +
      circular theme reveal, identity skeletons, the "This session" pill
   3. sidebar: the sliding active pill (FX.tabs on .side), glyph pop
   4. app entrance after the gate (.app.fx-enter, set by auth.js)
   5. launch-readiness strip: placeholder, storefront ring, entrance
   6. billing rows (both Stripe blocks): skeleton row, form rise, saved row
   7. #beToast (wiring.js toast()): icon, drain bar, spring in, ease out
   8. update toast exit + progress sweep, force-update gate entrance
   9. Company Profile
   Then the phone layer and the reduced-motion block.

   House rules: only transform / opacity animate (plus one-shot stroke
   draws); nothing is infinite and no pill carries a dot; every
   sweep is capped (8 passes); colours come from tokens, or are the fixed
   chrome colours the topbar uses in both themes. Keyframes are fx-prefixed (fxShell*).
   ======================================================================== */

/* ---------- 1. sign-in gate ---------- */
/* The gate is the console with nobody signed in (index.html #signin). Its arrivals are one-time: the bar's
   items fade in left to right (as the app bar's do after sign-in), the intro and the product cards rise
   60ms apart, and the card springs in just behind them. The hairline grid and the glows are static:
   nothing on the gate idles. */
#signin .si-top > *{animation:fxFade .5s var(--fx-soft) calc(80ms + var(--si-e,0) * 45ms) backwards}
#signin .si-top > :nth-child(2){--si-e:1}
#signin .si-top > :nth-child(n+3){--si-e:3}
#signin .si-intro > *,#signin .si-prods > *,#signin .si-foot{animation:fxRise .5s var(--fx-ease) calc(140ms + var(--si-r,0) * 60ms) backwards}
#signin .si-intro > :nth-child(2){--si-r:1}
#signin .si-intro > :nth-child(3){--si-r:2}
#signin .si-prods > :nth-child(1){--si-r:3}
#signin .si-prods > :nth-child(2){--si-r:4}
#signin .si-foot{--si-r:6}
#signin .card{animation:fxModalIn .46s var(--fx-spring) .1s backwards}
/* The shown view's rows rise in 35ms apart, and replay whenever showView() switches views (display
   none -> block restarts them). Not while the restore skeleton is up: the views are hidden then and
   fx.css's failsafe owns their animation; when .fx-restoring drops, this starts and the form arrives.
   The status line is left out: auth.js moves it between views and FX.swap rolls its text in. */
#signin:not(.fx-restoring) .si-view > *{animation:fxRise var(--fx-d3) var(--fx-ease) calc(var(--si-i,0) * 35ms) backwards}
#signin:not(.fx-restoring) .si-view > #siMsg{animation:none}
#signin .si-view > :nth-child(2){--si-i:1}
#signin .si-view > :nth-child(3){--si-i:2}
#signin .si-view > :nth-child(4){--si-i:3}
#signin .si-view > :nth-child(5){--si-i:4}
#signin .si-view > :nth-child(6){--si-i:5}
#signin .si-view > :nth-child(7){--si-i:6}
#signin .si-view > :nth-child(n+8){--si-i:7}
/* the card's title fades in as the session check ends, and again on every view switch (showView restarts it) */
#signin:not(.fx-restoring) .si-head{animation:fxFade var(--fx-d3) var(--fx-soft) backwards}
/* index.html's global :focus-visible{outline:none} leaves inputs to this: a green ring that eases in */
#signin input{transition:border-color var(--fx-d2) var(--fx-soft),box-shadow var(--fx-d2) var(--fx-soft)}
#signin input:focus{border-color:var(--green-br);box-shadow:0 0 0 3px var(--si-ring);outline:none}
/* the primary button: a glow and an arrow nudge on hover, a press dip, and a light band while the request
   is in flight (auth.js disables the button for exactly that span). 8 passes at most. */
#signin .si-go{position:relative;overflow:hidden;
  transition:background-color var(--fx-d2) var(--fx-soft),box-shadow var(--fx-d2) var(--fx-soft),transform 90ms ease}
#signin .si-go::after{content:"";position:absolute;inset:0;pointer-events:none;transform:translateX(-100%);
  background:linear-gradient(100deg,transparent 30%,rgba(255,255,255,.34) 50%,transparent 70%)}
#signin .si-go:disabled::after{animation:fxShimmer 1.1s var(--fx-soft) 8}
#signin .si-arr{transition:transform var(--fx-d2) var(--fx-soft)}
@media (hover:hover){
  #signin .si-go:not(:disabled):hover{box-shadow:0 10px 24px -12px rgba(43,167,106,.8)}
  #signin .si-go:not(:disabled):hover .si-arr{transform:translateX(3px)}
}
#signin .si-go:not(:disabled):active{transform:translateY(1px) scale(.985)}
#signin .si-link{transition:text-decoration-color var(--fx-d2) var(--fx-soft)}
#signin .si-ghost,#signin .si-show{transition:border-color var(--fx-d2) var(--fx-soft),background-color var(--fx-d2) var(--fx-soft)}
#signin .si-ghost:hover{border-color:var(--ink-soft)}
/* The gate leaves through FX.closeAnimated (auth.js enterApp): fx.css fades the [data-fx-modal] backdrop
   and drops its children. Only the card drops here: the bar must stay put in its 56px band (the app's own
   bar is underneath, pixel for pixel) and the intro simply fades with the gate. */
#signin.fx-out .card{animation:fxOut var(--fx-d2) var(--fx-in) forwards}
#signin.fx-out > .si-top,#signin.fx-out > .si-body{animation:none}

/* ---------- 2. topbar ---------- */
/* backend badge (wiring.js setBadge) and contest chip: text only, no dot (founder review 09-28). The
   badge's own colour and ground carry the state. The contest chip's text takes the green the dot used to
   carry once the contest state is known (wiring.js adds .fx-known), and stays grey while loadAdSlots dims
   the chip because no contest is running. */
#contestChip{transition:opacity var(--fx-d3) var(--fx-soft)}
#contestChip.fx-known:not([style*="opacity"]) #contestChipText{color:var(--green-br);transition:color var(--fx-d3) var(--fx-soft)}
/* theme toggle: the half-disc turns over with the theme (the dark state is the flipped disc) */
.themeBtn{transition:transform .5s var(--fx-spring),border-color var(--fx-d2) ease,color var(--fx-d2) ease}
:root[data-theme="dark"] .themeBtn{transform:rotate(180deg)}
/* Circular theme reveal (theme boot script, when startViewTransition exists and motion is allowed): the
   new theme is uncovered by a clip-path circle from the toggle, so neither snapshot cross-fades. */
::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}
/* identity skeletons (L3): the avatars shimmer as neutral discs instead of the placeholder initial */
.acct .av.fx-sk-block{background:rgba(251,248,240,.10)}
#acctPic.fx-sk-block{background:var(--line)}

/* "This session" activity pill (G23). Hidden until a loader reports a real change between two server
   answers this session. Topbar chrome colours (the bar is dark in both themes); the list is a paper card. */
.fx-act-wrap{position:relative;flex:none}
.fx-act{display:inline-flex;align-items:center;gap:8px;max-width:340px;height:30px;padding:0 12px;
  border:1px solid #322f26;border-radius:999px;background:#1f1c15;color:#cfcab9;cursor:pointer;
  font:600 12px/1 "Hanken Grotesk",sans-serif;transition:border-color var(--fx-d2) ease,color var(--fx-d2) ease}
.fx-act:hover,.fx-act[aria-expanded="true"]{border-color:#4a4536;color:#FBF8F0}
.fx-act-k{flex:none;font:700 9.5px/1 "JetBrains Mono",ui-monospace,monospace;letter-spacing:.12em;text-transform:uppercase;color:#9b9684}
/* the newest entry's tone colours the kicker (the pill carries no dot; wiring.js sets data-tone) */
.fx-act[data-tone="green"] .fx-act-k{color:var(--green-br)}
.fx-act[data-tone="gold"] .fx-act-k{color:var(--gold)}
.fx-act-t{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fx-act-list{position:absolute;right:0;top:calc(100% + 10px);z-index:80;width:320px;max-width:calc(100vw - 32px);
  box-sizing:border-box;padding:6px;background:var(--paper-2);color:var(--ink);border:1px solid var(--line);
  border-radius:14px;box-shadow:var(--shadow-lg);transform-origin:90% 0;
  animation:fxModalIn var(--fx-d3) var(--fx-spring) backwards}
.fx-act-h{padding:8px 10px 6px;font:700 10px/1 "JetBrains Mono",ui-monospace,monospace;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-soft)}
.fx-act-row{display:flex;align-items:baseline;gap:9px;padding:9px 10px;font-size:12.5px;line-height:1.4;
  animation:fxRise var(--fx-d3) var(--fx-ease) calc(var(--fx-i,0) * 30ms) backwards}
.fx-act-row + .fx-act-row{border-top:1px solid var(--line)}
.fx-act-rdot{flex:none;width:6px;height:6px;border-radius:50%;background:var(--green-br);transform:translateY(-1px)}
.fx-act-rdot.gold{background:var(--gold)}
.fx-act-rt{flex:1;min-width:0;color:var(--ink-2)}
.fx-act-rw{flex:none;font:500 10.5px/1 "JetBrains Mono",ui-monospace,monospace;color:var(--ink-soft);white-space:nowrap}

/* ---------- 3. sidebar ---------- */
/* The active product is a dark pill that SLIDES between products (FX.tabs on .side, index.html product
   switch script). Until the pill is placed (.fx-tabs-ready) the items render exactly as before. The pill
   carries the green spine and the drop shadow that .prod.active used to. */
.side > .fx-tabpill{contain:none;box-shadow:0 8px 22px -14px rgba(23,21,15,.6)}
.side > .fx-tabpill::before{content:"";position:absolute;left:-14px;top:14px;bottom:14px;width:3px;
  border-radius:0 3px 3px 0;background:var(--green-br)}
.side.fx-tabs-ready > .prod.active{background:transparent;border-color:transparent;box-shadow:none}
.side.fx-tabs-ready > .prod.active::before{content:none}
/* fx.css narrows .fx-tabs-ready children to colour transitions; the sidebar keeps its hover nudge too */
.side.fx-tabs-ready > :is(.prod,.acctbtn){transition:transform .16s var(--fx-soft),background-color .16s var(--fx-soft),
  border-color .16s var(--fx-soft),box-shadow .16s var(--fx-soft),opacity .16s ease}
/* Text never sits light-on-paper: the item gaining the pill turns light a beat late (the pill is still
   arriving), the item losing it turns dark at once (the pill leaves immediately). A transition takes the
   timing of the state it goes TO, so the two rules below split the directions. */
.side.fx-tabs-ready > .prod.active :is(.pname,.pdesc){transition:color .2s var(--fx-soft) .09s}
.side.fx-tabs-ready > .prod:not(.active) :is(.pname,.pdesc){transition:color .1s var(--fx-soft)}
/* the glyph of the product that just became active pops once (also the account avatar) */
.prod.active .glyph,.acctbtn.active .pic{animation:fxPop .36s var(--fx-spring) backwards}
@media (hover:hover){
  .acctbtn{transition:border-color .2s var(--fx-soft),background-color .2s var(--fx-soft),transform .2s var(--fx-soft)}
  .acctbtn:not(.active):hover{border-color:var(--line-2);transform:translateY(-1px)}
}

/* ---------- 4. app entrance ---------- */
/* auth.js adds .fx-enter to .app as the gate leaves (and removes it 1.4s later): the topbar items fade in
   left to right, the sidebar slides in, then the main column fades up. Once per sign-in or restore. */
.app.fx-enter .topbar > *{animation:fxFade .5s var(--fx-soft) calc(120ms + var(--fx-e,0) * 45ms) backwards}
.app.fx-enter .topbar > :nth-child(2){--fx-e:1}
.app.fx-enter .topbar > :nth-child(3){--fx-e:2}
.app.fx-enter .topbar > :nth-child(n+4){--fx-e:3}
.app.fx-enter .topbar > :nth-last-child(-n+3){--fx-e:5}
.app.fx-enter .side{animation:fxShellSideIn .5s var(--fx-ease) .16s backwards}
.app.fx-enter .main{animation:fxFade .45s var(--fx-soft) .24s backwards}
@keyframes fxShellSideIn{from{opacity:0;transform:translateX(-10px)}}

/* ---------- 5. launch-readiness strip (Impressions > Create) ---------- */
/* rises in each time it goes from display:none to flex (the first /me answer, or a later state change) */
#launchReady{animation:fxRise var(--fx-d3) var(--fx-ease) backwards}
/* renderLaunchReadiness keeps two stable children: the storefront ring slot and the chip body, which is
   display:contents so the header and chips stay the strip's own flex items */
#launchReady > .fx-rdy-body{display:contents}
#launchReady > .fx-rdy-ring:not([hidden]){display:inline-flex;flex:none;margin-right:2px;position:relative}
/* a complete storefront reads as a check inside the closed ring, not a bare circle */
#launchReady > .fx-rdy-ring.fx-full::after{content:"✓";position:absolute;inset:0;display:grid;place-items:center;
  font:800 12px/1 "Hanken Grotesk",sans-serif;color:var(--green-br);animation:fxPop var(--fx-d3) var(--fx-spring) .45s backwards}
/* L6: placeholder strip in #launchReady's box until /me answers (wiring.js removes it) */
#fxReadySk{display:flex;align-items:center;gap:10px;flex-wrap:wrap;box-sizing:border-box;min-height:46px;
  padding:10px 16px;margin-bottom:14px;pointer-events:none}

/* ---------- 6. billing rows (Chance Ad Slots + Impressions Billing) ---------- */
/* L5: .fx-pm-slot wraps the saved-card row and the add button; while /reservation is unanswered it holds
   a button-shaped pill and two hint lines (template fxTplPm) instead of "+ Add a payment method" */
.fx-pm-slot{position:relative}
.fx-pm-skrow{display:flex;align-items:center;gap:14px;flex-wrap:wrap;width:100%}
.fx-pm-sklines{flex:1;min-width:160px}
/* the card form rises in when opened; the saved-card row pops in when it first shows */
#pmForm,#impPmForm{animation:fxRise var(--fx-d3) var(--fx-ease) backwards}
#pmOnFile,#impPmOnFile{animation:fxPop var(--fx-d3) var(--fx-spring) backwards}
/* Save card is disabled while Stripe confirms: its sheen band sweeps (8 passes at most) */
:is(#pmSave,#impPmSave):disabled::after{animation:fxShimmer 1.1s var(--fx-soft) 8}
:is(#addPmBtn,#impAddPmBtn):disabled{cursor:progress}

/* ---------- 7. result toast (#beToast, wiring.js toast()) ---------- */
/* Same text, colours and 4.2s as before. Adds an icon, a 2px bar that drains over the 4.2s, a spring in,
   an ease-out exit, and one shake on errors. A hidden toast no longer catches clicks (visibility).
   Its corner (right/bottom 18px, max-width 360px) stays in wiring.js's inline style, so it can never lose
   its place if this sheet fails to load; the overrides below therefore need !important. */
#beToast{display:flex;align-items:flex-start;gap:10px;overflow:hidden;
  transition:opacity var(--fx-d2) var(--fx-in),transform var(--fx-d2) var(--fx-in)}
#beToast.bt-hide{transform:translateY(8px);visibility:hidden;pointer-events:none;
  transition:opacity var(--fx-d2) var(--fx-in),transform var(--fx-d2) var(--fx-in),visibility 0s linear var(--fx-d2)}
#beToast.bt-show{animation:fxShellToastIn var(--fx-d3) var(--fx-spring)}
#beToast.bt-show.bt-err{animation:fxShellToastIn var(--fx-d3) var(--fx-spring),fxShake 280ms var(--fx-soft) var(--fx-d3)}
#beToast .bt-ico{flex:none;display:grid;place-items:center;width:20px;height:20px;border-radius:50%;background:rgba(255,255,255,.2)}
#beToast .bt-ico svg{width:12px;height:12px;overflow:visible}
#beToast .bt-ico path{fill:none;stroke:#fff;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:1}
#beToast .bt-ico circle{fill:#fff}
#beToast.bt-show .bt-ico path{animation:fxShellStroke .38s var(--fx-ease) .14s backwards}
#beToast .bt-msg{flex:1;min-width:0}
#beToast .bt-bar{position:absolute;left:0;right:0;bottom:0;height:2px;background:rgba(255,255,255,.55);
  transform-origin:0 50%;transform:scaleX(0)}
#beToast.bt-show .bt-bar{animation:fxShellDrain 4.2s linear}
@keyframes fxShellToastIn{from{opacity:0;transform:translateY(12px) scale(.98)}}
@keyframes fxShellStroke{from{stroke-dashoffset:1}}
@keyframes fxShellDrain{from{transform:scaleX(1)}to{transform:scaleX(0)}}
/* the update toast (#updateToast, right/bottom 20px) shares this corner: while it is open the result toast
   sits just above it instead of underneath (index.html sets data-open and --upd-h, its measured height) */
:root:has(#updateToast[data-open]) #beToast{bottom:calc(30px + var(--upd-h,150px))!important}

/* ---------- 8. update toast + force-update gate ---------- */
/* Exit through FX.closeAnimated. The toast's own #updateToast{animation:b2sUpdIn} out-ranks .fx-out. */
#updateToast.fx-out{animation:fxOut var(--fx-d2) var(--fx-in) forwards}
/* downloading: a light band sweeps the progress fill (8 passes; the fill's width keeps moving after) */
#updateToast[data-fx-state="downloading"] #updateToastBarFill{position:relative;overflow:hidden}
#updateToast[data-fx-state="downloading"] #updateToastBarFill::after{content:"";position:absolute;inset:0;
  transform:translateX(-100%);background:linear-gradient(90deg,transparent,rgba(255,255,255,.55),transparent);
  animation:fxShimmer 1.3s var(--fx-soft) 8}
/* the blocking gate fades in and its message rises (it only ever appears from display:none) */
#updateGate{animation:fxFade .3s var(--fx-soft)}
#updateGate > div{animation:fxRise .42s var(--fx-ease) .08s backwards}

/* ---------- 9. Company Profile ---------- */
#profile > .phead{animation:fxRise var(--fx-d3) var(--fx-ease) backwards}
/* L4: the name and phone inputs hide their placeholder values until /me answers; a field the advertiser
   focuses shows its text at once, so typing is never invisible */
#profile .fx-sk-solid:focus{color:var(--ink);-webkit-text-fill-color:var(--ink);animation:none}
#pfLogo.fx-sk-block{background:var(--paper-3)}

/* ---------- 10. narrow screens and phones ---------- */
@media (max-width:1100px){
  #fxActWrap{display:none}
}
@media (max-width:768px){
  /* restore skeleton: the phone card's padding and row heights (index.html phone layer, section 5) */
  #signin.fx-restoring .fx-restore-sk{left:18px;right:18px;top:18px}
  .fx-restore-sk .si-sk-seg{height:54px}
  .fx-restore-sk .si-sk-h{margin-top:19px}
  .fx-restore-sk .si-sk-l{margin-top:24px}
  .fx-restore-sk .si-sk-f{height:48px;margin-top:12px}
  .fx-restore-sk .si-sk-f + .si-sk-l{margin-top:18px}
  .fx-restore-sk .si-sk-l + .si-sk-f + .si-sk-l + .si-sk-f{margin-top:12px}
  .fx-restore-sk .si-sk-btn{height:52px;margin-top:18px}
  .fx-restore-sk .fx-restore-cap{padding:15px 0 16px}
  /* bottom tab bar: the indicator is the 2px top bar the phone layer draws on the active tab, sliding */
  .side > .fx-tabpill{background:transparent;box-shadow:none}
  .side > .fx-tabpill::before{content:none}
  .side > .fx-tabpill::after{content:"";position:absolute;top:0;left:22%;right:22%;height:2px;border-radius:2px;background:var(--ink)}
  .side.fx-tabs-ready > .prod.active::after{content:none}
  .app.fx-enter .side{animation-name:fxRise}
  /* the toast clears the bottom tab bar and spans the screen */
  #beToast{left:12px;right:12px!important;max-width:none!important;bottom:calc(76px + env(safe-area-inset-bottom))!important}
  .fx-pm-skrow{flex-direction:column;align-items:stretch;gap:8px}
  .fx-pm-skrow .fx-sk-pill{width:100%}
}
@media (max-width:900px) and (max-height:460px){
  #beToast{bottom:12px!important}
}

/* ---------- 11. reduced motion ---------- */
/* index.html's kill switch makes every animation .001ms but keeps delays (a staggered row would sit at
   its from-state for the delay), so everything this file starts is switched off outright, and the sweeps
   and the drain bar are removed. */
@media (prefers-reduced-motion: reduce){
  #signin .card,#signin:not(.fx-restoring) .si-view > *,#signin.fx-out .card,
  #signin .si-top > *,#signin .si-intro > *,#signin .si-prods > *,#signin .si-foot,
  #signin:not(.fx-restoring) .si-head,
  .prod.active .glyph,.acctbtn.active .pic,.fx-act-list,.fx-act-row,
  .app.fx-enter .topbar > *,.app.fx-enter .side,.app.fx-enter .main,
  #launchReady,#pmForm,#impPmForm,#pmOnFile,#impPmOnFile,
  #beToast.bt-show,#beToast.bt-show .bt-ico path,#beToast.bt-show .bt-bar,
  #updateToast.fx-out,#updateGate,#updateGate > div,#profile > .phead,
  #launchReady > .fx-rdy-ring.fx-full::after{animation:none}
  #signin .si-go::after,:is(#pmSave,#impPmSave)::after,#beToast .bt-bar,
  #updateToast[data-fx-state="downloading"] #updateToastBarFill::after{display:none}
  .themeBtn,#signin input,#signin .si-seg-pill,#signin .si-seg button,#signin .si-go,#signin .si-arr,
  #signin .si-link,#signin .si-ghost,#signin .si-show,.acctbtn,#beToast,#beToast.bt-hide{transition:none}
  .acctbtn:not(.active):hover{transform:none}
}
