/* The interactive demo: the build card and the animated film.
   Shared by frontend/marketing/index.html and frontend/coming-soon.html, so there
   is one copy. Needs /tokens.css loaded first. Markup and behaviour are in
   demo.js, which renders into <div id="demo">.

   This file is one of the few the coming-soon gate lets through without a
   preview cookie (_EXEMPT_ASSETS in src/hireday/api/coming_soon.py). A new file the demo
   needs must be added there too, or it 404s behind the gate -- in production
   only, which is why that is easy to miss. */

/* Scope colours, matching templates/builder.html where they are defined. --global
   means scope and ONLY scope: it marks a task that comes from the company
   checklist, never an owner and never a due mode. One token, one meaning. */
:root{--global:#1B6B41;--global-dark:#14532F;--global-dim:#A9D5B8;--global-tint:#E1EDE2}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){--global:#6FD79B;--global-dark:#8FE5B3;
    --global-dim:#2C4A3C;--global-tint:#27362D}
}
:root[data-theme="dark"]{--global:#6FD79B;--global-dark:#8FE5B3;
  --global-dim:#2C4A3C;--global-tint:#27362D}

/* The demo sets its own body metrics, so it reads the same whichever page hosts it. */
.stage,.film{font-size:15px;line-height:1.55}
@media (prefers-reduced-motion:reduce){
  .hire.active .halo circle{animation:none;opacity:0}
}

/* ---------- the build surface ---------- */
.stage{max-width:760px;margin:26px auto 0;padding:0 20px}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-card);
  box-shadow:var(--shadow-sm);overflow:hidden}
.step{border-bottom:1px solid var(--border);padding:18px 22px}
.step:last-child{border-bottom:0}
.step[hidden]{display:none}
.step-h{display:flex;align-items:baseline;gap:10px;margin-bottom:12px}
.step-n{font-weight:600;font-size:.6875rem;letter-spacing:.1em;
  color:var(--text-3);flex:none}
.steph{margin:0;font:inherit;font-weight:inherit}
.step-t{font-size:.9375rem;font-weight:600;letter-spacing:-.01em;margin:0;display:inline}
.step-sub{color:var(--text-3);font-size:.8125rem;margin-left:auto;font-weight:600;}

/* choice chips (templates, hires) */
.choices{display:flex;gap:8px;flex-wrap:wrap}
.choice{display:inline-flex;align-items:center;gap:8px;background:var(--surface-2);
  border:1px solid var(--border);border-radius:var(--radius-sm);padding:9px 14px;
  font:inherit;font-size:.875rem;color:var(--text-2);cursor:pointer;text-align:left}
.choice:hover{border-color:var(--border-mid);color:var(--text)}
.choice[aria-pressed="true"]{background:var(--accent-tint);border-color:var(--accent-dim);
  color:var(--accent-tx);font-weight:600}
.choice .av{width:22px;height:22px;border-radius:50%;background:var(--bg-tint);
  display:grid;place-items:center;font-size:.625rem;font-weight:700;color:var(--text-2);flex:none}
.choice[aria-pressed="true"] .av{background:var(--accent);color:var(--on-accent)}

/* task picker, shaped like the real ledger */
.bkt{display:flex;align-items:center;gap:10px;margin:14px 0 6px}
.bkt span:first-child{font-weight:600;font-size:.6875rem;letter-spacing:.1em;
  color:var(--text-3)}
.bkt i{flex:1;height:1px;background:var(--border);display:block}
.bkt-note{font-weight:600;font-size:.625rem;color:var(--text-3);flex:none}
.bkt.is-global span:first-child{color:var(--global)}
.bkt.is-global i{background:var(--global-dim)}
.bkt.is-global .bkt-note{color:var(--global)}
/* A global row is reference, not workspace: tinted, locked, and it keeps the
   scope colour the builder uses for exactly this. */
.trow.is-global{background:var(--global-tint);cursor:default}
.trow.is-global:hover{background:var(--global-tint)}
.tbox.locked{background:var(--global-dim);border-color:var(--global-dim);position:relative}
.tbox.locked::after{content:"";position:absolute;left:4.5px;top:1px;width:4px;height:8px;
  border:solid var(--global-dark);border-width:0 2px 2px 0;transform:rotate(45deg)}
.globe{color:var(--global);font-size:.625rem;vertical-align:2px}
/* The carry marker is a disclosure, not decoration: it opens what the task
   carries underneath the row. Sized to the 24px target minimum. */
.carry{display:inline-flex;vertical-align:-2px;margin-left:6px;color:var(--accent-tx)}
button.carry{background:none;border:0;padding:0;width:24px;height:24px;margin-left:2px;
  cursor:pointer;border-radius:var(--radius-sm);align-items:center;justify-content:center}
button.carry:hover{background:var(--accent-tint)}
button.carry[aria-expanded="true"]{background:var(--accent-tint)}
.tnote{grid-column:1/-1;margin:2px 0 0 26px;padding:8px 10px;border-radius:var(--radius-sm);
  background:var(--bg-tint);color:var(--text-2);font-size:.8125rem;line-height:1.5}
.tnote-url{font-weight:600;font-size:.75rem;color:var(--accent-tx);
  word-break:break-all}
.carry svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}
.ledger{border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden}
.trow{display:grid;grid-template-columns:26px minmax(0,1fr) 92px 96px;align-items:center;
  gap:10px;padding:10px 12px;border-bottom:1px solid var(--border);
  background:none;border-left:0;border-right:0;border-top:0;width:100%;
  font:inherit;text-align:left;color:var(--text);cursor:pointer}
.trow:last-child{border-bottom:0}
.trow:hover{background:var(--bg-tint)}
/* Selected is the DEFAULT here -- the demo opens with every job task on -- so
   tinting the selected ones put accent behind nine rows out of nine and made the
   normal state the loud one. Brand lives in lines, not surfaces (see
   UI-DESIGN-STANDARDS): the checkbox carries the state, and the rows that earn a
   visual difference are the ones the visitor has turned OFF. */
/* --text-3 and a strikethrough, and NOT opacity on top. Opacity composites the
   text toward the background, so the 5.9:1 these tokens are chosen for is not
   what ends up on screen -- and "turned off" is already unambiguous without it. */
.trow[aria-pressed="false"] .tname,
.trow[aria-pressed="false"] .town,
.trow[aria-pressed="false"] .tdue{color:var(--text-3)}
.trow[aria-pressed="false"] .tname{text-decoration:line-through;
  text-decoration-thickness:1px;text-decoration-color:var(--border-mid)}
/* 3-4px on a 16px box is not an off-scale radius, it is the only radius that reads
   as a rounded square at that size; --radius-sm (6px) turns a checkbox into a blob. */
.tbox{width:16px;height:16px;border:1.5px solid var(--border-mid);border-radius:4px;
  background:var(--surface);position:relative;flex:none}
.trow[aria-pressed="true"] .tbox{background:var(--accent);border-color:var(--accent)}
.trow[aria-pressed="true"] .tbox::after{content:"";position:absolute;left:4.5px;top:1px;
  width:4px;height:8px;border:solid var(--on-accent);border-width:0 2px 2px 0;transform:rotate(45deg)}
/* 24px line box whether or not the row carries a disclosure button, so the two
   rows that do are not 2px taller than the five that do not. */
.tname{font-size:.875rem;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;line-height:24px}
.town{font-size:.75rem;color:var(--text-2);font-weight:600}
/* nowrap + 96px: "before start" is 12 monospace characters, ~86px, which did not
   fit the old 84px column -- so four of the seven rows wrapped their due date to
   a second line and stood 15px taller than the rest. A custom due can be any
   length, so it truncates rather than reflowing the row. */
.tdue{font-weight:600;font-size:.75rem;color:var(--text-3);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* The relative due is what you SET; the date is what the hire actually sees.
   Showing both is the point -- "a week before" is only meaningful next to the
   day it lands on. */
.tdate{display:block;font-size:.625rem;color:var(--text-3);line-height:1.6}
/* The count is the whole point of the section -- it is the sentence that says
   what one press is about to do -- and it used to change in total silence. One
   short pulse on the number itself, not the row it sits in. */
/* Always accent, not only after the first toggle: it is the number the section
   exists to produce, and a colour that appears once you interact is a colour that
   means "you touched something" rather than "this is the answer". The bump is
   motion only. */
#sendSum b:first-child{color:var(--accent-tx)}
#sendSum.bumped b:first-child{animation:sum-bump var(--dur-slow) var(--ease-out)}
@keyframes sum-bump{0%{transform:translateY(-2px)}60%{transform:translateY(0)}100%{transform:none}}
#sendSum b:first-child{display:inline-block}
@media (prefers-reduced-motion: reduce){
  #sendSum.bumped b:first-child{animation:none}
}
.custom-row{display:flex;gap:8px;padding:10px 12px;border-top:1px solid var(--border);
  background:var(--bg-tint-2)}
.custom-row input{flex:1;background:var(--surface);border:1px solid var(--border-mid);
  border-radius:var(--radius-sm);padding:7px 10px;font:inherit;font-size:.875rem;color:var(--text)}
.custom-row input::placeholder{color:var(--text-3)}
/* the lightweight compose block */
.compose .custom-row{border-top:0}
.compose-row{display:flex;align-items:center;gap:9px;padding:7px 12px;
  border-top:1px solid var(--border);background:var(--bg-tint-2);flex-wrap:wrap}
.crl{font-weight:600;font-size:.625rem;letter-spacing:.08em;
  color:var(--text-3);min-width:62px;flex:none;text-transform:uppercase}
.crchips{display:flex;gap:4px;flex-wrap:wrap}
.crchip{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius-pill);padding:3px 10px;font:inherit;font-size:.75rem;
  color:var(--text-3);cursor:pointer}
.crchip:hover{color:var(--text);border-color:var(--border-mid)}
.crchip[aria-pressed="true"]{background:var(--panel-inv);border-color:var(--panel-inv);
  color:var(--panel-inv-tx);font-weight:600}
.compose-row input{flex:1;min-width:180px;background:var(--surface);
  border:1px solid var(--border-mid);border-radius:var(--radius-sm);padding:6px 10px;
  font:inherit;font-size:.8125rem;color:var(--text)}
.compose-row input::placeholder{color:var(--text-3)}
.mini{border:1px solid var(--border-mid);background:var(--surface);color:var(--text-2);
  border-radius:var(--radius-sm);padding:7px 13px;font:inherit;font-size:.8125rem;
  font-weight:600;cursor:pointer}
.mini:hover{color:var(--text)}
/* one row per person: who they are, the job they are in, what that adds */
/* No flex-wrap. With wrapping allowed the row wraps BEFORE it shrinks, so the
   "+3" dropped to a second line the moment the content went 20px over -- which
   it does for the two longer emails at 900px, leaving rows of 49, 74, 74 and 49.
   Without it .hpick shrinks (floored at 150px) and the email ellipsises instead.
   Narrow screens do not need the wrap either: below 780px the row is a grid. */
.hrow{display:flex;align-items:center;gap:10px;padding:9px 10px;border:1px solid var(--border);
  border-radius:var(--radius-sm);margin-bottom:8px;background:var(--surface-2)}
.hrow.on{background:var(--accent-tint);border-color:var(--accent-dim)}
.hpick{display:flex;align-items:center;gap:9px;background:none;border:0;padding:0;
  font:inherit;cursor:pointer;text-align:left;color:var(--text);min-width:150px}
.hpick .av{width:26px;height:26px;border-radius:50%;background:var(--bg-tint);
  display:grid;place-items:center;font-size:.625rem;font-weight:700;color:var(--text-2);flex:none}
.hrow.on .hpick .av{background:var(--accent);color:var(--on-accent)}
/* "Other" accepts 40 characters and derives an email from it, so without this a
   long name pushes the job chips onto a second line and that row alone stands
   28px taller than its neighbours. */
.hnm{font-size:.875rem;font-weight:600;display:block;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.hem{font-size:.6875rem;color:var(--text-3);font-weight:600;display:block;
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.jobs{display:flex;gap:4px;margin-left:auto;flex-wrap:wrap}
.jobbtn{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-pill);
  padding:4px 11px;font:inherit;font-size:.75rem;color:var(--text-3);cursor:pointer}
.jobbtn:hover:not(:disabled){color:var(--text);border-color:var(--border-mid)}
.jobbtn[aria-pressed="true"]{background:var(--panel-inv);border-color:var(--panel-inv);
  color:var(--panel-inv-tx);font-weight:600}
.jobbtn:disabled{color:var(--text-3);background:var(--bg-tint-2);border-color:var(--border);cursor:not-allowed}
.hct{font-weight:600;font-size:.75rem;color:var(--accent-tx);
  min-width:26px;text-align:right}
.tbox.locked.team{background:var(--accent-dim);border-color:var(--accent-dim)}
.tbox.locked.team::after{border-color:var(--accent-tx)}
button.disclose{width:100%;background:none;border:0;padding:0;font:inherit;color:inherit;
  cursor:pointer;margin-bottom:0}
button.disclose .chev{color:var(--text-3);transition:transform var(--dur-quick) var(--ease-out);margin-left:8px}
button.disclose[aria-expanded="true"] .chev{transform:rotate(90deg)}
button.disclose[aria-expanded="true"]{margin-bottom:12px}
.othername{display:flex;gap:8px;margin-top:10px}
.othername input{flex:1;background:var(--surface);border:1px solid var(--border-mid);
  border-radius:var(--radius-sm);padding:8px 11px;font:inherit;font-size:.875rem;color:var(--text)}
/* Prose gets the body face. DM Mono is reserved by UI-DESIGN-STANDARDS section 2 for
   "the uppercase, letter-spaced section headers and the monospace pills that carry a
   scope or source label" -- labels, counts, dates and addresses. A sentence set in a
   monospace face reads like terminal output rather than like someone talking, which
   is exactly the machine-written texture this page cannot afford. Also lifted off the
   12px floor: the standard says never set a sentence a user must read below xs. */
.hint{color:var(--text-2);font-size:.8125rem;line-height:1.5;margin-top:8px}

/* send bar */
.sendbar{display:flex;align-items:center;gap:12px;padding:14px 20px;
  background:var(--surface);border:1px solid var(--border);
  border-radius:0 0 var(--radius-card) var(--radius-card);border-top:0;
  position:sticky;bottom:0;z-index:5;box-shadow:var(--shadow-md)}
.sendbar .sum{color:var(--text-2);font-size:.8125rem;flex:1}
.sendbar .sum b{color:var(--text)}
.btn{background:var(--accent);color:var(--on-accent);border:0;border-radius:var(--radius-sm);
  padding:10px 22px;font:inherit;font-weight:700;font-size:.9375rem;cursor:pointer}
.btn:disabled{background:var(--surface);color:var(--text-3);border:1px solid var(--border);
  cursor:not-allowed}
.btn:not(:disabled):hover{background:var(--accent-dark)}

/* ---------- the film ---------- */
.film{max-width:760px;margin:26px auto 0;padding:0 20px}
.film[hidden]{display:none}
.screen{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--radius-card);box-shadow:var(--shadow-md);overflow:hidden;
  position:relative}
.scene{padding:22px;display:none}
.scene.on{display:block;animation:fade var(--dur-slow) var(--ease-out) both}
@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.scene-h{display:flex;align-items:center;gap:9px;margin-bottom:14px}
.scene-h .who{width:26px;height:26px;border-radius:50%;background:var(--bg-tint);
  display:grid;place-items:center;font-size:.625rem;font-weight:700;color:var(--text-2);flex:none}
.scene-h b{font-size:.9375rem;font-weight:600}
.scene-h .when{margin-left:auto;font-weight:600;font-size:.6875rem;color:var(--text-3)}

.mail{display:flex;gap:11px;padding:12px 14px;border:1px solid var(--border);
  border-radius:var(--radius-sm);margin-bottom:9px;background:var(--surface-2);
  animation:fade var(--dur-slow) var(--ease-std) both}
.mail .ic{width:28px;height:28px;border-radius:var(--radius-sm);display:grid;place-items:center;
  flex:none;background:var(--bg-tint);color:var(--text-2)}
.mail .ic svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:1.75;
  stroke-linecap:round;stroke-linejoin:round}
.mail .bd{min-width:0;flex:1}
.mail .sub{font-size:.875rem;font-weight:600}
.mail .to{font-size:.75rem;color:var(--text-3);font-weight:600;}
.tag{font-weight:600;font-size:.625rem;letter-spacing:.08em;padding:3px 8px;
  border-radius:var(--radius-pill);white-space:nowrap;align-self:center}
.tag.wait{background:var(--amber-bg);color:var(--amber-tx);border:1px solid var(--custom-dim)}
.tag.sent{background:var(--accent-tint);color:var(--accent-tx);border:1px solid var(--accent-dim)}

.chk{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid var(--border);
  border-radius:var(--radius-sm);margin-bottom:8px;background:var(--surface-2)}
.chk .bx{width:17px;height:17px;border:1.5px solid var(--border-mid);border-radius:4px;
  flex:none;position:relative;background:var(--surface)}
.chk.done .bx{background:var(--accent);border-color:var(--accent)}
.chk.done .bx::after{content:"";position:absolute;left:5px;top:1.5px;width:4px;height:8px;
  border:solid var(--on-accent);border-width:0 2px 2px 0;transform:rotate(45deg)}
.chk .lb{font-size:.875rem;flex:1}
.chk.done .lb{color:var(--text-3);text-decoration:line-through}
.chk .mark{font-weight:600;font-size:.6875rem;color:var(--accent-tx);opacity:0}
.chk.done .mark{opacity:1}

.win{text-align:center;padding:26px 20px}
.win .big{font-size:2.6rem;line-height:1;margin-bottom:10px}
.win h3{margin:0 0 6px;font-size:1.25rem;font-weight:700;letter-spacing:-.02em}
.win p{color:var(--text-2);margin:0 auto;max-width:44ch;font-size:.9375rem}
.wingrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:9px;margin-top:18px}
.wincard{background:var(--accent-tint);border:1px solid var(--accent-dim);
  border-radius:var(--radius-sm);padding:11px 13px;text-align:left}
.wincard b{display:block;font-size:.8125rem;color:var(--accent-tx)}
.wincard span{font-size:.75rem;color:var(--text-2)}

/* the org-hierarchy epilogue */
.syspick{display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap}
.sysbtn{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius-sm);
  padding:6px 12px;font:inherit;font-size:.8125rem;color:var(--text-2);cursor:pointer}
.sysbtn:hover{border-color:var(--border-mid);color:var(--text)}
.sysbtn[aria-pressed="true"]{background:var(--accent-tint);border-color:var(--accent-dim);
  color:var(--accent-tx);font-weight:600}
.sysrow{display:flex;align-items:center;gap:11px;padding:12px 14px;border:1px solid var(--border);
  border-radius:var(--radius-sm);background:var(--surface-2)}
.sys{font-weight:600;font-size:.625rem;letter-spacing:.08em;
  background:var(--bg-tint);color:var(--text-2);border:1px solid var(--border);
  border-radius:var(--radius-pill);padding:3px 9px;flex:none}
.sysbd{font-size:.8125rem;color:var(--text-2);min-width:0}
.sysbd b{color:var(--text)}
.arrow{text-align:center;color:var(--text-3);font-size:1.05rem;line-height:1;padding:7px 0}
.matchrow{padding:12px 14px;border:1px solid var(--accent-dim);border-radius:var(--radius-sm);
  background:var(--accent-tint)}
.mtag{display:block;font-weight:600;font-size:.625rem;letter-spacing:.1em;
  color:var(--accent-tx);margin-bottom:3px}
.matchrow b{font-size:.9375rem}
.msub{color:var(--text-2);font-size:.8125rem;margin-left:7px}
/* ---------- the animated stage ---------- */
#stage{display:block;width:100%;height:auto;padding:14px 10px 0}
#stage text{font-family: var(--font-ui)}
.card{}
#stage .card{fill:var(--surface-2);stroke:var(--border);stroke-width:1}
#stage .origin{fill:var(--bg-tint);stroke:var(--accent-dim,var(--border-mid));stroke-width:1}
#stage .olbl{font-size:11px;font-weight:600;fill:var(--accent-tx)}
#stage .wire.seed{stroke-dasharray:none;opacity:.5}
#stage .lbl{font-size:12px;font-weight:700;fill:var(--text)}
#stage .sub{font-size:9px;fill:var(--text-3);font-weight:600;}
#stage .stay{font-size:9px;fill:var(--text-3);font-weight:600;}

/* The hire's own list. Rows fade up as they are ticked, so the block fills rather
   than sitting there greyed out waiting -- the film is short and a row that never
   changes is a row nobody reads. */
#stage .jrow{opacity:0;transform:translateY(3px);
  transition:opacity var(--dur-base) var(--ease-out),transform var(--dur-base) var(--ease-out)}
#stage .jrow.shown{opacity:1;transform:none}
#stage .jbox{fill:var(--surface);stroke:var(--border-mid);stroke-width:1.1}
#stage .jrow.done .jbox{fill:var(--global);stroke:var(--global)}
#stage .jtick{fill:none;stroke:var(--panel-inv-tx);stroke-width:1.6;stroke-linecap:round;
  stroke-linejoin:round;opacity:0;transition:opacity var(--dur-quick) var(--ease-out)}
#stage .jrow.done .jtick{opacity:1}
#stage .jt{font-size:10.5px;fill:var(--text-2);font-weight:600;}
#stage .jrow.done .jt{fill:var(--text-3)}
#stage .jo{font-size:9px;fill:var(--text-3);font-weight:600;text-anchor:end}
#stage .ln{fill:var(--border-mid)}
#stage .ln.glob{fill:var(--global-dim)}
#stage .ln.team{fill:var(--accent-dim)}

/* wires draw themselves, then carry colour when their role activates */
#stage .wire{fill:none;stroke:var(--border-mid);stroke-width:1.5;
  stroke-dasharray:220;stroke-dashoffset:220;opacity:.55}
#stage .wire.lit{stroke-dashoffset:0;opacity:1;stroke:var(--accent);
  transition:stroke-dashoffset var(--dur-slow) var(--ease-out),
    opacity var(--dur-base) var(--ease-out),stroke var(--dur-base) var(--ease-out)}
#stage .wire.done{stroke:var(--global)}
#stage .ahead{fill:var(--text-3)}

#stage .pill{fill:var(--surface-2);stroke:var(--border);stroke-width:1}
#stage .rlbl{font-size:11.5px;font-weight:600;fill:var(--text-2)}
#stage .rml{font-size:8.5px;fill:var(--text-3);font-weight:600;}
/* The hire's job. The picture used to show hires as pure RECIPIENTS -- a name and
   a status -- but OwnerType makes them owners like IT, HR and Buddy, and "each job
   adds its own tasks" is the claim the fan-out exists to prove. Mono because it is
   a label, not prose. */
#stage .job{font-size:8.5px;fill:var(--text-3);font-weight:600;}
/* Marcus is not a mailbox: no tick, and the dashed pill stays quiet even when lit. */
.role.quiet .pill{fill:none}
.role.quiet.on .pill,.role.quiet.done .pill{fill:none;stroke:var(--border-mid)}
#stage .ct{font-weight:700;fill:var(--accent-tx)}
.role.quiet .pill{stroke-dasharray:4 3}
.role.quiet.on .pill{fill:var(--surface-2);stroke:var(--border-mid)}
.role.quiet.on .rlbl{fill:var(--text-2);font-weight:600}
.role.quiet.done .pill{fill:var(--surface-2);stroke:var(--border-mid)}
.role.quiet.done .rlbl{fill:var(--text-2)}
.role.quiet .tick path{stroke:var(--text-3)}
#stage .ic svg{fill:none;stroke:var(--text-3);stroke-width:1.8;
  stroke-linecap:round;stroke-linejoin:round}
#stage .tick path{fill:none;stroke:var(--global);stroke-width:2.4;
  stroke-linecap:round;stroke-linejoin:round;opacity:0}
.role.on .pill{stroke:var(--accent);fill:var(--accent-tint)}
.role.on .rlbl{fill:var(--accent-tx);font-weight:700}
.role.on .rml{fill:var(--accent-tx)}
.role.done .rml{fill:var(--global)}
.role.done .ct{fill:var(--global)}
.role.on .ic svg{stroke:var(--accent-tx)}
.role.done .pill{stroke:var(--global-dim);fill:var(--global-tint)}
.role.done .rlbl{fill:var(--global)}
.role.done .ic svg{stroke:var(--global)}
.role.done .tick path{opacity:1;animation:pop var(--dur-base) var(--ease-out) both}
#stage .node{transition:opacity var(--dur-base) var(--ease-out)}
@keyframes pop{from{transform:scale(.4);opacity:0}to{transform:scale(1);opacity:1}}

#stage .av{fill:var(--surface-2);stroke:var(--border-mid);stroke-width:1.5}
#stage .av.more{stroke-dasharray:4 3}
#stage .ini{font-size:12px;font-weight:700;fill:var(--text-2)}
#stage .nm{font-size:12px;font-weight:600;fill:var(--text)}
#stage .st{font-size:9px;fill:var(--text-3);font-weight:600;}
#stage .halo circle{fill:none;stroke:var(--accent);stroke-width:2;opacity:0}
.hire.active .halo circle{opacity:1;animation:ring 1.3s ease-out infinite}
.hire.active .av{stroke:var(--accent);fill:var(--accent-tint)}
.hire.done .av{fill:var(--global-tint);stroke:var(--global)}
.hire.done .ini{fill:var(--global)}
.hire.done .st{fill:var(--global)}
@keyframes ring{0%{transform:scale(1);opacity:.75}100%{transform:scale(1.5);opacity:0}}
/* transform-box defaults to view-box in SVG, so a transform-origin written in
   local coordinates resolves against the whole 660x330 canvas instead of the
   element -- the ring scaled from the diagram's top-left corner rather than
   pulsing in place. fill-box makes the origin relative to the element's own
   bounding box, which is what "center" is supposed to mean here.
   (MDN: transform-box; W3C css-transforms-1.) */
#stage .halo{transform-box:fill-box;transform-origin:center}

/* Only opacity and transform are reliably GPU-accelerated, so the flight animates
   those and nothing else. will-change promotes the handful of moving nodes rather
   than the whole diagram. */
#stage .pk{opacity:0;will-change:transform,opacity}
#stage .pk svg{fill:none;stroke:var(--accent-tx);stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}
#stage .pk.fly{animation:fly var(--dur-travel) var(--ease-std) both}
@keyframes fly{0%{opacity:0}12%{opacity:1}88%{opacity:1}100%{opacity:0}}

/* Zoom-out. The unit shrinks into the first tile slot while three more spin up
   beside it, staggered -- the shape of pods coming online, which is the honest
   picture of bulk assign. Replaces a dashed arrow that curved back to the start
   and explained nothing. */
/* The pull-back is the one move on the page that should read as a camera rather
   than a state change, so it runs longer than any UI transition and shares its
   curve with the viewBox lerp in reframe() -- cubic-bezier(.65,0,.35,1) is the
   CSS spelling of the easeInOutCubic that lerp uses. If the two disagreed, the
   diagram would arrive before the frame did. */
#stage{--dur-pull:1300ms;--ease-pull:cubic-bezier(.65,0,.35,1)}
#unit{transition:transform var(--dur-pull) var(--ease-pull),
  opacity var(--dur-base) var(--ease-out)}
/* Once it has shrunk into the first tile the unit hands over: it fades out and
   that tile's own progress dots fade in, so John stops being the diagram and
   becomes one of the row. */
#stage.settled #unit{opacity:0}
#grid .tpos[data-slot="0"] .tprog,#grid .tpos[data-slot="0"] .tcnt{opacity:0;
  transition:opacity var(--dur-base) var(--ease-out)}
#stage.settled #grid .tpos[data-slot="0"] .tprog,
#stage.settled #grid .tpos[data-slot="0"] .tcnt{opacity:1}
/* All three numbers come from layoutPods, which measures the unit and fits it to
   the tile. They used to be hand-tuned against a 592x232 unit in a 660-wide box;
   both of those have changed twice since, and each time the unit landed off
   centre until someone re-derived them. */
#stage.zoomed #unit{transform:translate(var(--zx,22px),var(--zy,86px)) scale(var(--zs,.19))}
/* Phase A is ONE container opened up. p1/p2 and their wires used to run here
   too, which meant the pull-back "revealed" the same three hires it had just
   shown -- the zoom restated instead of revealing. They are pods now. */
#stage #p1,#stage #p2,#stage #pMore,
#stage .wire[data-w="h1"],#stage .wire[data-w="h2"]{display:none}
#grid{opacity:1}
/* transform-box:fill-box for the same reason the halo needs it -- without it
   transform-origin:center resolves against the whole 660x270 canvas, so each tile
   scaled toward the middle of the stage and drifted into place instead of
   settling where it belongs. The stagger is set per slot in JS now, because the
   number of pods depends on who you picked. */
#grid .tile{opacity:0;transform:scale(.88);transform-box:fill-box;transform-origin:center;
  transition:opacity var(--dur-slow) var(--ease-out),transform var(--dur-slow) var(--ease-out)}
#stage.zoomed #grid .tile{opacity:1;transform:scale(1)}
#stage .tcard{fill:var(--surface-2);stroke:var(--border-mid);stroke-width:1}
#stage .tile.next .tcard{stroke-dasharray:5 4;fill:none}
#stage .tnm{font-size:12px;font-weight:700;fill:var(--text)}
#stage .tjob{font-size:8.5px;fill:var(--text-3);font-weight:600;}
/* Each pod's progress through the same list the hire above just finished. Four
   unlabelled dots said nothing -- a track that fills, with "4 of 10" over it,
   says a person is being onboarded. The width steps once per task rather than
   sliding, so it still reads as progress when motion is off. */
#stage .ttrack{fill:var(--border);opacity:.7}
#stage .tfill{fill:var(--accent);transition:width var(--dur-quick) linear}
#stage .tcnt{font-size:9.5px;font-weight:600;fill:var(--text-2);
  font-weight:600;}
#stage .tile.next .tfill{display:none}
#stage .tile.next .tcnt{display:none}
#stage .tlab{font-size:10px;font-weight:600;fill:var(--text-2);opacity:0;
  transition:opacity var(--dur-slow) var(--ease-out) 480ms}
#stage .tlab.next{fill:var(--text-3);font-weight:600;font-size:8.5px}
#stage.zoomed .tlab{opacity:1}
#stage .tile.unit .tcard{fill:none;stroke:var(--accent)}

#stage .tcap{font-size:11px;font-weight:600;fill:var(--text-2);opacity:0;
  transition:opacity var(--dur-slow) var(--ease-out) 560ms}
#stage.zoomed .tcap{opacity:1}

#loop.on{opacity:1}
#stage .loopwire{fill:none;stroke:var(--text-3);stroke-width:1.4;stroke-dasharray:5 5;
  opacity:.75}
#stage .syscard{fill:var(--surface-2);stroke:var(--border-mid);stroke-width:1}
#stage .syslbl{font-size:11px;font-weight:600;fill:var(--text)}
#stage .syssub{font-size:9px;fill:var(--text-3);font-weight:600;}
#stage .tierbadge{fill:var(--panel-inv);stroke:none}
#stage .tiertxt{font-size:8px;font-weight:700;letter-spacing:.09em;fill:var(--panel-inv-tx);
  font-weight:600;}

.caption{text-align:center;color:var(--text-2);font-size:.875rem;padding:4px 20px 14px;
  min-height:2.4em}
.caption b{color:var(--text)}
/* Swapping textContent made each beat arrive as a jump cut even with motion on.
   The 4px lift is the only moving part, so it is the part reduced-motion drops. */
.caption.in{animation:capIn var(--dur-base) var(--ease-out) both}
@keyframes capIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
.progress{display:flex;gap:5px;padding:0 22px 16px}
.pdot{height:3px;flex:1;background:var(--border);border-radius:3px}
.pdot.on{background:var(--accent)}
.filmbar{display:flex;align-items:center;gap:12px;padding:14px 22px;background:var(--surface-2);
  border-top:1px solid var(--border)}
.filmbar .lbl{flex:1;color:var(--text-2);font-size:.8125rem}

@media (max-width:640px){
  /* The due date used to be display:none here, so a phone saw the tasks and the
     owners but none of the dates -- and the dates are what this section's own
     header promises ("required to start - with SLAs"). Giving the name the full
     width and dropping owner + due to a second line shows everything AND stops
     the truncation: three of the seven read "Provision company..." when the
     owner sat beside them in a 76px column. Every row gains the line, so the
     rhythm stays even. */
  .trow{grid-template-columns:26px auto minmax(0,1fr);
    grid-template-areas:"box name name" ". own due" "note note note";
    row-gap:2px;column-gap:8px;align-items:baseline}
  .tnote{grid-area:note;margin-left:0}
  .tbox{grid-area:box;align-self:center}
  .tname{grid-area:name}
  .town{grid-area:own}
  .tdue{grid-area:due}
  /* 01/03 put title, count and chevron on one flex row; at 375px the title and
     the count each wrapped to two lines against each other. The count gets its
     own line. */
  .step-h{flex-wrap:wrap}
  /* text-align:left because the UA centres a button's text, which only showed
     once the count became a full-width row. 25px lines it up under the title:
     the 01 is 15px and the flex gap is 10. */
  .step-sub{order:4;flex:0 0 100%;margin-left:0;padding-left:25px;text-align:left}
  .disclose .chev{order:3;margin-left:auto}
  /* label left, rule, note right does not fit either. Stack them, both left --
     the note pushed to the right edge of its own line read as a stray fragment. */
  .bkt{flex-wrap:wrap;row-gap:2px}
  .bkt i{display:none}
  .bkt-note{flex:0 0 100%;margin-left:0}
  /* The diagram keeps its layout on a phone but the type does not survive the
     scale, so lift it rather than relaying out -- the shapes still read. */
  #stage .lbl{font-size:15px}
  #stage .rlbl{font-size:14px}
  #stage .rml{font-size:11px}
  #stage .nm{font-size:14px}
  #stage .ini{font-size:14px}
  #stage .st,#stage .sub{font-size:11px}
  #stage .job{font-size:11px}
  #stage .tnm{font-size:14px}
  #stage .tlab{font-size:12px}
  #stage .tlab.next{font-size:11px}
  #stage .tcap{font-size:13px}
  #stage .syslbl{font-size:13px}
  #stage .syssub{font-size:11px}
  #stage .tiertxt{font-size:10px}
  .filmbar .lbl{display:none}
  .filmbar{justify-content:flex-end}
  /* [avatar name email] [3 job chips] [+3] on one flex row needs ~560px. At 375
     the name was breaking mid-word ("John / Doe") and running under the email.
     Three stacked rows instead: name, then email, then the job chips full width. */
  .sendbar{flex-wrap:wrap}
  .sendbar .sum{flex:1 0 100%;order:2}
  .sendbar .btn{width:100%;order:1}
}
/* The hire row needs ~633px of content for [avatar name email][3 job chips][+3]
   on one line. At an 800px viewport it gets 674 and fits; at 700 it gets 574 and
   the chips wrap -- by a different amount per row, so the three rows stood at 49,
   77 and 85px. Stack below 780 rather than at 640.

   Grid, not wrapped flex: .hpick contains a full-width email, so as a flex item
   it claims the whole row and pushes the "+3" onto a line of its own. */
@media (max-width:780px){
  /* At 375 the stage is 333px wide. Owners AND the hire's list in one frame is
     510 units, which puts the role labels back at 7px -- the thing the camera
     exists to avoid. So the list waits for the push-in that the day-one beat
     does, and John loses his job line, which the pod label repeats anyway. */
  #stage #jlist{opacity:0;transition:opacity var(--dur-slow) var(--ease-out)}
  #stage.listing #jlist{opacity:1}
  #stage #p0 .job{display:none}

  .hrow{display:grid;grid-template-columns:minmax(0,1fr) auto;
    column-gap:10px;row-gap:7px;align-items:center}
  .hpick{grid-area:1/1;flex-wrap:wrap}
  .hnm{flex:1 1 auto}
  /* min-height so the unselected "Other" row, whose email is empty until you name
     someone, still reserves the line and matches the three above it. */
  .hem{flex:0 0 100%;padding-left:35px;min-height:1.55em}
  .hct{grid-area:1/2}
  .jobs{grid-area:2/1/auto/-1;margin-left:0}
}

/* ---------- attract mode (data-attract; see demo.js) ---------- */
.demo-hints{display:flex;flex-wrap:wrap;gap:4px 24px;margin:0;padding:12px 22px;
  list-style:none;counter-reset:hint;background:var(--bg-tint);
  border-bottom:1px solid var(--border);font-size:.8125rem;font-weight:600;color:var(--accent-tx)}
.demo-hints li{counter-increment:hint;display:flex;align-items:center;gap:8px}
.demo-hints li::before{content:counter(hint);display:grid;place-items:center;width:20px;height:20px;
  border-radius:50%;background:var(--accent);color:var(--on-accent);font-size:.6875rem}
.stage{position:relative}
/* The drawn pointer. Travel is narrative motion across the card, so it takes
   --dur-travel, the one token allowed past the 500ms band. */
.attract-ptr{position:absolute;left:0;top:0;z-index:6;pointer-events:none;opacity:0;
  transition:transform var(--dur-travel) var(--ease-std),opacity var(--dur-base) var(--ease-out)}
.attract-ptr.on{opacity:1}
.attract-ptr svg{width:22px;height:22px;display:block;fill:var(--text);stroke:var(--surface);
  stroke-width:1.5;stroke-linejoin:round;transition:transform var(--dur-quick) var(--ease-std)}
.attract-ptr.press svg{transform:scale(.8)}
.attract-tip{position:absolute;left:18px;top:22px;white-space:nowrap;padding:4px 12px;
  border-radius:var(--radius-sm);background:var(--panel-inv);color:var(--panel-inv-tx);
  font-size:.8125rem;font-weight:600;box-shadow:var(--shadow-md)}
.attract-ptr.flip .attract-tip{left:auto;right:4px}
/* Five pulses on the narrative duration, then still: a held state, not a loop that never ends. */
.btn.attract-pulse{animation:attract-pulse var(--dur-travel) var(--ease-out) 5}
@keyframes attract-pulse{0%{box-shadow:0 0 0 0 var(--accent-dim)}100%{box-shadow:0 0 0 12px transparent}}
