/* ============================================================
   hero-app.css — the landing hero's live product demo.

   Every rule here is a scoped copy of the real /app chrome
   (app/app.css) so the hero shows the ACTUAL product: the same
   sidebar tabs, the same Lemma AI panel, the same canvas boxes,
   and the same spawn / flash animations the agent uses in-app.

   Scoped under .appdemo so it can't touch the marketing page,
   and so the two `.canvas` / `.chip` rules in styles.css that
   share a name with app chrome are overridden, not inherited.

   Keep in sync with app/app.css when the agent panel changes.
   ============================================================ */

.appdemo {
  --ad-tb: 56px;          /* toolbar height   (app: --tb-h 64) */
  --ad-sb: 268px;         /* sidebar width    (app: --sb-w 274) */
  --ad-crumb: 46px;       /* breadcrumb strip (app: 52) */
  --accent: var(--green);
  --accent-soft: var(--mint);
  --accent-line: var(--mint-line);
  --spring: cubic-bezier(.34, 1.4, .5, 1);

  position: relative;
  height: 664px;
  background: var(--paper-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
  isolation: isolate;
}
.appdemo .ad-app { display: flex; flex-direction: column; height: 100%; min-height: 0; }

.appdemo *, .appdemo *::before, .appdemo *::after { box-sizing: border-box; }
.appdemo button { font: inherit; color: inherit; background: none; border: 0; cursor: default; }

/* the demo is a picture of the app — nothing inside is a real control */
.appdemo .ad-shell, .appdemo .tb { user-select: none; }

/* ============================================================
   Toolbar  (app .tb)
   ============================================================ */
.appdemo .tb {
  height: var(--ad-tb);
  flex: none;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 14px;
  background: rgba(251,250,244,0.82);
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  border-bottom: 1px solid var(--line-soft);
  position: relative;
  z-index: 40;
}
.appdemo .tb__group { display: flex; align-items: center; gap: 3px; }
.appdemo .tb__tools { gap: 2px; }
.appdemo .tb__right { margin-left: auto; }
.appdemo .tb__div { width: 1px; height: 20px; background: var(--line); margin: 0 5px; flex: none; }

.appdemo .tbtool {
  display: inline-flex; align-items: center; gap: 7px;
  height: 34px; padding: 0 9px;
  border-radius: 9px;
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.02em;
  white-space: nowrap;
  transition: background .22s var(--ease), color .22s var(--ease),
              transform .22s var(--ease), box-shadow .22s var(--ease);
}
.appdemo .tbtool svg { width: 18px; height: 18px; transition: transform .22s var(--ease); }
.appdemo .tbtool.is-active {
  background: var(--accent-soft);
  color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent-line);
}
.appdemo .tbicon {
  width: 34px; height: 34px;
  display: inline-grid; place-items: center;
  border-radius: 9px;
  color: var(--ink-soft);
  transition: background .22s var(--ease), color .22s var(--ease),
              transform .22s var(--ease), box-shadow .22s var(--ease);
}
.appdemo .tbicon svg { width: 18px; height: 18px; transition: transform .22s var(--ease); }
.appdemo .tbicon.is-on {
  background: var(--accent-soft); color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent-line);
}
/* the ghost cursor "presses" a toolbar control */
.appdemo .tbicon.is-press, .appdemo .tbtool.is-press {
  background: var(--accent-soft); color: var(--accent);
  transform: translateY(0) scale(0.96);
  box-shadow: inset 0 0 0 1px var(--accent-line);
}

.appdemo .zoom { display: flex; align-items: center; gap: 2px; }
.appdemo .zoom__val {
  font-family: var(--font-mono); font-size: 12px; color: var(--ink-faint);
  min-width: 42px; text-align: center;
}
.appdemo .sync-chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 0 10px; height: 28px; border-radius: 999px;
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-faint);
  white-space: nowrap;
}
.appdemo .sync-chip i { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-faint); flex: none; }
.appdemo .sync-chip[data-state="saving"] i { background: var(--amber); animation: ad-chip-pulse 1s ease-in-out infinite; }
.appdemo .sync-chip[data-state="saved"] i { background: var(--green); }
@keyframes ad-chip-pulse { 50% { opacity: .35; transform: scale(.8); } }

/* ============================================================
   Shell + sidebar  (app .shell / .sb)
   ============================================================ */
.appdemo .ad-shell { flex: 1; display: flex; min-height: 0; }

.appdemo .sb {
  width: var(--ad-sb);
  flex: none;
  display: flex; flex-direction: column;
  background: rgba(251,250,244,0.7);
  backdrop-filter: saturate(1.3) blur(12px);
  -webkit-backdrop-filter: saturate(1.3) blur(12px);
  border-right: 1px solid var(--line-soft);
  min-height: 0;
  /* focus mode collapses the sidebar (app hides it outright — the demo
     slides it so the extra canvas space reads as a deliberate gain) */
  transition: width .52s var(--ease), opacity .3s var(--ease);
}
.appdemo.is-focus .sb { width: 0; opacity: 0; border-right-color: transparent; }
.appdemo.is-focus .sb__grip { opacity: 0; }

.appdemo .sb__inner { width: var(--ad-sb); flex: none; display: flex; flex-direction: column; min-height: 0; height: 100%; }

.appdemo .sb__tabs { display: flex; gap: 4px; padding: 11px 11px 7px; flex: none; }
.appdemo .sb__tab {
  flex: 1;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: 34px; border-radius: 9px;
  font-family: var(--font-mono);
  font-size: 11.5px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-faint);
  transition: background .22s var(--ease), color .22s var(--ease), box-shadow .22s var(--ease);
}
.appdemo .sb__tab svg { width: 15px; height: 15px; }
.appdemo .sb__tab.is-active { color: var(--green-700); background: var(--mint); }

.appdemo .sb__panel {
  flex: 1; display: flex; flex-direction: column; min-height: 0;
  padding: 6px 10px 8px;
  overflow: clip;
}
.appdemo .sb__grip { width: 5px; flex: none; margin-left: -3px; z-index: 5; transition: opacity .3s var(--ease); }

/* profile footer */
.appdemo .sb__foot {
  flex: none;
  display: flex; align-items: center; gap: 10px;
  padding: 7px 12px;
  border-top: 1px solid var(--line-soft);
}
.appdemo .footacct {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 10px;
  padding: 4px 8px; margin: -2px -4px;
  border-radius: 12px; text-align: left;
}
.appdemo .avatar {
  width: 32px; height: 32px; flex: none;
  border-radius: 9px;
  background: var(--green); color: #fff;
  display: grid; place-items: center;
  font-family: var(--font-serif); font-size: 0.95rem; font-weight: 600;
}
.appdemo .who { flex: 1; min-width: 0; line-height: 1.25; }
.appdemo .who b { display: block; font-size: 0.94rem; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.appdemo .who span { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-faint); }
.appdemo .footacct__dots { opacity: 0.55; width: 30px; height: 30px; }
.appdemo .footacct__dots svg { width: 17px; height: 17px; }

/* ============================================================
   Lemma AI panel  (app .agent — the whole point of the demo)
   ============================================================ */
.appdemo .agent {
  position: relative;
  flex: 1; display: flex; flex-direction: column; min-height: 0; min-width: 0;
  padding: 2px; gap: 8px;
  font-size: 1.02rem;
}
.appdemo .agent__head { display: flex; align-items: center; gap: 9px; padding: 4px 4px 10px; flex: none; min-width: 0; }
.appdemo .agent__brand {
  flex: 1; min-width: 0; white-space: nowrap;
  font-family: var(--font-serif); font-size: 1.22rem; font-weight: 500;
  color: var(--ink); letter-spacing: -0.01em;
}
.appdemo .agent__brand em { color: var(--green); font-style: italic; }
.appdemo .agent__brandspark { color: var(--green); display: inline-block; }
.appdemo .agent__brandspark.is-wiggling { animation: ad-icon-wiggle 1.45s cubic-bezier(.25,.8,.25,1) both; }
@keyframes ad-icon-wiggle {
  0%, 100% { transform: rotate(0deg) scale(1); opacity: 1; }
  22% { transform: rotate(-5deg) scale(1.025); opacity: 0.96; }
  44% { transform: rotate(4.5deg) scale(1.04); opacity: 1; }
  66% { transform: rotate(-2.5deg) scale(1.025); }
  84% { transform: rotate(1.25deg) scale(1.01); }
}
.appdemo .agent__plan {
  flex: none;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.14em;
  padding: 4px 8px; border-radius: 999px;
  color: var(--green-700); background: var(--mint); border: 1px solid var(--mint-line);
}
.appdemo .agent__headbtn {
  flex: none; width: 30px; height: 30px;
  display: grid; place-items: center; border-radius: 8px;
  color: var(--ink-faint);
}
.appdemo .agent__headbtn svg { width: 16px; height: 16px; }

/* context row */
.appdemo .agent__ctxrow {
  flex: none; display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 0 0 8px; min-width: 0;
}
.appdemo .agent__ctxhead {
  display: inline-flex; align-items: center; gap: 6px;
  flex: 1; min-width: 0;
  padding: 4px 2px; border-radius: 8px;
  color: var(--ink-faint); text-align: left;
}
.appdemo .agent__ctxchev {
  width: 13px; height: 13px; flex: none;
  transition: opacity .2s var(--ease), width .2s var(--ease), margin .2s var(--ease);
}
/* nothing attached → the chevron collapses and gives the line its width back */
.appdemo .agent__ctxhead.is-empty .agent__ctxchev { opacity: 0; width: 0; margin: 0; }
.appdemo .agent__ctxline {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.02em;
  color: inherit; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.appdemo .agent__autoattach {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-faint); padding: 4px 6px; border-radius: 8px; white-space: nowrap;
}
.appdemo .agent__autoattach i { width: 22px; height: 12px; border-radius: 999px; background: var(--line); position: relative; flex: none; }
.appdemo .agent__autoattach i::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 8px; height: 8px; border-radius: 50%; background: #fff; transition: left .2s var(--ease);
}
.appdemo .agent__autoattach.is-on { color: var(--green-700); }
.appdemo .agent__autoattach.is-on i { background: var(--green); }
.appdemo .agent__autoattach.is-on i::after { left: 12px; }
.appdemo .agent__ctxtoggle {
  flex: none; white-space: nowrap;
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.04em; color: var(--ink-faint);
}

/* ---------- thread ---------- */
.appdemo .agent__thread {
  flex: 1; min-height: 0; min-width: 0;
  overflow-y: auto; overflow-x: hidden;
  padding: 4px 0;
  scrollbar-width: none;
}
.appdemo .agent__thread::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* empty state */
.appdemo .agent__hello { width: 100%; padding: 6px 8px; text-align: center; animation: ad-rise .28s var(--ease) both; }
@keyframes ad-rise { from { opacity: 0; transform: translateY(7px); } }
.appdemo .agent__spark { font-size: 1.5rem; color: var(--green); display: inline-block; }
.appdemo .agent__title { font-family: var(--font-serif); font-size: 1.12rem; font-weight: 500; margin-top: 4px; }
.appdemo .agent__sub {
  width: 100%; max-width: 30ch; margin: 5px auto 0;
  min-height: 26px;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-soft); font-size: 0.85rem; line-height: 1.5;
}
.appdemo .agent__chips { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.appdemo .agent .chip {
  width: 100%; box-sizing: border-box;
  padding: 8px 12px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--paper-3);
  font-family: var(--font-sans); font-size: 0.85rem; font-weight: 400; color: var(--ink-soft);
  letter-spacing: normal;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: none;
}
.appdemo .agent .chip:hover { transform: none; background: var(--paper-3); }

/* ---------- messages ---------- */
.appdemo .agent-msg { padding: 9px 2px; animation: ad-msg-in .3s var(--ease); }
@keyframes ad-msg-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.appdemo .agent-msg--user { display: flex; flex-direction: column; align-items: flex-end; }
.appdemo .agent-msg__bubble {
  max-width: 88%;
  padding: 10px 13px;
  background: linear-gradient(170deg, var(--mint), color-mix(in srgb, var(--mint) 62%, var(--paper-3)));
  border: 1px solid var(--mint-line);
  border-radius: 16px 16px 5px 16px;
  box-shadow: var(--shadow-sm);
  font-size: 0.94rem; line-height: 1.55; color: var(--ink);
  white-space: pre-wrap; word-break: break-word;
}

.appdemo .agent-msg__imgrow { display: flex; flex-wrap: wrap; gap: 7px; justify-content: flex-end; margin-bottom: 6px; }
.appdemo .agent-msg__imgcard {
  margin: 0; width: 132px;
  border: 1px solid var(--line); border-radius: 13px;
  background: var(--paper-3); overflow: hidden;
  box-shadow: var(--shadow-sm);
  animation: ad-fchip-in .3s var(--ease) backwards;
}
.appdemo .agent-msg__imgcard .ad-shot { display: block; width: 100%; height: 84px; }
.appdemo .agent-msg__imgcard figcaption {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  padding: 5px 6px 5px 9px;
  border-top: 1px solid var(--line-soft);
  font-family: var(--font-mono); font-size: 9px; color: var(--ink-soft);
  overflow: hidden; white-space: nowrap;
}
.appdemo .agent-msg__imgcard figcaption span { overflow: hidden; text-overflow: ellipsis; }
.appdemo .agent-msg__imgcard figcaption em {
  font-style: normal; font-size: 8.5px; letter-spacing: 0.08em; text-transform: uppercase;
  flex: none; padding: 1px 6px; border-radius: 999px;
  color: var(--green-700); background: var(--mint); border: 1px solid var(--mint-line);
}

.appdemo .agent-msg__head { display: flex; align-items: center; gap: 7px; margin-bottom: 5px; }
.appdemo .agent-msg__spark { color: var(--green); font-size: 0.95rem; }
.appdemo .agent-msg--assistant.is-live .agent-msg__spark { animation: ad-spark 1.4s var(--ease) infinite; }
@keyframes ad-spark { 0%, 100% { transform: scale(1) rotate(0deg); opacity: 1; } 50% { transform: scale(1.18) rotate(12deg); opacity: 0.82; } }
.appdemo .agent-msg__who { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink-soft); }
.appdemo .agent-msg__model {
  font-family: var(--font-mono); font-size: 9.5px; color: var(--ink-faint);
  padding: 3px 8px; border-radius: 999px; background: var(--paper-2); border: 1px solid var(--line-soft);
}
.appdemo .agent-msg__model:empty { display: none; }

.appdemo .agent-msg__body { font-size: 0.94rem; line-height: 1.6; color: var(--ink); word-break: break-word; }
.appdemo .agent-msg__body p { margin: 0 0 8px; }
.appdemo .agent-msg__body p:last-child { margin-bottom: 0; }
.appdemo .agent-msg__body b, .appdemo .agent-msg__body strong { font-weight: 600; }
/* a display-math block inside a reply (app: KaTeX .katex-display) */
.appdemo .ad-mathline {
  margin: 10px 0;
  font-size: 1.02rem;
  text-align: center;
  overflow-x: auto;
  white-space: nowrap;
  scrollbar-width: none;
}
.appdemo .ad-mathline::-webkit-scrollbar { width: 0; height: 0; display: none; }

.appdemo .lm-caret {
  display: inline-block; width: 7px; height: 13px; margin-left: 2px; vertical-align: -2px;
  background: var(--green); border-radius: 2px; animation: ad-caret 0.9s steps(2) infinite;
}
@keyframes ad-caret { 50% { opacity: 0; } }

/* reasoning drawer */
.appdemo .agent-msg__think {
  margin: 0 0 7px; border: 1px solid var(--line-soft); border-radius: 9px;
  background: var(--paper-2); overflow: hidden;
  animation: ad-card-in .3s var(--ease);
}
.appdemo .agent-msg__think summary {
  padding: 7px 11px;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink-faint); list-style: none; display: flex; align-items: center; gap: 6px;
}
.appdemo .agent-msg__think summary::before { content: "\25B8"; transition: transform .2s var(--ease); }
.appdemo .agent-msg__think[open] summary::before { transform: rotate(90deg); }
.appdemo .agent-msg__think summary::-webkit-details-marker { display: none; }
.appdemo .agent-msg__think-body {
  padding: 2px 11px 10px;
  font-size: 0.82rem; line-height: 1.55; color: var(--ink-faint);
  white-space: pre-wrap; max-height: 92px; overflow: hidden;
}

/* live status line */
.appdemo .agent-msg__status {
  display: flex; align-items: center; gap: 8px; margin-top: 8px;
  font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-faint);
}
.appdemo .agent-msg__status i {
  width: 13px; height: 13px; flex: none; border-radius: 50%;
  border: 1.5px solid var(--mint-line); border-top-color: var(--green);
  animation: ad-spin .8s linear infinite;
}
.appdemo .agent-msg__status span {
  background: linear-gradient(90deg, var(--ink-faint) 30%, var(--green-700) 50%, var(--ink-faint) 70%);
  background-size: 200% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: ad-shimmer 1.6s linear infinite;
}
.appdemo .agent-msg__status[data-kind="build"] i { border-top-color: var(--amber); }
@keyframes ad-spin { to { transform: rotate(360deg); } }
@keyframes ad-shimmer { to { background-position: -200% 0; } }

/* ---------- result card ---------- */
.appdemo .agent-msg__card:empty { display: none; }
.appdemo .agent-card {
  margin-top: 10px; padding: 11px 13px;
  border: 1px solid var(--mint-line); border-radius: 12px;
  background: linear-gradient(160deg, var(--mint), rgba(232,241,234,0.25));
  animation: ad-card-in .35s var(--ease);
}
@keyframes ad-card-in { from { opacity: 0; transform: translateY(5px) scale(0.99); } to { opacity: 1; transform: none; } }
.appdemo .agent-card__steps { display: flex; flex-direction: column; gap: 4px; margin-bottom: 9px; }
.appdemo .agent-card__step {
  display: flex; align-items: center; gap: 7px;
  font-family: var(--font-mono); font-size: 10px; line-height: 1.35; color: var(--ink-soft);
  animation: ad-step-in .3s var(--ease) backwards;
}
.appdemo .agent-card__step:nth-child(2) { animation-delay: .08s; }
.appdemo .agent-card__step:nth-child(3) { animation-delay: .16s; }
@keyframes ad-step-in { from { opacity: 0; transform: translateX(-5px); } to { opacity: 1; transform: none; } }
.appdemo .agent-card__step i {
  width: 13px; height: 13px; flex: none; border-radius: 50%;
  display: grid; place-items: center; font-size: 8px; font-style: normal;
}
.appdemo .agent-card__step.is-ok i { background: var(--green); }
.appdemo .agent-card__step.is-ok i::before { content: "\2713"; color: #fff; }
.appdemo .agent-card__result { display: flex; align-items: center; gap: 8px; font-size: 0.9rem; color: var(--green-700); }
.appdemo .agent-card__check {
  width: 19px; height: 19px; flex: none; border-radius: 50%;
  background: var(--green); color: #fff;
  display: grid; place-items: center; font-size: 10px;
  animation: ad-check-pop .45s cubic-bezier(.34,1.56,.5,1) backwards;
}
@keyframes ad-check-pop { from { transform: scale(0); } to { transform: scale(1); } }
.appdemo .agent-card__actions { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.appdemo .agent-card__btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 7px 11px; border-radius: 9px;
  background: var(--green); color: #fff;
  font-size: 0.82rem; font-weight: 600;
  box-shadow: var(--shadow-sm);
  transition: transform .18s var(--ease), box-shadow .18s var(--ease), filter .18s var(--ease);
}
.appdemo .agent-card__btn--ghost { background: var(--paper-3); color: var(--ink-soft); border: 1px solid var(--line); }
.appdemo .agent-card__btn.is-press { transform: translateY(-1px); box-shadow: var(--shadow-md); filter: brightness(1.05); }

/* ---------- upload tray ---------- */
.appdemo .agent__files { flex: none; display: flex; flex-wrap: wrap; gap: 6px; padding: 0 2px 8px; }
.appdemo .agent__files[hidden] { display: none; }
.appdemo .agent-fchip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 4px 7px 4px 5px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--paper-3);
  box-shadow: var(--shadow-sm);
  font-size: 0.78rem; font-weight: 500; color: var(--ink); max-width: 100%;
  animation: ad-fchip-in .25s var(--ease) backwards;
}
@keyframes ad-fchip-in { from { opacity: 0; transform: translateY(4px) scale(0.96); } }
.appdemo .agent-fchip__thumb {
  width: 24px; height: 24px; flex: none;
  border-radius: 8px; border: 1px solid var(--mint-line);
  overflow: hidden; display: block;
}
.appdemo .agent-fchip__thumb .ad-shot { width: 100%; height: 100%; display: block; }
.appdemo .agent-fchip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.appdemo .agent-fchip em {
  font-style: normal; font-family: var(--font-mono); font-size: 9px;
  letter-spacing: 0.04em; white-space: nowrap;
  padding: 2px 7px; border-radius: 999px;
  color: var(--green-700); background: var(--mint); border: 1px solid var(--mint-line);
}
.appdemo .agent-fchip.is-reading em { animation: ad-fchip-pulse 1.1s ease-in-out infinite; }
@keyframes ad-fchip-pulse { 50% { opacity: 0.45; } }
.appdemo .agent-fchip button { width: 15px; height: 15px; display: grid; place-items: center; border-radius: 50%; color: var(--ink-faint); font-size: 13px; line-height: 1; }

/* ---------- composer ---------- */
.appdemo .agent__composer {
  position: relative; flex: none;
  display: flex; flex-direction: column; align-items: stretch; gap: 6px;
  padding: 9px 10px 8px; margin: 0 0 2px;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--paper-3);
  box-shadow: 0 8px 24px rgba(31,79,55,0.05);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.appdemo .agent__composer.is-focus {
  border-color: var(--green-300);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--green) 10%, transparent);
}
.appdemo .agent__text {
  width: 100%; min-height: 22px;
  font-family: var(--font-sans); font-size: 0.94rem; line-height: 1.5;
  color: var(--ink); padding: 4px 4px 0;
  white-space: pre-wrap; word-break: break-word;
}
.appdemo .agent__text.is-empty::before { content: attr(data-placeholder); color: var(--ink-faint); }
/* the app's textarea has a native caret; the demo draws one so the typing
   doesn't read as text being pasted in */
.appdemo .agent__composer.is-focus .agent__text:not(.is-empty)::after {
  content: ""; display: inline-block;
  width: 1.5px; height: 1.05em; margin-left: 1px; vertical-align: -0.18em;
  background: var(--ink);
  animation: ad-caret 1s steps(2) infinite;
}
.appdemo .agent__hint {
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .03em;
  color: var(--ink-faint); text-align: right;
  max-height: 0; opacity: 0; overflow: hidden;
  transition: max-height .2s var(--ease), opacity .2s var(--ease);
}
.appdemo .agent__composer.is-focus .agent__hint { max-height: 16px; opacity: .7; }

.appdemo .agent__bar { display: flex; align-items: center; gap: 5px; }
.appdemo .agent__bar-sp { flex: 1; min-width: 0; }
.appdemo .agent__tool {
  width: 30px; height: 30px; flex: none;
  display: grid; place-items: center; border-radius: 9px;
  color: var(--ink-faint);
  transition: background .15s var(--ease), color .15s var(--ease);
}
.appdemo .agent__tool svg { width: 18px; height: 18px; }
.appdemo .agent__tool.is-press { background: var(--mint); color: var(--green-700); }

.appdemo .agent__modewrap { position: relative; display: inline-flex; }
.appdemo .agent__modechip {
  display: inline-flex; align-items: center; gap: 3px;
  height: 28px; padding: 0 8px; border-radius: 8px;
  border: 1px solid var(--line); color: var(--ink-soft);
  font-family: var(--font-sans); font-size: 12px;
}
.appdemo .agent__modechip svg { width: 13px; height: 13px; color: var(--ink-faint); }

.appdemo .agent__review {
  display: inline-flex; align-items: center; gap: 6px;
  height: 28px; padding: 0 10px; border-radius: 999px;
  border: 1px solid var(--line); color: var(--ink-faint);
  font-family: var(--font-sans); font-size: 11.5px;
}
.appdemo .agent__review i { width: 6px; height: 6px; border-radius: 50%; background: var(--ink-faint); flex: none; }
.appdemo .agent__review.is-on { color: var(--green-700); border-color: var(--mint-line); background: var(--mint); }
.appdemo .agent__review.is-on i { background: var(--green); }

.appdemo .agent__send {
  width: 32px; height: 32px; flex: none;
  display: grid; place-items: center; border-radius: 10px;
  border: 1px solid var(--line); color: var(--ink-faint);
  transition: background .18s var(--ease), color .18s var(--ease), border-color .18s var(--ease), transform .15s var(--ease);
}
.appdemo .agent__send svg { width: 17px; height: 17px; }
.appdemo .agent__send.is-ready { background: var(--green); border-color: var(--green); color: #fff; }
.appdemo .agent__send.is-press { transform: translateY(-1px) scale(0.95); }

/* ============================================================
   Main workspace  (app .main / .crumb / .canvas / .box)
   ============================================================ */
.appdemo .ad-main { position: relative; flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; }

.appdemo .crumb {
  height: var(--ad-crumb); flex: none;
  display: flex; align-items: center; gap: 8px;
  padding: 0 16px;
  border-bottom: 1px solid var(--line-soft);
  background: rgba(251,250,244,0.6);
  backdrop-filter: blur(8px);
  font-family: var(--font-mono); font-size: 12px;
}
.appdemo .crumb__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex: none; }
.appdemo .crumb__seg { color: var(--ink-soft); }
.appdemo .crumb__seg.is-class { color: var(--accent); }
.appdemo .crumb__sep { color: var(--line); }
.appdemo .crumb__title { color: var(--ink); font-weight: 500; }

/* the `.canvas` name is shared with the marketing mock in styles.css —
   restate every property that rule sets so nothing leaks through */
.appdemo .canvas {
  flex: 1; position: relative; overflow: hidden;
  padding: 0; min-height: 0;
  background-color: var(--paper);
  background-image:
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px),
    linear-gradient(var(--grid-major) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-major) 1px, transparent 1px);
  background-size: 28px 28px, 28px 28px, 140px 140px, 140px 140px;
}
.appdemo .ad-layer { position: absolute; top: 0; left: 0; transform-origin: 0 0; }

.appdemo .box {
  position: absolute;
  background: var(--paper-3);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--shadow-sm);
  display: flex;
  overflow: visible;
  transition: box-shadow .15s var(--ease);
}
.appdemo .box--placing { opacity: 0; }
.appdemo .box__content {
  flex: none; width: 100%;
  padding: 11px 13px;
  white-space: pre-wrap;
  font-family: var(--font-sans);
  font-size: 13px; line-height: 1.5; color: var(--ink);
  border-radius: 12px;
}
.appdemo .box--math { align-items: center; justify-content: center; }
.appdemo .box__math-display {
  width: 100%; text-align: center;
  padding: 8px 12px;
  color: var(--ink);
  white-space: nowrap;
  font-size: 16px;
}

/* math typesetting — mirrors the app's KaTeX display box without pulling
   KaTeX (and a CSP exception) onto the marketing page */
.appdemo .m { font-family: var(--font-serif); font-style: italic; letter-spacing: .01em; }
.appdemo .m .up { font-style: normal; }
.appdemo .m .op { font-style: normal; color: var(--ink); padding: 0 .16em; }
.appdemo .m .int { font-style: normal; font-size: 1.5em; line-height: 0; vertical-align: -0.18em; padding-right: .06em; }
.appdemo .m sup { font-size: .68em; vertical-align: .48em; line-height: 0; }
.appdemo .m .thin { display: inline-block; width: .18em; }

/* the agent's box entrance + "I just placed this" ring — copied verbatim
   from app.css so the landing demo and the app share one signature */
.appdemo .box--spawn { animation: ad-box-spawn .52s cubic-bezier(.22,1.2,.36,1) backwards; }
@keyframes ad-box-spawn {
  from { opacity: 0; transform: scale(0.9) translateY(8px); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}
.appdemo .box--pop { animation: ad-box-pop .42s var(--spring) both; }
@keyframes ad-box-pop { from { opacity: 0; transform: scale(.82); } 65% { transform: scale(1.015); } }

.appdemo .box--flash { animation: ad-ai-bloom 3s var(--ease) forwards; }
@keyframes ad-ai-bloom {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--glow) 0%, transparent), var(--shadow-md); }
  10%  { box-shadow: 0 0 30px 3px color-mix(in srgb, var(--glow) 48%, transparent), var(--shadow-md); }
  68%  { box-shadow: 0 0 22px 2px color-mix(in srgb, var(--glow) 32%, transparent), var(--shadow-md); }
  100% { box-shadow: var(--shadow-sm); }
}
.appdemo .box--flash::before {
  content: "";
  position: absolute;
  inset: -2.5px;
  border-radius: 14.5px;
  padding: 2.5px;
  background: linear-gradient(115deg, var(--glow) 0%, #25b083 20%, #5ad7ad 38%, #34c2cf 56%, #25b083 76%, var(--glow) 100%);
  background-size: 200% 100%;
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  z-index: 4;
  animation: ad-ai-ring 3s var(--ease) forwards, ad-ai-flow 1.5s linear infinite;
}
@keyframes ad-ai-ring {
  0%   { opacity: 0; transform: scale(1.05); }
  14%  { opacity: 1; transform: scale(1); }
  68%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1); }
}
@keyframes ad-ai-flow {
  from { background-position: 0% 50%; }
  to   { background-position: 200% 50%; }
}

/* ============================================================
   Editing a text box  (app .box.editing + .textbar)

   Everything below is the real in-app editor: double-clicking a
   text box turns it into a contenteditable and floats the
   formatting toolbar above it. Copied rule-for-rule from
   app.css so the hero shows the actual editing chrome.
   ============================================================ */
.appdemo .box.editing { cursor: text; z-index: 9500; }
/* the app hands the box a native caret (placeCaretEnd); the demo draws one so
   edit mode reads as "there is a cursor in here", not as a styling change */
.appdemo .box.editing .box__content::after {
  content: ""; display: inline-block;
  width: 1.5px; height: 1.05em; margin-left: 1px; vertical-align: -0.18em;
  background: var(--ink);
  animation: ad-caret 1s steps(2) infinite;
}
/* …and drops it the moment a selection exists, like a real caret does */
.appdemo .box.editing.is-selecting .box__content::after { display: none; }

/* the drag selection. styles.css sets ::selection to a solid green for the
   whole site (the app loads it too), but a solid fill would bury the very
   thing this beat is showing — the colour and highlight landing underneath.
   The app's own editable code box tints instead of filling, so follow that. */
.appdemo .ad-sel {
  background: color-mix(in srgb, var(--green) 22%, transparent);
  border-radius: 2px;
}

/* Same bar, same order, same look — at hero scale. The app's toolbar is 532px
   wide, and the hero's whole preview is 575px, so it runs at ~0.82 like every
   other piece of chrome in this file (--ad-tb 56 vs the app's 64, --ad-sb 268
   vs 274). Full size would leave the camera nowhere to stand. */
.appdemo .textbar {
  position: absolute; bottom: 100%; left: 0;
  margin-bottom: 9px;
  display: flex; align-items: center; gap: 2px;
  padding: 4px 6px;
  background: rgba(251,250,244,0.96);
  backdrop-filter: saturate(1.3) blur(10px);
  -webkit-backdrop-filter: saturate(1.3) blur(10px);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: var(--shadow-md);
  white-space: nowrap;
  cursor: default;
  user-select: none;
  z-index: 6;
  /* it's a picture of a toolbar — the ghost cursor drives it, not the visitor */
  pointer-events: none;
  animation: ad-rise-sm .22s var(--ease-out-q, var(--ease));
}
@keyframes ad-rise-sm { from { opacity: 0; transform: translateY(5px) scale(.98); } }
.appdemo .textbar__btn {
  min-width: 21px; height: 21px; padding: 0 5px;
  display: inline-grid; place-items: center;
  border-radius: 6px;
  font-family: var(--font-sans); font-size: 11.5px; color: var(--ink-soft);
  transition: background .12s var(--ease), color .12s var(--ease), transform .12s var(--ease);
}
/* the ghost cursor can't trigger :hover, so it sets .is-hover — same look */
.appdemo .textbar__btn.is-hover { background: var(--paper-2); color: var(--ink); }
.appdemo .textbar__btn.is-on { background: var(--accent-soft); color: var(--accent); }
.appdemo .textbar__btn.textbar__swatch {
  min-width: 0; width: 14px; height: 14px; padding: 0; margin: 0 1px;
  border-radius: 50%;
  background: var(--sw);
  border: 1px solid rgba(0,0,0,0.14);
}
.appdemo .textbar__btn.textbar__swatch.is-hover { background: var(--sw); transform: scale(1.18); }
.appdemo .textbar__btn.textbar__swatch--text { border-radius: 4px; }
.appdemo .textbar__btn.textbar__clear { font-size: 9.5px; color: var(--ink-faint); }
.appdemo .textbar__div { width: 1px; height: 15px; background: var(--line); margin: 0 3px; flex: none; }
.appdemo .textbar__font {
  height: 21px; padding: 0 3px;
  font-family: var(--font-mono); font-size: 9.5px; color: var(--ink-soft);
  background: var(--paper-3); border: 1px solid var(--line); border-radius: 6px;
  outline: none; cursor: pointer;
}
.appdemo .textbar__size {
  font-family: var(--font-mono); font-size: 10px; color: var(--ink-soft);
  min-width: 20px; text-align: center;
}

/* ============================================================
   Selection + Agent attachment  (app .box.is-selected /
   .box.is-attached)

   Two rings with the same 2px geometry: selection is a flat
   accent ring, attachment is a live green→teal→blue gradient
   that flows. Built as a masked pseudo-ring for the same reason
   the app does it — a box-shadow can't carry a gradient.
   ============================================================ */
.appdemo .box.is-selected { box-shadow: 0 0 0 2px var(--accent), var(--shadow-md); }
.appdemo .box.is-selected, .appdemo .box.editing { z-index: 9500; }

.appdemo .box.is-attached { box-shadow: 0 6px 22px -12px rgba(48,131,145,0.40), var(--shadow-md); }
.appdemo .box.is-attached::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 14px;          /* box radius (12) + 2px ring offset */
  padding: 2px;
  background: linear-gradient(105deg,
    #287e68 0%, #319b7c 28%, #347f91 50%, #3a8fb0 64%, #319b7c 82%, #287e68 100%);
  background-size: 220% 100%;
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  animation: ad-attach-flow 7s ease-in-out infinite alternate;
  pointer-events: none;
  z-index: 3;
}
@keyframes ad-attach-flow { from { background-position: 0% 50%; } to { background-position: 100% 50%; } }
/* the ring lands with the attach, rather than snapping on */
.appdemo .box.is-attached.ad-just-attached::after { animation: ad-attach-in .5s var(--ease) backwards, ad-attach-flow 7s ease-in-out infinite alternate; }
@keyframes ad-attach-in { from { opacity: 0; transform: scale(1.06); } }

/* ============================================================
   Context menu  (app .ctx — contextmenu.js openBoxMenu)

   Item for item and in the same order the app builds for a
   single editable box: Edit, Attach, Duplicate, Bring to front,
   Unselect, rule, Delete. Nothing here is live; the ghost cursor
   drives it, so it's pointer-events: none like the textbar.
   ============================================================ */
.appdemo .ctx {
  position: absolute; z-index: 55;
  min-width: 168px;
  padding: 6px;
  background: var(--paper-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  pointer-events: none;
  transform-origin: top left;
  animation: ad-ctx-in .16s var(--ease);
}
@keyframes ad-ctx-in { from { opacity: 0; transform: scale(.96) translateY(-3px); } }
.appdemo .ctx[hidden] { display: none; }
.appdemo .ctx__item {
  display: flex; align-items: center; gap: 9px;
  width: 100%; padding: 6px 9px;
  border-radius: 8px;
  font-family: var(--font-sans); font-size: 12px; color: var(--ink);
  text-align: left; white-space: nowrap;
  transition: background .2s var(--ease), color .2s var(--ease);
}
.appdemo .ctx__item svg { width: 14px; height: 14px; color: var(--ink-faint); flex: none; }
/* the ghost cursor can't trigger :hover — same look, set by hand */
.appdemo .ctx__item.is-hover { background: var(--mint); color: var(--green-700); }
.appdemo .ctx__item.is-hover svg { color: var(--green-700); }
.appdemo .ctx__item--danger { color: #b3463c; }
.appdemo .ctx__item--danger svg { color: #b3463c; }
.appdemo .ctx__div { height: 1px; background: var(--line-soft); margin: 5px 4px; }

/* ============================================================
   Editing a math box  (app .box--math.editing + .box__math-edit)

   The app swaps the KaTeX display for a MathLive field, which
   typesets as you type — so the demo reveals the equation
   already typeset, placeholder box and all, rather than
   spelling out LaTeX the student would never see.
   ============================================================ */
/* math boxes hug their rendered content in the app (autoSizeMath), so the
   ones the demo grows while typing size to content instead of a fixed width */
.appdemo .box.ad-fit { width: max-content; }
.appdemo .box.ad-fit .box__math-display { width: auto; }
.appdemo .box__math-display .box__ph {
  font-family: var(--font-mono); font-size: 0.72em; color: var(--ink-faint);
}
.appdemo .box__math-edit {
  min-width: 110px; min-height: 44px;
  box-sizing: border-box;
  padding: 8px 14px;
  display: flex; align-items: center; justify-content: center;
  white-space: nowrap;
  color: var(--ink); font-size: 16px;
}
/* MathLive draws its own caret inside the field */
.appdemo .ad-mcaret {
  display: inline-block; width: 1.5px; height: 1.1em; margin-left: 1px;
  vertical-align: -0.2em; background: var(--green);
  animation: ad-caret 1s steps(2) infinite;
}
/* \placeholder{} — the dashed slot MathLive parks the caret in */
.appdemo .m .ph {
  display: inline-block; width: .62em; height: .72em;
  vertical-align: -0.04em; margin: 0 .06em;
  border: 1px dashed var(--green); border-radius: 2px;
  background: color-mix(in srgb, var(--green) 8%, transparent);
}

/* ---------- typesetting the display box can't get from KaTeX ---------- */
/* integral with limits stacked on the sign */
.appdemo .m .intg { display: inline-flex; align-items: center; font-style: normal; vertical-align: -0.32em; }
.appdemo .m .intg > .sym { font-size: 2em; line-height: .74; font-style: normal; }
.appdemo .m .intg > .lims {
  display: inline-flex; flex-direction: column; justify-content: space-between;
  align-self: stretch; margin-left: .04em;
  font-size: .5em; line-height: 1; font-style: normal;
}
/* fraction — the app gets this from KaTeX \frac */
.appdemo .m .frac {
  display: inline-flex; flex-direction: column; align-items: center;
  vertical-align: -0.46em; margin: 0 .18em;
}
.appdemo .m .frac > .num { padding: 0 .3em .09em; }
.appdemo .m .frac > .den { padding: .11em .3em 0; border-top: 1px solid currentColor; }
/* radical — √ glyph plus an overline on the radicand (\sqrt) */
.appdemo .m .rad { display: inline-flex; align-items: flex-start; }
.appdemo .m .rad > .sr { font-style: normal; font-size: 1.15em; line-height: 1; }
.appdemo .m .rad > .rc { border-top: 1px solid currentColor; padding: .06em .1em 0; margin-top: .12em; }

/* ============================================================
   Demo furniture — ghost cursor + chapter rail
   ============================================================ */
.appdemo .ghost-cursor {
  position: absolute; top: 0; left: 0; z-index: 60;
  width: 20px; height: 20px;
  margin: -3px 0 0 -3px;
  opacity: 0; pointer-events: none;
  filter: drop-shadow(0 3px 7px rgba(16,40,28,0.3));
  /* --ad-cur lets a move set its own pace: long travel across the app reads as
     .62s, a hop between two toolbar buttons as .34s, a drag as 0 (hand-driven) */
  transition: left var(--ad-cur, .62s) var(--ease), top var(--ad-cur, .62s) var(--ease),
              opacity .22s var(--ease), transform .14s var(--ease);
}
/* styles.css puts a drop-shadow on the svg; the wrapper carries it here so the
   ring pseudo-element isn't shadowed too — cancel the inherited one */
.appdemo .ghost-cursor svg { width: 100%; height: 100%; display: block; filter: none; }
.appdemo .ghost-cursor.is-visible { opacity: 1; }
/* An armed placement tool turns the real canvas cursor into a crosshair
   (app: .canvas.tool-place) — and a crosshair points from its centre, not
   from a tip, so the glyph re-centres on the hotspot when it swaps.

   Both glyphs are taken out of flow: as static blocks they stack, and the
   hidden one would sit a full 20px below the visible one. The `svg` in these
   selectors is load-bearing — the rule above is `.appdemo .ghost-cursor svg`,
   so a two-class selector loses to it and neither glyph would ever hide. */
.appdemo .ghost-cursor__arrow, .appdemo .ghost-cursor__cross { position: absolute; inset: 0; }
.appdemo .ghost-cursor svg.ghost-cursor__cross { display: none; }
.appdemo .ghost-cursor.is-cross { margin: -10px 0 0 -10px; }
.appdemo .ghost-cursor.is-cross svg.ghost-cursor__arrow { display: none; }
.appdemo .ghost-cursor.is-cross svg.ghost-cursor__cross { display: block; }
.appdemo .ghost-cursor.is-click { transform: scale(0.82); }
/* button held down through a drag-select — pressed, but no click ring */
.appdemo .ghost-cursor.is-down { transform: scale(0.88); }
.appdemo .ghost-cursor::after {
  content: ""; position: absolute; inset: -6px;
  border-radius: 50%; border: 1.5px solid var(--green);
  opacity: 0; transform: scale(0.4);
}
.appdemo .ghost-cursor.is-click::after { animation: ad-click-ring .45s var(--ease); }
@keyframes ad-click-ring {
  from { opacity: .7; transform: scale(0.4); }
  to   { opacity: 0; transform: scale(1.5); }
}

/* Chapter rail — sits low over the canvas, inside .ad-main, so it can never
   overlap the sidebar's profile row (and it follows the canvas when focus
   mode widens it). Quiet enough to read as a progress hint, not a control. */
.appdemo .ad-rail {
  position: absolute; left: 50%; bottom: 12px; z-index: 20;
  transform: translateX(-50%);
  display: flex; align-items: center; gap: 7px;
  padding: 7px 12px; border-radius: 999px;
  background: rgba(251,250,244,0.9);
  backdrop-filter: saturate(1.3) blur(10px);
  -webkit-backdrop-filter: saturate(1.3) blur(10px);
  border: 1px solid var(--line-soft);
  box-shadow: var(--shadow-sm);
  opacity: .92;
  transition: opacity .2s var(--ease);
}
.appdemo .ad-rail:hover { opacity: 1; }
.appdemo .ad-rail[hidden] { display: none; }
/* The visible dash is small on purpose — the clickable area isn't. Vertical
   padding + a negative margin give each step a ~24px tall hit target without
   changing how the rail reads or how tall it sits. */
.appdemo .ad-rail__step {
  position: relative; width: 26px; height: 6px; border-radius: 999px;
  background: var(--mint-line); cursor: pointer;
  /* content-box is load-bearing: the sheet's global border-box would fold the
     padding into the 6px and leave a zero-height track */
  box-sizing: content-box;
  padding: 9px 0; margin: -9px 0; background-clip: content-box;
  transition: width .3s var(--ease), background-color .18s var(--ease);
}
.appdemo .ad-rail__step:hover { background-color: var(--green-300); }
.appdemo .ad-rail__step:focus-visible {
  outline: 2px solid var(--green); outline-offset: 3px; border-radius: 999px;
}
.appdemo .ad-rail__step.is-active { width: 46px; }
/* the fill rides the same content box as the track, so it can't spill into the
   padded hit area */
.appdemo .ad-rail__step i {
  position: absolute; inset: 9px 0; width: 0%;
  background: var(--green); border-radius: 999px; display: block;
}

/* off-screen / hidden tab: freeze every running animation with the clock */
.appdemo.is-paused .ad-shell *, .appdemo.is-paused .tb * { animation-play-state: paused !important; }

/* ============================================================
   Fallbacks: reduced motion, small screens, print
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .appdemo *, .appdemo *::before, .appdemo *::after {
    transition: none !important;
    animation: none !important;
  }
  .appdemo .ghost-cursor { display: none; }
  .appdemo .ad-rail { display: none; }
}

@media (max-width: 1080px) {
  .appdemo { --ad-sb: 250px; }
}
/* ============================================================
   Fitting the frame to the page

   The demo is a picture of the DESKTOP app, so it never reflows into
   a phone layout — below the width the app needs, it scales instead.
   Sidebar beside canvas, same story, same camera, same eight
   chapters, just smaller: a visitor on a phone sees what a visitor on
   a laptop sees.

   hero-demo.js owns the numbers (there is no breakpoint here — it
   compares the room it has against the frame it needs) and sets
   --ad-scale / --ad-frame-w / --ad-fit-h. The wrapper carries the
   scaled height, because a transform doesn't take up any room and the
   page would otherwise leave a hole the size of the full frame.
   ============================================================ */
.preview-fit { position: relative; min-width: 0; }
.preview-fit.is-scaled { height: var(--ad-fit-h); }
/* Taking the frame out of flow is what keeps the fit honest. In flow, its
   pinned --ad-frame-w becomes the grid column's minimum, the column grows to
   match, and the next measurement reads that back — a scale feeding its own
   input, oscillating forever. Out of flow the wrapper is sized by the page
   alone, which is the only width worth measuring. */
.preview-fit.is-scaled > .appdemo {
  position: absolute; top: 0; left: 0;
  width: var(--ad-frame-w);
  transform: scale(var(--ad-scale));
  transform-origin: top left;
}
