/* ==========================================================================
   Joy · DBS voice-first payments
   Design system: the official DBS brand palette.

     DBS Red    #EC1C24   (primary action, accents, brand marks)
     DBS Black  #111111   (headers, dark surfaces, never flat #000000)
     Canvas     #F4F5F7   (app background)
     Hairline   #E5E7EB   (1px borders instead of coloured glows)

   Elevation rule: surfaces separate with a hairline border plus a micro-shadow
   (--sh-1 / --sh-2). There are no blurry red glow shadows anywhere, those read
   as a prototype. Icons are inline SVG (see icons.js), never OS emoji.
   ========================================================================== */
:root{
  /* ---- brand: DBS Red ---- */
  --brand:#EC1C24;
  --brand-dk:#D91820;
  /* the muted red the idle mic uses: brand red at ~65% over white. Reads as an affordance
     rather than an alarm, and leaves full-strength red for "the mic is actually open". */
  --brand-soft:#F26B71;
  --brand-2:#F4545A;
  --brand-3:#FBC9CB;
  --brand-tint:#FEF2F3;
  --brand-tint2:#FDE3E5;
  --brand-line:#F6C7CA;
  /* text-safe derivations of the brand for small type on white */
  --brand-ink:#B0141B;
  --brand-ink2:#7E0E13;

  /* ---- neutrals ---- */
  --ink:#111111;
  --ink2:#3F3F46;
  --muted:#71717A;
  --faint:#A1A1AA;
  --line:#E5E7EB;
  --bg:#F4F5F7;
  --card:#FFFFFF;

  /* ---- semantic ---- */
  --ok:#0E9F6E;
  --ok-bg:#E7F7F0;
  --warn:#B45309;
  --warn-bg:#FFF7E6;
  --stop:#C02626;
  --stop-bg:#FDECEC;
  --info:#1D4ED8;
  --info-bg:#EEF4FF;

  /* ---- elevation: hairlines + micro-shadows, never coloured glows ---- */
  --sh-1:0 1px 2px rgba(17,17,17,.04), 0 1px 3px rgba(17,17,17,.06);
  --sh-2:0 2px 8px rgba(17,17,17,.06);
  --sh-3:0 10px 28px rgba(17,17,17,.12);
  --sh-red:0 2px 8px rgba(236,28,36,.22);

  /* ---- dark studio (the engine room around the phone) ---- */
  --studio:#0F0F11;
  --studio2:#1A1A1E;
  --studio-line:#2E2E36;

  /* ---- DBS app surface ---- */
  --dbs-red:#EC1C24;
  --dbs-red-dk:#D91820;
  --dbs-black:#111111;
  --dbs-ink:#111111;
  --dbs-gray:#F4F5F7;
  --dbs-line:#E5E7EB;

  /* ---- device mockup ---- */
  --device-w:392px;
  --device-bezel:#2b2b30;
  --device-pad:7px;
  --device-r:54px;
  --screen-r:47px;
  --statusbar-h:48px;

  /* ---- layout rhythm: scales down automatically via clamp() on small screens ---- */
  --gutter:clamp(14px, 3.5vw, 28px);
  --radius:16px;
  --font:'Inter',system-ui,-apple-system,'SF Pro Text','Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{
  font-family:var(--font);
  -webkit-font-smoothing:antialiased;
  background:var(--studio);color:var(--ink);min-height:100vh;
  padding:0 var(--gutter);
  overflow-x:hidden;
}
.wrap{max-width:1460px;margin:0 auto}
/* The studio is bound to the VIEWPORT HEIGHT, not to its content. The top bar and the
   footer take what they need, the stage takes the rest, and the four inspector panels
   split that leftover height into equal halves (2x2). Nothing on the page scrolls, so all
   four panels are visible at once during a demo; each panel scrolls its OWN contents. */
#studio{display:flex;flex-direction:column;height:100vh;height:100dvh;
  padding-bottom:clamp(10px,1.6vw,18px)}
img{max-width:100%}
button{font-family:inherit}
[hidden]{display:none !important}

/* ==========================================================================
   SVG ICONS (icons.js)
   One base size; every icon inherits currentColor, so colour is set by the
   component that contains it and never by an inline style.
   ========================================================================== */
.ic{width:18px;height:18px;flex-shrink:0;display:block}
.ic-sm{width:14px;height:14px}
.ic-lg{width:22px;height:22px}
/* inline form, for an icon that sits inside a line of text (e.g. a chat status line) */
.bub .ic{display:inline-block;vertical-align:-2px;width:13px;height:13px;margin-right:5px}

/* ==========================================================================
   DBS logo lockup
   The supplied asset is 1280×382 (aspect 3.351) with a transparent background.
   `dbs-logo-white.png` is the same lockup with only the dark wordmark knocked
   out to white, for the black headers; the red mark is untouched in both.
   `width:auto` + `object-fit:contain` are both load-bearing: inside a column
   flex container the default `align-items:stretch` will happily widen a
   replaced element to the full row and squash the lockup. Anything that puts
   the logo in a column flex box must also set `align-items`/`align-self`.
   ========================================================================== */
.dbslogo{display:block;height:26px;width:auto;object-fit:contain;flex-shrink:0}
.dbslogo.sm{height:19px}
.dbslogo.lg{height:38px}
.dbsmark-img{display:block;height:30px;width:auto}

/* ==========================================================================
   SCREEN SWITCHER, one DBS app screen at a time, inside the device
   ========================================================================== */
.scr{display:none;position:absolute;inset:0;flex-direction:column;overflow:hidden}
.scr.on{display:flex}

/* ==========================================================================
   STAGE, device on the left, technical inspector on the right
   ========================================================================== */
.stage{display:flex;gap:var(--gutter);align-items:stretch;flex:1 1 auto;min-height:0}
/* HERO PAIR: Joy beside the phone, centred as one group and BOTTOM-aligned, so she reads as
   standing next to the device rather than floating above it.
   `flex:0 0 auto` is load-bearing: the pair must stay at its natural width and leave the
   inspector to take every remaining pixel. Do NOT centre it with auto margins here - auto
   margins on a flex item absorb the free space that `flex:1` on .ins needs, which collapses
   the inspector to its content width. Centring is done on the STAGE instead, and only while
   the inspector is hidden (see #studio.appmode below). */
.stage .heropair{display:flex;align-items:flex-end;justify-content:center;
  gap:clamp(8px,1.3vw,22px);flex:0 0 auto;min-width:0}
/* On the login and digibank-home screens the inspector is display:none, so the stage's only
   flex item is the pair and this centres it. On the Joy chat screen .ins has flex:1 and eats
   all the free space, which makes this rule a no-op. One rule, both cases. */
#studio.appmode .stage{justify-content:center}
/* Joy herself. Hidden on the chat screen (see #studio.appmode below) because that is where the
   inspector needs the width and the phone needs the height.
   `max-height` is a belt-and-braces cap, not a duplicate of the height: if the hero pair's height
   ever resolves to `auto`, the percentage inside `min()` becomes invalid and `height` falls back
   to the image's own 720px intrinsic size. The cap stops that from silently making Joy taller
   than the phone she is standing next to. */
.joymascot{display:none;flex-shrink:0;height:min(52%,360px);max-height:360px;width:auto;
  filter:drop-shadow(0 18px 30px rgba(0,0,0,.55))}
.joymascot img{display:block;height:100%;width:auto;object-fit:contain}
#studio.appmode .joymascot{display:block}
.stage .ins{flex:1;min-width:0;min-height:0}

/* ==========================================================================
   DEVICE MOCKUP
   A dummy phone bezel so the DBS app reads as an app on a phone, not as a
   web page. The status bar and dynamic island are drawn over whichever screen
   is showing, which is why every screen's header is black and reserves
   --statusbar-h of top padding.
   ========================================================================== */
.device{
  position:relative;flex-shrink:0;
  /* --phone-zoom shrinks the UI INSIDE the phone without changing the phone itself: the
     bezel/frame keeps its size, the screen contents render --phone-zoom smaller. 1 = full
     size. Tune this ONE value to taste.
     Raised .88 -> 1 on 8 Oct: the phone read small on a projector. The frame also grew at the
     same time (the top bar lost a whole row of badges), so the logical layout width the screen
     contents see barely moved (432px -> ~418px) while every element inside renders 13.6%
     larger. Changing this number alone would re-flow the chat; changing it together with the
     frame keeps the proportions the design was drawn at. */
  --phone-zoom:1;
  /* Height-driven by the STAGE (which fills the leftover viewport height), with the aspect
     ratio locked, so the phone keeps phone proportions instead of stretching to fill a
     column. Width is derived; max-width is only a guard for very narrow screens. */
  height:100%;
  aspect-ratio:1 / 2.05;
  width:auto;max-width:92vw;
  background:#0b0b0d;
  border:6px solid var(--device-bezel);
  border-radius:var(--device-r);
  padding:var(--device-pad);
  overflow:hidden;
  box-shadow:
    0 0 0 1px #55555e,
    0 0 0 2px rgba(0,0,0,.5),
    0 26px 60px rgba(0,0,0,.6),
    inset 0 0 0 2px #46464f;
}
/* side buttons, subtle, but they sell the hardware */
.device::before,
.device::after{
  content:'';position:absolute;width:3px;border-radius:2px;background:#3a3a42;z-index:1;
}
.device::before{left:-9px;top:150px;height:62px;box-shadow:0 -78px 0 #3a3a42, 0 84px 0 #3a3a42}
.device::after{right:-9px;top:196px;height:96px}

.device-screen{
  /* Laid out at 1/--phone-zoom of the frame and then scaled back down by --phone-zoom, so
     every element inside (chat, composer, status bar, popups) renders smaller while the
     bezel is untouched. The radius is divided by the same factor so the corners stay put. */
  position:relative;
  width:calc(100% / var(--phone-zoom));
  height:calc(100% / var(--phone-zoom));
  border-radius:calc(var(--screen-r) / var(--phone-zoom));
  overflow:hidden;background:#fff;
  display:flex;flex-direction:column;
  transform:scale(var(--phone-zoom));
  transform-origin:top left;
}

/* dynamic island */
.island{
  position:absolute;top:12px;left:50%;transform:translateX(-50%);
  width:104px;height:28px;border-radius:16px;background:#000;z-index:9;
  pointer-events:none;
}
/* mock iOS status bar, white, because every screen header is black */
.statusbar{
  position:absolute;top:0;left:0;right:0;height:var(--statusbar-h);z-index:8;
  display:flex;align-items:center;justify-content:space-between;
  padding:0 30px;pointer-events:none;
  color:#fff;font-size:12.5px;font-weight:700;letter-spacing:.2px;
}
.sb-time{font-variant-numeric:tabular-nums}
.sb-right{display:flex;align-items:center;gap:6px}
.sb-sig{display:flex;align-items:flex-end;gap:1.5px;height:11px}
.sb-sig i{width:3px;background:#fff;border-radius:1px;display:block}
.sb-sig i:nth-child(1){height:4px}
.sb-sig i:nth-child(2){height:6px}
.sb-sig i:nth-child(3){height:8.5px}
.sb-sig i:nth-child(4){height:11px}
.sb-bat{position:relative;width:22px;height:11px;border:1.5px solid rgba(255,255,255,.65);
  border-radius:3px;display:block}
.sb-bat::after{content:'';position:absolute;right:-3.5px;top:2.5px;width:2px;height:5px;
  background:rgba(255,255,255,.65);border-radius:0 1px 1px 0}
.sb-bat b{position:absolute;left:1.5px;top:1.5px;bottom:1.5px;width:13px;background:#fff;
  border-radius:1.5px;display:block}

/* ==========================================================================
   1 · DBS digibank LOG IN  (the User ID / PIN form is the screen)
   The launchpad (hero photo + six tiles) and the form's back arrow were removed on
   2 Oct. The form is no longer an overlay on top of anything, so it needs no
   absolute positioning and no z-index.
   ========================================================================== */
#scrLogin{background:#000;color:#fff}
.lgwrap{flex:1;display:flex;flex-direction:column;background:#000;color:#fff;
  overflow:hidden;min-height:0;position:relative}

.lgform{
  flex:1;min-height:0;position:relative;background:#000;color:#fff;
  padding:calc(var(--statusbar-h) + 14px) 22px 24px;overflow-y:auto;
}
.lgform-head{padding:8px 0 22px;display:flex;align-items:flex-start}
.lgform-head .dbslogo{height:28px}

.lgf{display:block;margin-bottom:10px;position:relative}
/* The wrapper exists so the trailing validity tick can anchor to the input box itself
   rather than to the whole field, which would drift if the field ever gains a label. */
.lgf .lgin{position:relative;display:block}
.lgf input{
  width:100%;border:1px solid #2a2a32;border-radius:8px;
  padding:14px 14px;font-size:14.5px;font-family:inherit;color:#fff;
  background:#0c0c10;outline:0;transition:.15s;
}
.lgf input::placeholder{color:#7a7a86}
.lgf input:focus{border-color:#3a3a45;background:#0e0e13}
/* trailing validity tick, only shown once the User ID actually matches a real
   Singapore NRIC shape, so the cue carries information instead of always being on */
.lgf .lgok{
  position:absolute;right:12px;top:50%;transform:translateY(-50%);
  width:17px;height:17px;border-radius:50%;background:var(--ok);color:#fff;
  display:flex;align-items:center;justify-content:center;
  opacity:0;transition:opacity .18s;pointer-events:none;
}
.lgf .lgok.on{opacity:1}
.lgf .lgok .ic{width:11px;height:11px}

.lgforgot{font-size:12px;color:#7a7a86;margin:6px 0 22px;text-align:left}
.lgforgot a{color:#7a7a86;text-decoration:underline}
.lgforgot a:hover{color:#fff}

.lgrow{display:flex;gap:10px;margin-bottom:22px}
/* .lgform .lgbtn is deliberately more specific than the shared .btn-dbs, so the
   login button takes the real digibank proportions (48px, squarer radius)
   instead of the taller rounded shape the shared button carries. */
.lgform .lgbtn{
  flex:1;height:48px;margin-top:0;font-size:14px;letter-spacing:.8px;
  font-weight:800;border-radius:8px;
}
.lgface{
  width:48px;height:48px;background:#000;border:1.5px solid #2a2a32;color:#fff;
  border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;
  flex-shrink:0;
}
.lgface:hover{background:#0c0c10;border-color:var(--dbs-red)}
.lgface .ic{width:22px;height:22px}

.lgsep{display:flex;align-items:center;gap:10px;color:#6a6a76;font-size:12px;margin:18px 0 16px}
.lgsep::before,.lgsep::after{content:'';flex:1;height:1px;background:#1c1c22}
.lgstarted{
  width:100%;height:48px;border:1.5px solid #3a3a45;background:transparent;color:#fff;
  border-radius:8px;font-size:13.5px;font-weight:800;letter-spacing:.6px;cursor:pointer;
  font-family:inherit;
}
.lgstarted:hover{background:#0a0a0e}
.lgnote{font-size:10.5px;color:#5a5a64;line-height:1.55;margin-top:18px;text-align:center}

/* shared DBS primary button, kept above .lgform .lgbtn so the login button can
   narrow it without a specificity fight */
.btn-dbs{
  width:100%;border:0;border-radius:12px;padding:14px;margin-top:5px;
  background:var(--dbs-red);color:#fff;font-size:15px;font-weight:800;
  cursor:pointer;transition:.15s;box-shadow:var(--sh-red);
}
.btn-dbs:hover{background:var(--dbs-red-dk)}
.btn-dbs:active{transform:scale(.985)}

.lgfoot{font-size:10px;color:#3a3a44;text-align:center;padding:18px 20px 20px;line-height:1.6}

/* ==========================================================================
   2 · DBS APP WINDOW  (home + Pay & Transfer)
   ========================================================================== */
#appwin{background:var(--dbs-gray)}
.appbar{
  background:var(--dbs-black);display:flex;align-items:center;gap:10px;
  padding:calc(var(--statusbar-h) + 6px) 16px 12px;flex-shrink:0;
}
.appbar-t{color:#fff;font-size:12.5px;font-weight:700;letter-spacing:.2px}
.appbar-x{
  margin-left:auto;display:flex;align-items:center;gap:5px;
  background:transparent;border:1px solid #3A3A42;color:#D6D6DE;
  border-radius:999px;padding:5px 11px;font-size:11px;font-weight:700;cursor:pointer;transition:.14s;
}
.appbar-x .ic{width:13px;height:13px}
.appbar-x:hover{border-color:var(--dbs-red);color:#fff}
/* No side padding: every home card carries its own 14px side margin, so the red hero can run
   edge-to-edge the way it does in the real app. The BOTTOM padding is the important bit - it
   reserves the height of the 5-tab bar so the last card is never hidden behind it. */
.appbody{
  flex:1;overflow-y:auto;background:var(--dbs-gray);
  padding:0 0 calc(env(safe-area-inset-bottom, 0px) + 70px);min-height:0;
  scroll-behavior:smooth;
}
/* ---- red hero, built from type + the supplied cut-out ----
   Flat DBS red rather than a baked-in hero JPG, so the copy can never drift out of
   alignment with a bitmap and the wording stays editable. */
.hero{
  background:#9C1823;
  display:flex;align-items:flex-end;justify-content:space-between;
  gap:8px;padding:16px 14px 14px;position:relative;overflow:hidden;
}
.hero-copy{max-width:60%;position:relative;z-index:2}
.hero-t{font-size:17px;line-height:1.22;font-weight:800;color:#fff;letter-spacing:-.2px}
.hero-s{font-size:11.5px;line-height:1.32;font-weight:600;color:#fff;opacity:.92;margin-top:5px}
.hero-cutout{
  display:block;height:96px;width:auto;flex-shrink:0;position:relative;z-index:1;
  margin:0 -6px -14px 0;pointer-events:none;
}
@media (max-width:380px){
  .hero-t{font-size:15.5px}
  .hero-s{font-size:11px}
  .hero-cutout{height:80px}
}

/* ---- quick-action icon tiles (5 in a row) ---- */
.qrow{display:flex;justify-content:space-between;gap:6px;padding:14px 14px 6px;background:var(--dbs-gray)}
.qbtn{
  flex:1;min-width:0;background:transparent;border:0;padding:4px 0 8px;
  display:flex;flex-direction:column;align-items:center;gap:4px;cursor:pointer;
  font-size:10.5px;color:#2a2a32;font-weight:600;line-height:1.25;font-family:inherit;
}
.qimg{width:22px;height:22px;object-fit:contain;display:block;transition:.14s}
.qbtn:hover .qimg{transform:scale(1.05)}

/* ---- "Your accounts" / "Discover more" headers ---- */
.ahdr{font-size:14px;font-weight:800;color:#2a2a32;padding:14px 16px 8px}

/* ---- the shared white card language ---- */
.card{
  background:#fff;border:1px solid var(--dbs-line);border-radius:14px;padding:14px 16px;
  margin:0 14px 10px;display:flex;align-items:center;
}
/* deposits card */
.dep{flex-direction:row;justify-content:space-between;padding:18px 18px;align-items:flex-end}
.dep-l{font-size:16px;font-weight:800;color:#2a2a32}
.dep-t{font-size:16px;font-weight:800;color:#2a2a32}
.dep-acc{font-size:11px;color:#888;font-weight:600;margin-top:5px}
.dep-r{text-align:right}
.dep-k{font-size:11px;color:#888}
.dep-v{font-size:18px;font-weight:800;color:#2a2a32;margin-top:3px;letter-spacing:-.3px}

/* ---- Deposit promo card: same card language, pink-tinted "$" bubble ---- */
.promo{
  display:flex;align-items:center;gap:14px;padding:14px 16px;
  background:#fff;cursor:pointer;font:inherit;text-align:left;width:auto;
}
.promo-l{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}
.promo-t{font-size:14px;font-weight:800;color:#2a2a32}
.promo-d{font-size:11.5px;color:#666;line-height:1.4}
.promo-link{font-size:12px;color:var(--dbs-red);font-weight:700;margin-top:4px}
.promo-r{flex-shrink:0;width:64px;height:64px;border-radius:50%;
  background:linear-gradient(135deg,#ffe2e2 0%,#ffd4d4 100%);
  display:flex;align-items:center;justify-content:center;overflow:hidden}
.promo-bubble{font-size:30px;font-weight:900;color:#ff7a7a;line-height:1;
  text-shadow:0 1px 0 rgba(255,255,255,.6)}
.promo:hover{border-color:#f3c9c9;box-shadow:0 4px 14px rgba(255,51,51,.10)}

/* ---- Discover more ---- */
.row2{display:flex;gap:10px;padding:0 14px 10px}
.row2 .card{margin:0;flex:1}
.hero2{display:block;padding:14px 16px 16px}
.hero2-t{font-size:13px;font-weight:800;color:#2a2a32;margin-bottom:5px}
.hero2-d{font-size:11.5px;color:#666;line-height:1.45}
.hero2-link{font-size:12px;color:var(--dbs-red);font-weight:700;margin-top:10px}

/* ---- "Ask Joy to pay" CTA (big red, DBS marketing style) ----
   Joy's ONE entry point on the home screen. Sits directly under the quick-action row and
   above "Your accounts", so the eye order is shortcuts -> Joy -> accounts. */
.joy-cta{
  display:flex;align-items:center;gap:14px;width:auto;
  margin:6px 14px 4px;padding:16px 16px;border:0;border-radius:16px;
  background:linear-gradient(135deg,#ff3333 0%,#b8181b 100%);color:#fff;text-align:left;cursor:pointer;
  box-shadow:0 12px 26px rgba(255,51,51,.30);transition:.14s;
}
.joy-cta:hover{filter:brightness(1.06);transform:translateY(-2px)}
.joy-av{width:54px;height:54px;border-radius:50%;background:rgba(255,255,255,.95);padding:2px;
  object-fit:cover;object-position:50% 14%;flex-shrink:0}
.joy-cta-t{flex:1;min-width:0}
.joy-cta-b{display:inline-block;font-size:9px;font-weight:800;letter-spacing:.7px;
  background:rgba(0,0,0,.30);padding:3px 8px;border-radius:20px;margin-bottom:5px}
.joy-cta-h{font-size:15px;font-weight:900;letter-spacing:-.3px}
.joy-cta-d{font-size:10.5px;color:rgba(255,255,255,.92);line-height:1.45;margin-top:2px}
.joy-cta-arrow{font-size:22px;flex-shrink:0;opacity:.85}

/* ---- BOTTOM TAB NAV (5 items, dark, real digibank style) ----
   Pinned to the bottom of #appwin. Active tab is `.on`. */
.tabnav{
  position:absolute;left:0;right:0;bottom:0;
  display:flex;align-items:stretch;justify-content:space-around;
  background:#1d1d22;border-top:1px solid #0c0c10;
  padding:6px 0 calc(6px + env(safe-area-inset-bottom, 0px));
  z-index:5;
}
.tab{
  flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;
  background:transparent;border:0;padding:6px 0 4px;color:#7a7a86;cursor:pointer;
  font-family:inherit;font-size:10.5px;font-weight:600;line-height:1.1;
  /* outline:0 kills the browser's blue focus ring on click. */
  outline:0;
  transition:color .14s;
}
.tab img{width:24px;height:24px;display:block;opacity:.85}
/* Active tab = brighter icon + label (no background fill - a pill here made the
   "Pay & Transfer" label wrap onto two lines and looked bulky). */
.tab.on{color:#fff}
.tab.on img{opacity:1;filter:none}

/* section heading (Pay & Transfer). Inset now that .appbody carries no side padding;
   a slightly larger top margin so the heading doesn't sit flush against the dark appbar. */
.appsec{
  font-size:10.5px;font-weight:800;letter-spacing:1px;text-transform:uppercase;
  color:var(--muted);margin:24px 0 12px;padding:0 14px;
}

/* ---- Pay & Transfer (hero + elevated cards + recent payees) ----
   Joy gets her own featured card, the manual routes are individual rounded cards, the
   empty space below is a Recent Payees shortcut row, and the safety disclaimer sits at
   the bottom. (.applist/.approw/.pnote - the old flat list - were removed with the
   redesign; don't reintroduce them.) */

/* Hero: "Pay with Joy". Light blue so the flagship AI feature reads apart from the
   utility cards below. */
.payhero{
  background:#EAF4FF;
  border-radius:16px;
  margin:0 14px 10px;
  padding:16px 14px 14px;
}
.payhero-top{display:flex;align-items:center;gap:12px;margin-bottom:14px}
.payhero-av{
  width:56px;height:56px;border-radius:50%;
  background:#fff;padding:3px;flex-shrink:0;
  box-shadow:0 2px 8px rgba(0,0,0,.10);
}
.payhero-av img{width:100%;height:100%;border-radius:50%;object-fit:cover;object-position:50% 14%}
.payhero-meta{min-width:0;flex:1}
.payhero-t{font-size:15px;font-weight:800;color:#111;letter-spacing:-.2px;line-height:1.25}
.payhero-tag{
  display:inline-block;font-size:9px;font-weight:700;
  color:var(--dbs-red);letter-spacing:.5px;text-transform:uppercase;
  background:#fff;border:1px solid var(--dbs-red);
  padding:1px 5px;border-radius:5px;margin-left:5px;vertical-align:2px;
}
.payhero-d{font-size:11.5px;color:#3a3a3a;margin-top:3px;line-height:1.4}
.payhero-btn{
  width:100%;background:var(--dbs-red);color:#fff;border:0;
  border-radius:10px;padding:11px;font-size:13.5px;font-weight:800;
  cursor:pointer;font-family:inherit;
}
.payhero-btn:hover{filter:brightness(1.06)}

/* Manual options: individual elevated rounded cards (was a single flat list). */
.paycards{margin:0 14px 10px;display:flex;flex-direction:column;gap:8px}
.paycard{
  display:flex;align-items:center;gap:12px;
  background:#fff;border:1px solid var(--dbs-line);border-radius:12px;
  padding:13px 14px;width:100%;text-align:left;cursor:pointer;
  font-family:inherit;box-shadow:0 1px 3px rgba(0,0,0,.04);
}
.paycard-ic{
  width:36px;height:36px;border-radius:11px;background:var(--dbs-gray);
  display:flex;align-items:center;justify-content:center;
  flex-shrink:0;color:#3a3a3a;
}
.paycard-ic .ic{width:18px;height:18px}
.paycard-meta{flex:1;min-width:0}
.paycard-t{font-size:13px;font-weight:700;color:var(--dbs-ink)}
.paycard-d{font-size:11px;color:var(--muted);margin-top:2px}
.paycard-chev{color:var(--faint);flex-shrink:0}
.paycard-chev .ic{width:16px;height:16px}

/* Recent Payees: 1-tap shortcuts. Initial avatars in the brand tint. */
.recent{
  margin:0 14px 10px;
  background:#fff;border:1px solid var(--dbs-line);border-radius:12px;
  padding:14px;box-shadow:0 1px 3px rgba(0,0,0,.04);
}
.recent-h{font-size:13px;font-weight:800;color:var(--dbs-ink);margin-bottom:12px}
.recent-row{display:flex;gap:14px;overflow-x:auto;padding-bottom:2px}
.recent-av{
  flex:0 0 auto;width:52px;height:52px;border-radius:50%;
  background:var(--brand-tint);border:1px solid var(--brand-line);
  display:flex;align-items:center;justify-content:center;
  cursor:pointer;font-family:inherit;color:var(--brand-ink);
}
.recent-av:hover{filter:brightness(.97)}
.recent-init{font-size:13px;font-weight:800;color:var(--brand-ink)}
.recent-add{background:#fff;border:1px dashed var(--dbs-line);color:#999}
.recent-add .ic{width:20px;height:20px}

/* Bottom safety disclaimer (.paynote) was removed on 2 Oct - it duplicated what the
   Home screen already says and the Pay list now ends with the Recent payees row. */

/* ==========================================================================
   3 · JOY CHAT  (inside the device)
   ========================================================================== */
/* ONE consolidated DARK app bar, mirroring the real digibank / iggy chat header: near-black
   surface with a 3px DBS-red underline. Back control on the left, identity + trust state
   centred. Deliberately NO avatar, the assistant introduces itself in the message card and
   appears only in popups, so Joy is never shown twice on this screen. `.aback` is absolutely
   positioned so the title centres on the SCREEN rather than on the space beside the button. */
.abar{
  position:relative;flex-shrink:0;background:var(--dbs-black);
  border-bottom:3px solid var(--dbs-red);
  padding:calc(var(--statusbar-h) + 4px) 14px 11px;
  text-align:center;
}
.aback{
  position:absolute;left:8px;top:calc(var(--statusbar-h) - 3px);
  display:inline-flex;align-items:center;height:32px;padding:0 7px 0 2px;
  background:none;border:0;border-radius:9px;cursor:pointer;transition:background .14s;
  color:#fff;font-family:inherit;font-size:13.5px;font-weight:600;letter-spacing:-.1px;
}
.aback:hover{background:rgba(255,255,255,.12)}
.aback .ic{width:17px;height:17px}
.ameta{min-width:0}
.abar .nm{color:#fff;font-size:14.5px;font-weight:800;line-height:1.25;letter-spacing:-.2px}
.abar .sub{
  color:rgba(255,255,255,.72);font-size:11px;font-weight:600;margin-top:3px;
  display:flex;align-items:center;justify-content:center;gap:5px;
}
.safedot{width:5px;height:5px;border-radius:50%;background:#12A150;display:inline-block;flex-shrink:0}

/* The Joy screen is the only LIGHT header in the app, so the mock status bar has to
   flip to dark ink or it disappears against the page. showScreen() sets .light-bar. */
.device-screen.light-bar .statusbar{color:var(--ink)}
.device-screen.light-bar .sb-sig i{background:var(--ink)}
.device-screen.light-bar .sb-bat{border-color:rgba(17,17,17,.55)}
.device-screen.light-bar .sb-bat::after{background:rgba(17,17,17,.55)}
.device-screen.light-bar .sb-bat b{background:var(--ink)}

.chat{flex:1;overflow-y:auto;padding:12px;background:var(--bg);display:flex;flex-direction:column;
  gap:9px;scroll-behavior:smooth;min-height:0}
.chat::-webkit-scrollbar{width:6px}.chat::-webkit-scrollbar-thumb{background:#D5D5DD;border-radius:3px}

/* --------------------------------------------------------------------------
   LANGUAGE PILLS + VOICE-FIRST COMPOSER.
   The three language pills are CENTRED above the composer (the language is the first
   choice a voice-first customer makes; the selected one is the only dark chip). The
   composer is one white pill: keyboard toggle LEFT, the MIC dominant in the CENTRE,
   a matching spacer RIGHT so the mic stays optically centred. The text field is hidden
   until the customer taps the keyboard, so the default surface is speak-first.
   -------------------------------------------------------------------------- */
.langtog{display:flex;gap:6px;padding:8px 12px 0;flex-shrink:0;background:transparent;
  justify-content:center}
.lt{background:#fff;border:1px solid var(--line);color:var(--ink2);border-radius:20px;
  padding:5px 13px;font-size:11.5px;font-weight:700;font-family:inherit;cursor:pointer;
  letter-spacing:.2px;transition:background .14s,color .14s,border-color .14s}
.lt:hover{border-color:#c8c9cf}
/* the selected language is the only dark chip on the page, an unambiguous "this one" */
.lt.on{background:var(--ink);color:#fff;border-color:var(--ink)}

.cinput{display:flex;gap:8px;align-items:center;justify-content:space-between;flex-shrink:0;
  margin:10px 12px 14px;background:#fff;border:1px solid var(--line);
  border-radius:999px;padding:7px 10px;box-shadow:0 6px 18px rgba(17,17,17,.10)}
/* the "type instead" affordance, quietly on the left */
.cinput .kbd{width:42px;height:42px;border-radius:50%;border:0;flex-shrink:0;cursor:pointer;
  background:transparent;color:var(--ink2);display:flex;align-items:center;justify-content:center;
  transition:background .14s,color .14s}
.cinput .kbd:hover{background:var(--bg);color:var(--ink)}
.cinput .kbd .ic{width:21px;height:21px}
/* The left toggle has TWO icons: a keyboard (voice) and a "hide keyboard" arrow (typing).
   Showing only the one that matches the current mode keeps the button in the same spot. */
.cinput:not(.typing) .ic-dismiss{display:none}
.cinput.typing .ic-type{display:none}
/* mirrors the keyboard toggle so the dominant mic sits dead-centre */
.cinput .cspacer{width:42px;height:42px;flex-shrink:0}
/* the dominant control: idle is the full DBS red, so "the mic is open" is the obvious CTA
   even before it is tapped; the listening ring confirms the state, not the colour. */
.cinput .mic{width:56px;height:56px;border-radius:50%;border:0;flex-shrink:0;cursor:pointer;
  background:var(--dbs-red);color:#fff;display:flex;align-items:center;justify-content:center;
  box-shadow:0 6px 16px rgba(236,28,36,.34);transition:background .14s,transform .1s}
.cinput .mic:hover{background:var(--dbs-red-dk)}
.cinput .mic:active{transform:scale(.96)}
.cinput .mic .ic{width:26px;height:26px}
.cinput .mic.on{background:var(--dbs-red);animation:micring 1.4s ease-out infinite}
@keyframes micring{
  0%{box-shadow:0 0 0 0 rgba(236,28,36,.35)}
  70%{box-shadow:0 0 0 12px rgba(236,28,36,0)}
  100%{box-shadow:0 0 0 0 rgba(236,28,36,0)}
}
/* voice mode hides the field; tapping the keyboard reveals it and the composer becomes an
   ordinary field + mic (the keyboard toggle and its spacer stand down). */
.cinput .fld{display:none;flex:1;min-width:0;background:var(--bg);border:0;border-radius:18px;
  padding:11px 15px;font-size:13px;font-family:inherit;color:var(--ink);outline:0;transition:.14s}
.cinput .fld::placeholder{color:var(--faint)}
.cinput .fld:focus{background:#fff;box-shadow:0 0 0 2px var(--brand-tint)}
.cinput.typing .cspacer{display:none}
.cinput.typing .fld{display:block}
.cinput.typing .mic{width:44px;height:44px;box-shadow:none}
.cinput.typing .mic .ic{width:19px;height:19px}

/* "Let me try again" pulls attention back to the input rather than reloading */
.cinput .fld.flash{animation:fldflash .5s ease 2;background:var(--brand-tint)}
@keyframes fldflash{
  0%,100%{box-shadow:0 0 0 0 rgba(236,28,36,0)}
  50%{box-shadow:0 0 0 4px rgba(236,28,36,.18)}
}

.row{display:flex;flex-direction:column;gap:4px;max-width:94%}
.row.me{align-self:flex-end;align-items:flex-end}
.row.them{align-self:flex-start}
/* Cards (the intro block, the draft, the receipt) are page furniture rather than speech,
   so they use the whole content width instead of the 94% speech-bubble cap. Must come
   AFTER .row.them to win the align-self tie. */
.row.full{max-width:none;align-self:stretch}
.lab{font-size:9px;font-weight:800;letter-spacing:.7px;padding:2px 7px;border-radius:5px;display:inline-block}
.lab.AUNTIE{background:var(--brand-tint2);color:var(--brand-ink2)}
.lab.ATTACKER{background:var(--stop-bg);color:var(--stop)}
/* bubble() writes the FULL label text as the class (label="JOY" → class="lab JOY"), so these
   must use that spelling. INTERFACE / IFACE are the legacy spellings, kept so a stale cached
   page or an external caller still renders correctly. */
.lab.JOY,.lab.INTERFACE,.lab.IFACE{background:var(--info-bg);color:var(--info)}
.lab.SYSTEM{background:#F0F0F4;color:#52525B}
.lab.UNDERSTOOD{background:var(--ok-bg);color:#047857}
.lab.YOU{background:var(--dbs-black);color:#fff}
.why{color:var(--muted);font-style:italic}
.bub{padding:10px 13px;border-radius:14px;font-size:13.5px;line-height:1.5}
.bub.you{background:var(--dbs-red);color:#fff;border-bottom-right-radius:5px;font-weight:600}
.bub.attacker{background:var(--stop);color:#fff;border-bottom-right-radius:5px;font-weight:600}
.bub.iface{background:#fff;border:1px solid var(--dbs-line);border-bottom-left-radius:5px;color:var(--ink2);box-shadow:var(--sh-1);white-space:pre-line}
.bub.sys{background:#fff;border:1px dashed var(--line);color:#52525B;font-size:11.5px;
  font-family:ui-monospace,Menlo,Consolas,monospace;line-height:1.55}

/* The two diagnostic panels fill the stage as a 1x2 grid: one full-height column each. It used
   to be 2x2; the other two panels were folded into these (see index.html), which is why the
   chain and the agent contracts are now roughly twice as tall as they were. */
.ins{display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr;gap:var(--gutter);
  min-width:0;min-height:0}

/* ==========================================================================
   in-chat INTRO card
   ========================================================================== */
.intro{background:#fff;border:1px solid var(--line);border-radius:16px;overflow:hidden;
  box-shadow:var(--sh-1)}
/* Identity row: a near-black band (like iggy's) carrying Joy's face, the name + role sub and
   the DBS lockup. The avatar is the ONE persistent place on the chat surface where Joy's
   picture appears, the chat app bar above deliberately has NO avatar, and the popups below
   are momentary, so Joy is never shown twice in the persistent chat chrome. */
.intro .ihead{display:flex;align-items:center;gap:11px;
  background:var(--dbs-black);padding:13px 14px}
.intro .iav{
  width:38px;height:38px;border-radius:50%;background:#fff;overflow:hidden;flex-shrink:0;
  display:flex;align-items:center;justify-content:center;box-shadow:0 0 0 2px rgba(255,255,255,.25);
}
.intro .iav img{width:100%;height:100%;object-fit:cover;object-position:50% 14%}
.intro .imeta{min-width:0;display:flex;flex-direction:column;gap:1px}
.intro .inm{color:#fff;font-size:14px;font-weight:800;line-height:1.2;letter-spacing:-.1px}
.intro .isub{color:rgba(255,255,255,.7);font-size:10.5px;font-weight:600}
.intro .ihead .dbslogo{margin-left:auto;height:22px;flex-shrink:0}
.intro .ibody{padding:13px 14px 14px}
.intro .ihi{font-size:15px;font-weight:800;color:var(--ink);margin-bottom:7px;letter-spacing:-.1px}
.intro .itxt{font-size:13px;color:var(--ink2);line-height:1.55}

/* sample commands: a section label plus example utterances, inside the card body */
.cmdlabel{font-size:10.5px;font-weight:800;letter-spacing:.9px;color:var(--faint);
  margin:18px 0 10px}
.chips{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.chip{background:var(--brand-tint);border:1px solid var(--brand-line);color:var(--brand-ink);
  border-radius:800px;padding:8px 12px;font-size:12px;font-weight:700;font-family:inherit;
  cursor:pointer;transition:.14s;text-align:center;min-width:0}
.chip:hover{background:var(--dbs-red);border-color:var(--dbs-red);color:#fff}

/* ==========================================================================
   INLINE DRAFT CARD / FACE ID CARD — "Ready to confirm"
   The light-red hairline is the whole signal: this is a PROPOSAL, not a payment.
   The Face ID variant uses the brand red so it reads as the biometric gate it is.
   ========================================================================== */
.dcard{background:#fff;border:1.5px solid var(--brand-line);border-radius:14px;padding:14px;
  box-shadow:var(--sh-1)}
.dcard .dhead{display:flex;align-items:center;gap:7px}
.dcard .dtick{color:#12A150;display:flex;flex-shrink:0}
.dcard .dtick .ic{width:17px;height:17px}
.dcard .dtitle{font-size:13.5px;font-weight:800;color:var(--ink);letter-spacing:-.1px}
.dcard .dtag{margin-left:auto;font-size:9.5px;font-weight:800;letter-spacing:.9px;color:var(--faint)}
.dcard .drows{margin-top:12px;display:flex;flex-direction:column;gap:9px}
.dcard .dr{display:flex;align-items:baseline;gap:10px;font-size:12.5px}
.dcard .dr .k{color:var(--muted);flex-shrink:0}
.dcard .dr .v{margin-left:auto;min-width:0;text-align:right;font-weight:700;color:var(--ink)}
.dcard .dr .v.amt{color:var(--brand);font-weight:800}
.dcard .dbtn{width:100%;margin-top:14px;border:0;border-radius:10px;padding:13px;
  background:var(--brand);color:#fff;font-family:inherit;font-size:13.5px;font-weight:800;
  letter-spacing:.1px;cursor:pointer;transition:background .15s}
.dcard .dbtn:hover{background:var(--brand-dk)}
/* once acted on, the button reports what happened instead of inviting a second tap */
.dcard .dbtn:disabled{cursor:default;background:var(--ok-bg);color:#047857}
/* Face ID variant: brand-red hairline + tick so the biometric gate is visually distinct.
   Class is "fid-ready" (not "faceid") to avoid colliding with the popup scan-frame class
   `.faceid{width:92px;height:92px;animation:fpulse…}` which is defined in this file but
   currently rendered by no element — using "faceid" here would force the card into a
   92×92 halo box. */
.dcard.fid-ready{border-color:var(--brand)}
.dcard.fid-ready .dtick{color:var(--brand)}
.dcard.fid-ready .dtag{color:var(--brand)}

/* ==========================================================================
   inline clarification card
   ========================================================================== */
.ccard{background:#fff;border:1px solid var(--brand-line);border-radius:16px;padding:14px 15px;
  box-shadow:var(--sh-2)}
.ccard .ckick{display:inline-block;font-size:9px;font-weight:800;letter-spacing:.9px;
  color:var(--brand-ink);background:var(--brand-tint2);padding:3px 8px;border-radius:6px}
.ccard .ctitle{font-size:14px;font-weight:800;color:var(--ink);margin:9px 0 5px;line-height:1.3}
.ccard .ctext{font-size:13px;color:var(--ink2);line-height:1.55}
.ccard .cbtn{margin-top:12px;width:100%;border:0;border-radius:11px;padding:12px 14px;
  font-size:13px;font-weight:800;cursor:pointer;
  display:flex;align-items:center;justify-content:center;gap:7px;
  background:var(--dbs-red);color:#fff;transition:.15s;box-shadow:var(--sh-red)}
.ccard .cbtn:hover{background:var(--dbs-red-dk)}
.ccard .cbtn .ic{width:15px;height:15px}
.ccard .cnote{font-size:10.5px;color:var(--faint);margin-top:9px;line-height:1.45}
.ccard.done{opacity:.62}

/* ==========================================================================
   DBS payment RECEIPT (rendered in-chat after a successful payment)
   ========================================================================== */
.rcpt{background:#fff;border:1px solid var(--line);border-radius:16px;overflow:hidden;
  box-shadow:var(--sh-2)}
.rcpt .rhead{background:var(--dbs-black);padding:12px 14px;display:flex;align-items:center;gap:10px;
  border-bottom:2px solid var(--dbs-red)}
.rcpt .rhead .dbslogo{height:17px}
.rcpt .rhead .rt{color:#fff;font-size:10.5px;font-weight:700;margin-left:auto;letter-spacing:.3px}
.rcpt .rok{padding:17px 14px 6px;text-align:center}
.rcpt .rtick{width:50px;height:50px;border-radius:50%;background:var(--ok);color:#fff;
  display:flex;align-items:center;justify-content:center;margin:0 auto 10px}
.rcpt .rtick .ic{width:26px;height:26px}
.rcpt .rstatus{font-size:15px;font-weight:800;color:var(--ink);letter-spacing:-.2px}
.rcpt .rsub{font-size:11.5px;color:var(--muted);margin-top:4px}
.rcpt .ramt{font-size:28px;font-weight:800;letter-spacing:-1px;color:var(--dbs-ink);text-align:center;
  margin:13px 0 2px;font-variant-numeric:tabular-nums}
.rcpt .rrows{margin:13px 14px 0;border-top:1px solid var(--line)}
.rcpt .rr{display:flex;justify-content:space-between;gap:12px;padding:10px 0;border-bottom:1px solid var(--line);font-size:12.5px}
.rcpt .rr .k{color:var(--muted);flex-shrink:0}
.rcpt .rr .v{font-weight:700;color:var(--ink);text-align:right;word-break:break-word}
.rcpt .rfoot{margin:12px 14px 14px;background:var(--dbs-gray);border-radius:11px;padding:10px 12px;
  font-size:10px;color:var(--muted);line-height:1.5}
/* Banking-grade trust signal: the receipt proves itself against the append-only audit log.
   pending (grey) → ok (green, hash chain intact) → bad (red, broken) → warn (amber, unverified). */
.rcpt .rverify{margin:0 14px 10px;display:flex;align-items:center;gap:8px;border-radius:11px;
  padding:9px 12px;font-size:10.5px;font-weight:700;line-height:1.4;
  background:#EAF7F0;border:1px solid #B7E4CC;color:#0F7A4F}
.rcpt .rverify[data-state="pending"]{background:#EEF0F3;border-color:#DDE0E5;color:#6B7280;font-weight:600}
.rcpt .rverify[data-state="bad"]{background:#FDECEC;border-color:#F5C2C2;color:#B42318}
.rcpt .rverify[data-state="warn"]{background:#FFF7E6;border-color:#F3D99B;color:#92600A}
/* "Save receipt", keeps the card, not just the moment. Full width so it is an obvious
   target on a phone, and it never looks like a second payment action: grey, not red. */
.rcpt .rsave{margin:0 14px 14px}
.rcpt .rsbtn{display:flex;align-items:center;justify-content:center;gap:7px;width:100%;
  padding:9px 12px;border:1px solid var(--line);border-radius:11px;background:var(--dbs-gray);
  color:var(--ink);font:inherit;font-size:12px;font-weight:700;cursor:pointer;
  -webkit-tap-highlight-color:transparent}
.rcpt .rsbtn .ic{width:16px;height:16px;flex-shrink:0}
.rcpt .rsbtn:hover{background:#EAEBEE}
.rcpt .rsbtn:disabled{opacity:.55;cursor:default}
.rcpt .rsbtn.done{border-color:var(--ok);color:var(--ok);background:#fff}

/* ==========================================================================
   inspector (the engine room)
   ========================================================================== */
.panel{background:var(--studio2);border:1px solid var(--studio-line);border-radius:var(--radius);padding:10px 12px;min-width:0;
  display:flex;flex-direction:column;min-height:0;overflow:hidden}
.panel>h3{flex:0 0 auto}
/* Type scale: this is read off a projector in a room, not a laptop at 40cm. Body text in the
   panels is 11-12px and headings 12px; the previous 8-10.5px was legible on a retina display
   and invisible on a projector, which is the only place this screen is ever seen. */
.panel h3{font-size:12px;font-weight:800;letter-spacing:1px;color:#A0A0AE;margin-bottom:8px;display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.panel h3 .cnt{margin-left:auto;font-size:10.5px;color:#8A8A98;font-weight:700}

/* ---- the SECOND section inside a panel ----
   Each panel now carries two related things: what was PLANNED and what was RECORDED (left), who
   PROPOSED and what was DECIDED (right). A hairline rule and a sub-heading keep them readable
   as two sections instead of one undifferentiated list.
   `flex:0 1 auto` with a max-height, deliberately NOT `flex:1`: the section is sized by its own
   content, so the primary list above it keeps the lion's share of the panel, while still being
   able to grow (up to 44%) when the operator expands the audit trail. */
.psplit{display:flex;flex-direction:column;flex:0 1 auto;min-height:0;max-height:44%;
  margin-top:9px;padding-top:9px;border-top:1px solid var(--studio-line)}
.psplit>h3{flex:0 0 auto;margin-bottom:7px}
.psplit .audit{flex:1 1 auto;min-height:0}

/* control chain */
.chain{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-height:0;overflow-y:auto;padding-right:3px}
.chain::-webkit-scrollbar{width:5px}.chain::-webkit-scrollbar-thumb{background:#43434F;border-radius:3px}
.stg{display:flex;gap:8px;align-items:flex-start;padding:3px 6px;border-radius:8px;transition:.2s}
.stg .no{font-size:10.5px;font-weight:800;color:#8A8A98;width:21px;flex-shrink:0;padding-top:1px;font-family:ui-monospace,monospace}
.stg .tx{font-size:12px;color:#B9B9C4;line-height:1.3;flex:1}
.stg .ty{font-size:10px;color:#8A8A98;letter-spacing:.3px;margin-top:0}
.stg .sd{width:8px;height:8px;border-radius:50%;background:#43434F;flex-shrink:0;margin-top:5px}
.stg.run{background:rgba(236,28,36,.15)}
.stg.run .sd{background:var(--brand);box-shadow:0 0 0 4px rgba(236,28,36,.22)}
.stg.run .tx{color:#fff}
.stg.pass .sd{background:#12A150}
.stg.pass .tx{color:#C8F0D8}
.stg.fail{background:rgba(192,38,38,.16)}
.stg.fail .sd{background:#E5484D;box-shadow:0 0 0 4px rgba(229,72,77,.2)}
.stg.fail .tx{color:#FFB4B4;font-weight:700}
.stg.skip{opacity:.34}

/* agent output */
.agents{display:flex;flex-direction:column;gap:7px;flex:1 1 auto;min-height:0;overflow-y:auto;padding-right:3px}
.agents::-webkit-scrollbar{width:5px}.agents::-webkit-scrollbar-thumb{background:#43434F;border-radius:3px}
.acard{background:#17171B;border:1px solid #33333C;border-radius:10px;padding:9px 11px}
.acard .ah{font-size:9.5px;font-weight:800;letter-spacing:.6px;color:var(--brand);margin-bottom:6px}
.acard pre{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:10.5px;line-height:1.6;color:#C9C9D4;white-space:pre-wrap;word-break:break-word}
/* JSON syntax highlighting for the raw payload. Tuned for the dark .acard surface,
   the original palette (#7c3aed / #0f766e / #b45309) was picked for a white card and
   is unreadable here. */
.acard .jk{color:#C4B5FD}
.acard .js{color:#5EEAD4}
.acard .jn2{color:#FCD34D}
.acard .jb{color:#93C5FD;font-weight:700}

/* ---- verdict cards: Guardrails Agent + Intent Classifier ----
   These two agents exist to reach a yes/no, so the panel leads with the verdict
   and the numbers behind it, and tucks the raw payload behind a disclosure. */
.acard.verdict{background:#17171B;border-color:#3D3D48}
/* "Thinking out loud": the five specialists show as reviewing… the moment a turn is sent,
   then each card locks in (settled) when its verdict lands. Idle = consulted but not needed. */
.acard.thinking{border-color:#2F6F4F;background:#10140F}
.acard.thinking .athink{display:flex;align-items:center;gap:7px;font-size:10.5px;color:#8FE0B0;font-style:italic}
.acard.thinking .pulse{width:8px;height:8px;border-radius:50%;background:#34D399;box-shadow:0 0 0 0 rgba(52,211,153,.6);animation:pulse 1.1s infinite}
@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(52,211,153,.55)}70%{box-shadow:0 0 0 7px rgba(52,211,153,0)}100%{box-shadow:0 0 0 0 rgba(52,211,153,0)}}
.acard.settled{animation:lockin .45s ease}
@keyframes lockin{0%{transform:translateY(4px);opacity:.4;border-color:#2F6F4F}100%{transform:none;opacity:1}}
.acard.idle{border-color:#2A2A31;opacity:.62}
.acard.idle .athink{font-size:10px;color:#6F6F7C;font-style:italic}
.vhead{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:10px}
.vbadge{font-size:10px;font-weight:900;letter-spacing:.7px;padding:4px 10px;border-radius:6px;border:1px solid}
.vbadge.ok{background:#12301F;color:#7FE0A8;border-color:#1F5C3A}
.vbadge.warn{background:#3A2A12;color:#FFCF87;border-color:#6B4A1C}
.vbadge.stop{background:#3A1414;color:#FF9B9B;border-color:#6B2424}
.vtag{font-size:8.5px;font-weight:800;letter-spacing:.5px;padding:3px 7px;border-radius:5px;
  background:#26262E;color:#8B8B98;border:1px solid #3A3A46;white-space:nowrap}
.vtag.ok{color:#7FE0A8;border-color:#1F5C3A}
.vtag.warn{color:#FFCF87;border-color:#6B4A1C}
.vtag.stop{color:#FF9B9B;border-color:#6B2424}
.veng{margin-left:auto;font-size:8.5px;font-weight:800;letter-spacing:.5px;color:#6F6F7C;
  border:1px solid #3A3A46;border-radius:5px;padding:2px 6px;white-space:nowrap}
.vr{display:flex;gap:10px;justify-content:space-between;padding:4px 0;font-size:10.5px;
  border-bottom:1px dashed #2B2B34}
.vr:last-of-type{border-bottom:0}
.vr .vk{color:#6F6F7C;flex-shrink:0}
.vr .vv{color:#DCDCE4;font-weight:700;text-align:right;word-break:break-word}
.vr .vv code{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:10px;color:#C9C9D4}
.vwhy{margin-top:9px;font-size:10.5px;line-height:1.55;color:#A8A8B6;font-style:italic;
  border-left:2px solid var(--brand);padding-left:9px}
.vbar{margin:2px 0 10px}
.vbar-t{display:flex;justify-content:space-between;font-size:10.5px;color:#6F6F7C;margin-bottom:6px}
.vbar-t b{color:#fff;font-size:11.5px}
.vtrack{position:relative;height:7px;border-radius:4px;background:#2B2B34}
.vtrack i{display:block;height:100%;border-radius:4px;background:linear-gradient(90deg,var(--brand),#FF9A9A)}
.vtrack u{position:absolute;top:-3px;width:2px;height:13px;background:#fff;opacity:.5;border-radius:1px}
.vthr{font-size:9px;color:#5F5F6C;margin-top:6px;line-height:1.45}
.acard details.raw{margin-top:10px;border-top:1px solid #2B2B34;padding-top:8px}
.acard details.raw summary{font-size:9.5px;font-weight:800;letter-spacing:.5px;color:#6F6F7C;cursor:pointer}
.acard details.raw summary:hover{color:#A8A8B6}
.acard details.raw pre{margin-top:7px;max-height:170px;overflow:auto}
.placeholder{font-size:11px;color:#5F5F6C;font-style:italic}

/* state panel */
.statewrap{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-bottom:8px;flex:0 0 auto}
.statewrap3{grid-template-columns:1fr 1fr 1fr;gap:7px;margin-bottom:8px}
.sbox{background:#17171B;border:1px solid #33333C;border-radius:10px;padding:7px 9px;min-width:0}
.sbox .k{font-size:9.5px;font-weight:800;letter-spacing:.7px;color:#8A8A98;margin-bottom:3px}
/* 14px, and it WRAPS rather than truncating. The values here are now human labels
   ("Simulated - approved"), so they must break between words, never mid-word - the old
   CLARIFICATION_REQUIRED broke across two lines and read as a rendering fault. */
.sbox .v{font-size:14px;font-weight:800;color:#fff;font-family:ui-monospace,Menlo,monospace;line-height:1.25;overflow-wrap:break-word;word-break:normal}
.sbox .v.ok{color:#7FE0A8}.sbox .v.stop{color:#FF9B9B}.sbox .v.warn{color:#FFCF87}.sbox .v.brand{color:var(--brand)}
.outbadge{display:inline-block;padding:5px 11px;border-radius:7px;font-size:11px;font-weight:800;letter-spacing:.4px;
  background:#2A2A33;color:#8B8B98;border:1px solid #3F3F4A}
.outbadge.ok{background:#12301F;color:#7FE0A8;border-color:#1F5C3A}
.outbadge.stop{background:#3A1414;color:#FF9B9B;border-color:#6B2424}
.outbadge.warn{background:#3A2A12;color:#FFCF87;border-color:#6B4A1C}
.outbadge.brand{background:#3A1618;color:var(--brand);border-color:#6B2226}

/* The per-turn caption under the control chain (rewritten by captionFor() in ui.js from what
   that turn actually did, so it reads as a report rather than a slogan). */
.capline{flex:0 0 auto;margin-top:5px;padding:5px 9px;border-radius:10px;background:#17171B;
  border:1px solid #33333C;border-left:3px solid var(--brand);
  font-size:11px;font-weight:700;color:#E8E8EE;line-height:1.45}

/* VOICE ENGINE row. Hidden until Tencent ASR actually fails, then never hidden again, an
   indicator that only appears on a fallback cannot accidentally claim the real engine ran. */
.sbox.vengine{border-color:#6B4A1C;background:#2A1F0E;flex:0 0 auto}
.sbox.vengine .v{color:#FFCF87;font-family:inherit;font-size:11px;font-weight:700;line-height:1.4}

/* audit */
.audit{display:flex;flex-direction:column;gap:2px;flex:1 1 auto;min-height:0;overflow-y:auto;font-family:ui-monospace,Menlo,Consolas,monospace}
.audit::-webkit-scrollbar{width:5px}.audit::-webkit-scrollbar-thumb{background:#43434F;border-radius:3px}
.audit .ae{font-size:11px;color:#B9B9C4;padding:3px 6px;border-radius:5px;display:flex;gap:7px;line-height:1.4;word-break:break-word}
.audit .ae .t{color:#8A8A98;flex-shrink:0}
.audit .ae.ok{color:#7FE0A8}.audit .ae.stop{color:#FF9B9B}
.audit .ae.warn{color:#FFCF87}.audit .ae.brand{color:var(--brand)}
/* Progressive disclosure: three rows by default, the whole chain on demand. A wall of audit
   lines at equal weight buries the one that matters; three lines plus an explicit "there is
   more" is both easier to read and more obviously honest about the volume. */
.audit.trim .ae:nth-last-child(n+4){display:none}
.auditmore{align-self:flex-start;margin-top:5px;font-size:10.5px;font-weight:700;color:#8A8A98;
  background:#1E1E24;border:1px solid #33333C;border-radius:7px;padding:4px 9px;cursor:pointer;flex:0 0 auto}
.auditmore:hover{color:#fff;border-color:#4A4A57}

/* requirements coverage — the scoring panel.
   Five rows, each: number, the challenge's own wording, where it is demonstrated, and an
   honest status chip. The chip colours carry the honesty: green = live code, amber = the
   mock/simulated boundary. Amber is a feature here, not a defect - a labelled simulation is
   credible, an unlabelled one is not. */
.reqs{display:flex;flex-direction:column;gap:5px;flex:1 1 auto;min-height:0;overflow-y:auto;padding-right:3px}
.reqs::-webkit-scrollbar{width:5px}.reqs::-webkit-scrollbar-thumb{background:#43434F;border-radius:3px}
.rq{display:flex;gap:8px;align-items:flex-start;background:#17171B;border:1px solid #33333C;border-radius:10px;padding:7px 9px;transition:.2s}
.rq.hit{border-color:var(--brand);background:#241618}
.rq .rqn{flex:0 0 auto;font-size:10px;font-weight:800;color:#fff;background:#3A3A46;border-radius:5px;padding:2px 6px;line-height:1.35}
.rq.hit .rqn{background:var(--brand);color:#fff}
.rq .rqb{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}
.rq .rqt{font-size:11.5px;font-weight:650;color:#E9E9F0;line-height:1.28}
.rq .rqd{font-size:10px;color:#9A9AA8;line-height:1.3}
.rq .rqs{flex:0 0 auto;font-size:9px;font-weight:800;letter-spacing:.03em;text-transform:uppercase;padding:3px 6px;border-radius:5px;border:1px solid transparent;white-space:nowrap}
.rq .rqs.live{background:rgba(127,224,168,.14);color:#7FE0A8;border-color:rgba(127,224,168,.34)}
.rq .rqs.simulated{background:rgba(255,207,135,.14);color:#FFCF87;border-color:rgba(255,207,135,.34)}

/* ==========================================================================
   top chrome
   ========================================================================== */
.top{
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;flex:0 0 auto;
  padding:clamp(10px,2vw,16px) 0;
  position:sticky;top:0;z-index:20;
  background:linear-gradient(var(--studio) 78%, rgba(15,15,17,0));
  margin-bottom:clamp(8px,1.5vw,12px);
}
.logo{display:flex;align-items:center;gap:13px;min-width:0}
.logo .dbslogo{height:30px}
.logo h1{font-size:clamp(18px,2.4vw,22px);font-weight:800;color:#fff;letter-spacing:-.3px;white-space:nowrap}
.logo .tag{font-size:11.5px;color:#B9B9C4;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.badges{display:flex;gap:7px;flex-wrap:wrap;margin-left:auto;align-items:center}
.bdg{font-size:10.5px;font-weight:700;letter-spacing:.4px;padding:5px 10px;border-radius:999px;
  background:var(--studio2);border:1px solid var(--studio-line);color:#C9C9D4;white-space:nowrap}
.bdg.mock{background:#3A2A12;border-color:#6B4A1C;color:#FFCF87}
.bdg.live{background:#12301F;border-color:#1F5C3A;color:#7FE0A8}
/* The two status chips are <button>s now, because each one opens the page that backs up its
   claim. `.bdg` already restates background, border, padding, size and weight, so the button
   reset only needs the cursor, the hover and a visible focus ring - a chip that looks inert but
   is clickable, or vice versa, is worse than either. */
.bdgbtn{cursor:pointer;transition:.14s}
.bdgbtn:hover{filter:brightness(1.4)}
.bdgbtn:focus-visible{outline:2px solid #fff;outline-offset:2px}

/* technical-panels show/hide switch */
.techtoggle{display:flex;align-items:center;gap:7px;font-size:11px;font-weight:700;color:#C9C9D4;cursor:pointer;user-select:none}
.techtoggle input{appearance:none;width:30px;height:17px;border-radius:999px;background:#43434F;position:relative;cursor:pointer;flex-shrink:0;transition:.16s}
.techtoggle input::after{content:'';position:absolute;top:2px;left:2px;width:13px;height:13px;border-radius:50%;background:#fff;transition:.16s}
.techtoggle input:checked{background:var(--brand)}
.techtoggle input:checked::after{left:15px}
/* Voice Mode is deliberately INDISTINGUISHABLE from the Technical panels switch. It is a
   backstage rehearsal control: when it is on, nothing on screen may differ from normal, so
   the demo shows the system escalating rather than a label announcing it. Do NOT add a
   checked-state colour, body class, banner or badge here. */
.voicetoggle input:checked{background:var(--brand)}

/* TRTC Mode IS meant to be visible, and for the opposite reason to Voice Mode above: the two
   engines sound different, a judge may ask which one is speaking, and the operator needs to
   see the switch state at a glance. A distinct colour rather than the brand red so "Tencent is
   speaking" cannot be mistaken for the Voice Mode escalation switch sitting next to it. */
.trtctoggle input:checked{background:#2E9E6B}
.trtctoggle input:disabled{opacity:.5;cursor:progress}

/* SPEAKING row: amber when the selected engine could not speak and the other one answered
   instead. The point is that a fallback is never silent -- a voice that changes mid-demo
   without explanation is far more confusing than one that says why. */
.vengine.warn .v{color:#F0B429}
.vengine.warn .k{color:#F0B429}

/* small ghost buttons in the top bar */
.ghostbtn{font-size:10.5px;font-weight:700;letter-spacing:.4px;padding:5px 10px;border-radius:999px;
  background:transparent;border:1px dashed var(--studio-line);color:#8B8B98;cursor:pointer;white-space:nowrap;transition:.14s}
.ghostbtn:hover{border-color:var(--brand);color:#fff;border-style:solid}

/* personalisation status/settings button */
.personalize-btn{font-size:10.5px;font-weight:700;letter-spacing:.4px;padding:5px 10px;border-radius:999px;
  background:var(--studio2);border:1px solid var(--studio-line);color:#C9C9D4;cursor:pointer;white-space:nowrap;transition:.14s}
.personalize-btn:hover{border-color:#57575F}
.personalize-btn.active{background:#12301F;border-color:#1F5C3A;color:#7FE0A8}

/* hide the inspector when the customer is looking at the app, or when the
   technical-panels switch is off, the device then centres itself via its
   auto inline margins. */
body.tech-hidden .ins{display:none}
#studio.appmode .ins{display:none}

/* ==========================================================================
   scenario rail, backup only
   ========================================================================== */
.rail{
  background:var(--studio2);border:1px dashed var(--studio-line);border-radius:var(--radius);
  padding:13px var(--gutter);margin-bottom:clamp(12px,2.5vw,18px);
}
.rail h2{font-size:11px;font-weight:700;letter-spacing:1px;color:#8B8B98;margin-bottom:10px}
.grp{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin-bottom:7px}
.grp:last-child{margin-bottom:0}
.grp .glab{
  font-size:10px;font-weight:800;letter-spacing:.6px;color:#fff;background:#3A3A46;
  border-radius:6px;padding:4px 7px;min-width:52px;text-align:center;flex-shrink:0
}
.grp.A .glab{background:#7A3B3B}.grp.B .glab{background:#7A5A2B}
.grp.C .glab{background:#5A2B6B}.grp.D .glab{background:#2B5A6B}
.grp.E .glab{background:#2B6B4A}.grp.F .glab{background:#6B2B3B}
.grp.H .glab{background:var(--brand-ink)}
.grp.G .glab{background:#3B5A7A}
.grp.HAPPY .glab{background:var(--brand-ink2)}
.sbtn{
  background:#31313A;border:1px solid #43434F;color:#DCDCE4;border-radius:8px;
  padding:6px 11px;font-size:11.5px;font-weight:600;cursor:pointer;transition:.14s;white-space:nowrap
}
.sbtn:hover{background:var(--brand);border-color:var(--brand);color:#fff}
.sbtn:active{transform:scale(.97)}
.sbtn.on{background:var(--brand);border-color:var(--brand);color:#fff;box-shadow:0 0 0 3px rgba(236,28,36,.22)}

/* ==========================================================================
   modal / popup, a DBS verification sheet
   The popup lives inside .device-screen, so it is clipped to the phone's rounded screen and
   can never spill onto the laptop page. It starts below the status bar the way a native iOS
   sheet does, leaving the mock status bar and the dynamic island visible above it.
   ========================================================================== */
/* The card sits at the FOOT of the screen, not the middle. It reads as a sheet the customer
   just pulled up, and it leaves Joy in frame at the top of the card where she belongs. */
.modal{position:absolute;inset:0;top:var(--statusbar-h);background:rgba(10,10,12,.72);backdrop-filter:blur(5px);
  display:none;align-items:flex-end;justify-content:center;padding:16px;z-index:300}
.modal.show{display:flex;animation:fade .18s ease}
@keyframes fade{from{opacity:0}to{opacity:1}}
/* Sized against the phone screen now, not the viewport: 100% is the modal's content box
   (screen minus its padding), and a long card scrolls inside the phone rather than
   overflowing the bezel. */
.mcard{background:#fff;border-radius:20px;width:100%;max-width:100%;overflow:hidden;
  max-height:100%;overflow-y:auto;
  box-shadow:var(--sh-3);animation:pop .22s cubic-bezier(.2,1.1,.4,1)}
@keyframes pop{from{transform:translateY(14px) scale(.97);opacity:0}to{transform:none;opacity:1}}
/* Header is a soft pink stage, not a black bar: Joy is the thing on this card, so she gets
   the light surface and the full width. No red rule under it, against pink it just muddies
   the edge between the stage and the white body below. */
.mtop{flex:0 0 auto;background:linear-gradient(160deg,var(--brand-tint),var(--brand-tint2));
  padding:13px 14px 14px;text-align:center;position:relative}
.mtop .kicker{font-size:9px;font-weight:800;letter-spacing:.9px;color:var(--brand-ink);
  background:rgba(255,255,255,.85);padding:4px 10px;border-radius:20px;display:inline-block;
  max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ---------- the state animation slot ---------- */
.popStage{
  position:relative;
  /* aspect-ratio:4 / 3;
  max-height:190px; */
  height:180px;
  width:78%;
  margin:10px auto 0;
  overflow:hidden;
  border-radius:15px;
  align-self:center
}
.popStage .mstill{position:absolute;left:50%;bottom:-13%;transform:translateX(-50%);
  /* height:100% (was 118%): with bottom:-13%, a 118% tall box put the image's top ~5% ABOVE
     the stage, and overflow:hidden on .popStage clipped Joy's head off the top. 100% keeps
     the bottom peek but anchors the head safely inside the frame. */
  height:100%;width:auto;opacity:1;transition:opacity .35s ease;
  filter:drop-shadow(0 8px 16px rgba(196,30,30,.22))}
/* `cover` so the clip fills the rounded stage with no sharp inner rectangle, plus
   `object-position:center top` so that whenever `max-height` makes the stage wider than the
   clip's own 4:3, the few pixels `cover` must trim come off the clip's EMPTY bottom rather
   than off the top of Joy's head. See the note on .popStage above. */
.popStage .mmedia{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  object-position:center top;
  opacity:0;transition:opacity .35s ease}
.popStage .mmedia.on{opacity:1}
.popStage.on .mstill{opacity:0}
/* The welcome card is the only full-bleed introduction, so her stage gets slightly more room. */
.mtop.splash .popStage{max-height:210px;width:86%}
.mbody{padding:20px 20px 18px;text-align:center}
.mbody h3{font-size:18px;font-weight:800;color:var(--ink);margin-bottom:8px;line-height:1.25;letter-spacing:-.3px}
.mbody p{font-size:13.5px;color:var(--ink2);line-height:1.55}
.mfields{margin:15px 0 4px;border:1px solid var(--line);border-radius:12px;overflow:hidden;text-align:left}
.mfields .fr{display:flex;justify-content:space-between;gap:10px;padding:11px 13px;border-bottom:1px solid var(--line);font-size:13px}
.mfields .fr:last-child{border-bottom:0}
.mfields .fr .k{color:var(--muted);font-size:11.5px}
.mfields .fr .v{font-weight:800;color:var(--ink);text-align:right}
.mfields .fr.big .v{font-size:20px;color:var(--brand-ink);letter-spacing:-.5px}
.macts{display:flex;flex-direction:column;gap:8px;margin-top:16px}
.macts button{
  border:0;border-radius:12px;padding:14px;font-size:15px;font-weight:800;cursor:pointer;
  display:flex;align-items:center;justify-content:center;gap:8px;transition:.14s
}
.macts button .ic{width:17px;height:17px}
.macts .yes{background:var(--dbs-red);color:#fff;box-shadow:var(--sh-red)}
.macts .yes:hover{background:var(--dbs-red-dk)}
.macts .no{background:#F4F4F7;color:var(--ink2)}
.macts .no:hover{background:#EBEBF0}
.macts .dan{background:var(--stop);color:#fff}
.macts .ok2{background:var(--ok);color:#fff}
.mnote{font-size:10.5px;color:var(--faint);margin-top:12px;line-height:1.45}
.fstep{display:flex;gap:5px;justify-content:center;margin-top:14px}
.fstep i{width:26px;height:4px;border-radius:2px;background:#E4E4EA;display:block}
.fstep i.on{background:var(--brand)}
.fstep i.done{background:var(--ok)}
/* FaceID scan frame, a hairline DBS-red frame with a slow halo, not a glowing blob. */
.faceid{
  width:92px;height:92px;border-radius:24px;border:2px solid var(--brand);
  margin:4px auto 14px;display:flex;align-items:center;justify-content:center;
  background:var(--brand-tint);color:var(--dbs-red);animation:fpulse 1.9s infinite;
}
.faceid .ic{width:44px;height:44px}
@keyframes fpulse{0%,100%{box-shadow:0 0 0 0 rgba(236,28,36,.30)}50%{box-shadow:0 0 0 14px rgba(236,28,36,0)}}

/* footer note */
/* One line, not two. The disclaimer used to sit on its own row under the principle, which cost
   the stage ~16px of height on every screen - and the stage's height IS the phone's height.
   Pushed right on the same baseline so the two read as one footer sentence plus a colophon. */
.foot{flex:0 0 auto;margin-top:9px;color:var(--muted);font-size:11px;line-height:1.55;max-width:1460px;
  display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
.foot b{color:var(--faint)}
.foot .disclaim{margin-left:auto;color:#7A7A88;font-size:10.5px}

/* ==========================================================================
   EXPLAINER OVERLAY (index.html #explain)
   The two status chips in the top bar open this. It exists so the main screen can stay clean
   without the evidence disappearing: each chip makes a claim, and clicking the claim is what
   shows the working.
   A fixed layer rather than a routed page: the front end is one HTML file with no build step
   and no router, and a judge has to be able to get back to a live screen in one click without
   losing a turn that is mid-flight.
   ========================================================================== */
.explain{position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;
  padding:clamp(8px,2.6vh,32px);background:rgba(6,6,8,.84);backdrop-filter:blur(3px)}
.excard{display:flex;flex-direction:column;width:min(1180px,96vw);height:min(920px,94vh);
  background:var(--studio2);border:1px solid var(--studio-line);border-radius:20px;overflow:hidden;
  box-shadow:0 30px 80px rgba(0,0,0,.7)}
.exhead{display:flex;align-items:center;gap:12px;padding:12px 14px;flex:0 0 auto;
  border-bottom:1px solid var(--studio-line);background:linear-gradient(#1B1B21,#151519)}
.extabs{display:flex;gap:6px;flex-wrap:wrap;min-width:0}
.extab{font-size:11.5px;font-weight:800;letter-spacing:.4px;padding:7px 13px;border-radius:999px;
  background:#202027;border:1px solid var(--studio-line);color:#B9B9C4;cursor:pointer;white-space:nowrap;transition:.14s}
.extab:hover{color:#fff;border-color:#57575F}
.extab.on{background:var(--brand);border-color:var(--brand);color:#fff}
.exclose{margin-left:auto;flex:0 0 auto;width:34px;height:34px;display:grid;place-items:center;
  border-radius:10px;background:#202027;border:1px solid var(--studio-line);color:#C9C9D4;cursor:pointer;transition:.14s}
.exclose:hover{background:var(--brand);border-color:var(--brand);color:#fff}
.exbody{flex:1 1 auto;min-height:0;overflow-y:auto;padding:20px clamp(15px,3vw,30px) 28px}
.exbody::-webkit-scrollbar{width:9px}
.exbody::-webkit-scrollbar-thumb{background:#43434F;border-radius:4px}

/* The overlay reuses the page's own dark palette and type scale. Body text is 12.5-14px for the
   same reason the panels were raised: this is read off a projector, not a laptop at 40cm. */
.expage h2{font-size:clamp(19px,2.3vw,25px);font-weight:800;color:#fff;letter-spacing:-.3px;margin-bottom:9px}
.expage h3{font-size:13px;font-weight:800;letter-spacing:.9px;color:#A0A0AE;text-transform:uppercase;
  margin:24px 0 9px;padding-top:16px;border-top:1px solid var(--studio-line);
  display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.expage h3 .cnt{margin-left:auto;font-size:10.5px;color:#8A8A98;font-weight:700;letter-spacing:.3px}
.expage h4{font-size:12.5px;font-weight:800;letter-spacing:.5px;color:#fff;margin-bottom:10px;
  display:flex;align-items:center;gap:8px}
.exlead{font-size:14px;line-height:1.6;color:#C9C9D4;max-width:76ch;margin-bottom:4px}
.exlead b{color:#fff}
.exsub{font-size:12px;line-height:1.6;color:#9A9AA8;max-width:84ch;margin-bottom:12px}
.exnote{font-size:12px;line-height:1.6;color:#9A9AA8;margin-top:11px;padding:10px 12px;
  background:#17171B;border-left:2px solid var(--brand);border-radius:0 8px 8px 0}
.exdot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.exdot.live{background:#12A150}
.exdot.sim{background:#F0B429}

/* REAL vs SIMULATED, side by side. The two columns are the argument, so they must not stack on
   a projector: the reader needs to see both at once. */
.excols{display:grid;grid-template-columns:1fr 1fr;gap:clamp(14px,2vw,26px);margin-top:16px}
.excol{background:#17171B;border:1px solid #33333C;border-radius:14px;padding:14px 16px}
.excol.real{border-color:#1F5C3A}
.excol.sim{border-color:#6B4A1C}
.excol ul{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:10px}
.excol li{font-size:12.5px;line-height:1.55;color:#B9B9C4;padding-left:15px;position:relative}
.excol li::before{content:'';position:absolute;left:0;top:8px;width:5px;height:5px;border-radius:50%;background:#43434F}
.excol.real li::before{background:#12A150}
.excol.sim li::before{background:#F0B429}
.excol li b{color:#fff}

.exlist{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:10px;max-width:98ch}
.exlist li{font-size:12.5px;line-height:1.6;color:#B9B9C4;padding-left:16px;position:relative}
.exlist li::before{content:'';position:absolute;left:0;top:9px;width:6px;height:6px;border-radius:2px;background:var(--brand)}
.exlist li b{color:#fff}

/* the five agents: two columns of cards, each card a tiny typed contract */
.exagents{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.exagent{background:#17171B;border:1px solid #33333C;border-radius:12px;padding:12px 14px;min-width:0}
.exagent .exah{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:8px}
.exagent .exan{font-size:12.5px;font-weight:800;color:#fff}
.exagent .exas{font-size:9.5px;font-weight:800;letter-spacing:.4px;padding:3px 7px;border-radius:5px;
  background:#3A1618;color:var(--brand);border:1px solid #6B2226;white-space:nowrap}
.exagent dl{margin:0;display:grid;grid-template-columns:auto 1fr;gap:5px 10px;font-size:11.5px;line-height:1.5}
.exagent dt{color:#8A8A98;font-weight:700;white-space:nowrap}
.exagent dd{margin:0;color:#B9B9C4;min-width:0}
.exagent dd.never{color:#FF9B9B}

/* the sixteen controls, numbered to match the chain on the main screen */
.exctl{display:grid;grid-template-columns:1fr 1fr;gap:4px 14px}
.exct{display:flex;gap:9px;align-items:flex-start;padding:5px 8px;border-radius:8px;background:#17171B}
.exct.llm{background:#241618;border:1px solid #6B2226}
.exct .exctn{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:10.5px;font-weight:800;
  color:#8A8A98;flex-shrink:0;padding-top:1px}
.exct.llm .exctn{color:var(--brand)}
.exct .exctb{min-width:0}
.exct .exctt{font-size:12px;color:#C9C9D4;line-height:1.3}
.exct.llm .exctt{color:#fff;font-weight:700}
.exct .excty{font-size:10px;color:#8A8A98;line-height:1.35}

/* the failure-branch rail inside the overlay. The buttons are the same `.sbtn` elements the main
   screen used to carry; only the container changes, so the page-level margins go. */
.explain .rail{background:#17171B;border:1px dashed var(--studio-line);border-radius:14px;
  padding:14px 16px;margin:0}

/* ==========================================================================
   small-screen refinements
   ========================================================================== */
@media(max-width:900px){
  /* Below this the device and the panels can no longer sit side by side, so the
     viewport-height binding is released: the page scrolls again and each panel gets a
     sensible cap instead of a share of the screen. */
  #studio{height:auto;padding-bottom:0}
  .stage{flex-direction:column;align-items:stretch;flex:0 0 auto}
  .heropair{flex:1 1 auto}
  .device{height:min(760px,74vh)}
  /* A DEFINITE height, deliberately not a percentage. Below this breakpoint the page stops
     being viewport-height-bound, so the hero pair's height is content-derived and indefinite -
     and `min(52%, 360px)` becomes invalid at used-value time, falls back to `auto`, and renders
     Joy at her full 720px intrinsic height, taller than the phone beside her. vw always
     resolves. */
  .joymascot{height:min(32vw,250px)}
  .ins{grid-template-columns:1fr;grid-template-rows:auto}
  .chain,.agents,.reqs{max-height:clamp(200px,32vh,340px)}
  .audit{max-height:180px}
  /* the overlay's two-column arguments stack here, and the card takes the whole screen */
  .excols,.exagents,.exctl{grid-template-columns:1fr}
  .excard{height:96vh;width:98vw}
}
@media(max-width:640px){
  .top{align-items:flex-start;flex-direction:column;gap:10px}
  .badges{margin-left:0}
  /* Joy is a full-width luxury below this: the pair no longer fits beside the phone, and a
     half-visible mascot is worse than none. Same selector as the rule that shows her, so it
     wins on order rather than on !important. */
  #studio.appmode .joymascot{display:none}
  .rail h2{font-size:10px}
  .sbtn{padding:6px 9px;font-size:11px}
  .bdg{white-space:normal;line-height:1.35;text-align:center}
  .mbody{padding:18px 16px 16px}
  .mbody h3{font-size:17px}
  .macts button{padding:13px;font-size:14.5px}
  .enable{flex-direction:column;align-items:flex-start;gap:12px}
  .enable .earrow{display:none}
  .balcard .bv{font-size:25px}
}
@media(max-width:420px){
  :root{--device-r:40px;--screen-r:34px}
  .device{height:min(700px,78vh);border-width:5px}
  .device::before,.device::after{display:none}
}
