/* ==========================================================================
   Live Scenes — devices and screens for scripted product demos.

   Two rules hold this file together:
   1. One material. Every device is cut from --metal, so a phone and a laptop
      in the same picture look like they came from the same world.
   2. One knob. Each device takes --dv-w and derives everything else from the
      real hardware's proportions, so a device is never "drawn at a size".

   Timings live here too, as tokens. Change the pace of every demo on the site
   by editing seven values, not seven hundred.
   ========================================================================== */

.sc {
  /* motion */
  --sc-t-fast: 220ms;
  --sc-t-base: 420ms;
  --sc-t-slow: 780ms;
  --sc-ease: cubic-bezier(.22, .61, .36, 1);
  --sc-dwell: 2800ms;   /* how long a caption is held */
  --sc-beat: 550ms;     /* the silence between an action and its reaction */
  --sc-type: 58ms;      /* per character */

  /* material */
  /* Two treatments of one material, because that is how the real things are:
     a phone's rail is a polished band and catches light; a laptop body is a
     large matte surface. Using the polished one across a wide panel is what
     made it read as a beige plank. */
  --metal: linear-gradient(150deg, #cdd0d5 0%, #6d727b 16%, #b0b5bd 34%,
                           #4b505a 56%, #b8bdc5 74%, #5b606a 100%);
  --metal-body: linear-gradient(180deg, #4c515a 0%, #34383f 55%, #23262b 100%);
  --sc-screen: #05070c;
  --sc-shadow: 0 60px 120px rgba(0, 0, 0, .78), 0 10px 30px rgba(0, 0, 0, .55);
}

/* ---- shared device shell ------------------------------------------------ */
.dv {
  /* Size comes from the scene, unless the page it sits on says otherwise.
     Context wins, because the scene's value arrives as an inline style and
     nothing in a stylesheet can outrank that directly. */
  --dv-w: var(--dv-w-ctx, var(--dv-w-auto, 260px));
  position: relative;
  box-sizing: border-box;
  background: var(--metal);
  box-shadow: var(--sc-shadow);
}
.dv * { box-sizing: border-box; }

.dv-screen {
  position: relative;
  overflow: hidden;
  background: var(--sc-screen);
  display: flex;
  flex-direction: column;
}
.dv-screen > img { display: block; width: 100%; height: auto; }

/* ---- phone: iPhone 15 Pro Max proportions -------------------------------
   screen 430x932pt · device radius 58pt · screen radius 55pt
   Dynamic Island 125x36pt, 11pt below the top of the screen.
   Every number below is that measurement divided by 430.
   ------------------------------------------------------------------------ */
.dv--phone {
  --dv-ratio: 2.1674;
  --dv-band: calc(var(--dv-w) * .0128);
  width: var(--dv-w);
  height: calc(var(--dv-w) * var(--dv-ratio));
  border-radius: calc(var(--dv-w) * .1349);
  padding: var(--dv-band);
}
.dv--phone .dv-screen {
  width: 100%;
  height: 100%;
  border-radius: calc(var(--dv-w) * .1279);
}
.dv-island {
  position: absolute;
  left: 50%;
  top: calc(var(--dv-w) * .0256);
  transform: translateX(-50%);
  width: calc(var(--dv-w) * .2907);
  height: calc(var(--dv-w) * .0837);
  border-radius: calc(var(--dv-w) * .0419);
  background: #000;
  z-index: 6;
}
/* side buttons: volume on the left, power on the right */
.dv--phone::before,
.dv--phone::after {
  content: "";
  position: absolute;
  background: var(--metal);
  border-radius: 2px;
}
.dv--phone::before {
  left: calc(var(--dv-band) * -1);
  top: 24.5%;
  width: calc(var(--dv-band) * 1.1);
  height: 14%;
}
.dv--phone::after {
  right: calc(var(--dv-band) * -1);
  top: 25.5%;
  width: calc(var(--dv-band) * 1.1);
  height: 9.4%;
}

/* ---- laptop: lid, chin, and a foot wider than the lid -------------------- */
.dv--laptop {
  width: var(--dv-w);
  /* Thin bezel, thin chin. The old numbers were more than twice these and the
     lid read as a frame with a screen in it rather than a screen. */
  padding: calc(var(--dv-w) * .010) calc(var(--dv-w) * .010) calc(var(--dv-w) * .019);
  border-radius: calc(var(--dv-w) * .016) calc(var(--dv-w) * .016)
                 calc(var(--dv-w) * .005) calc(var(--dv-w) * .005);
  background: var(--metal-body);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .13);
}
.dv--laptop .dv-screen {
  aspect-ratio: 16 / 10;
  border-radius: calc(var(--dv-w) * .005);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .65);
}
.dv-base {
  display: block;
  position: relative;
  left: 50%;
  transform: translateX(-50%);
  /* Barely wider than the lid, and thin. A foot sticking out an eighth of the
     machine's width is what made it look like furniture. */
  width: 104%;
  height: calc(var(--dv-w) * .014);
  border-radius: 0 0 calc(var(--dv-w) * .010) calc(var(--dv-w) * .010);
  background: linear-gradient(180deg, #585d66 0%, #34383f 60%, #1e2126 100%);
  box-shadow: var(--sc-shadow);
}
.dv-base::after {                        /* the notch you open the lid by */
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  transform: translateX(-50%);
  width: 11%;
  height: 44%;
  border-radius: 0 0 calc(var(--dv-w) * .006) calc(var(--dv-w) * .006);
  background: rgba(0, 0, 0, .45);
}

/* ---- browser: window chrome only, for when a physical body is too much --- */
.dv--browser {
  width: var(--dv-w);
  border-radius: 14px;
  overflow: hidden;
  background: #0a0f18;
  border: 1px solid rgba(255, 255, 255, .14);
}
.dv--browser .dv-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 9px 13px;
  background: #111826;
  border-bottom: 1px solid rgba(255, 255, 255, .09);
}
.dv--browser .dv-bar i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #4b5563;
}
.dv--browser .dv-bar i:nth-child(1) { background: #ff5f57; }
.dv--browser .dv-bar i:nth-child(2) { background: #febc2e; }
.dv--browser .dv-bar i:nth-child(3) { background: #28c840; }
.dv--browser .dv-bar span {
  margin-left: 10px;
  font: 500 11.5px/1 Inter, system-ui, sans-serif;
  color: #8fa0b8;
  background: #0a0f18;
  border: 1px solid rgba(255, 255, 255, .09);
  border-radius: 20px;
  padding: 5px 12px;
}

/* ---- light mode ---------------------------------------------------------
   The devices stay dark on purpose: a product screenshot is a product
   screenshot in either theme. Only the shadow softens, because a heavy black
   shadow on a pale page reads as dirt.
   ------------------------------------------------------------------------ */
[data-bs-theme="light"] .sc,
body.light-mode .sc {
  --sc-shadow: 0 40px 90px rgba(15, 23, 42, .22), 0 8px 20px rgba(15, 23, 42, .14);
}

@media (prefers-reduced-motion: reduce) {
  .sc *,
  .sc *::before,
  .sc *::after {
    animation: none !important;
    transition: none !important;
  }
}

/* ==========================================================================
   The scene itself: stage, captions, controls, and the things that get said.
   ========================================================================== */

.sc-stage {
  display: flex;
  align-items: stretch;
  justify-content: center;
  gap: clamp(16px, 3vw, 44px);
  flex-wrap: wrap;
}

.sc-actor {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;   /* labels line up along the top, devices along the bottom */
  gap: 12px;
}

.sc-actor-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .14);
  color: #dbe4f0;
  font: 600 12.5px/1 Inter, system-ui, sans-serif;
  white-space: nowrap;
}

/* the message travelling from one actor to the other */
.sc-relay {
  align-self: center;
  position: relative;
  width: clamp(40px, 6vw, 120px);
  height: 2px;
  background: repeating-linear-gradient(90deg,
              rgba(46, 166, 255, .5) 0 6px, transparent 6px 12px);
  opacity: 0;
  transition: opacity var(--sc-t-base) var(--sc-ease);
}
.sc.is-relaying .sc-relay { opacity: .9; }
.sc-relay i {
  position: absolute;
  top: -4px; left: 0;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: #2ea6ff;
  box-shadow: 0 0 14px rgba(46, 166, 255, .9);
  opacity: 0;
  transition: left var(--sc-t-slow) var(--sc-ease), opacity var(--sc-t-fast) linear;
}
.sc.is-relaying .sc-relay i { left: calc(100% - 10px); opacity: 1; }

/* what is happening, in words */
.sc-caption {
  margin: 20px auto 0;
  max-width: 62ch;
  text-align: center;
  color: #c6d3e4;
  font: 400 15px/1.5 Inter, system-ui, sans-serif;
  min-height: 3em;
  transition: opacity var(--sc-t-base) var(--sc-ease);
}

/* controls */
.sc-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 6px;
}
.sc-progress { display: flex; align-items: center; gap: 6px; }
.sc-dot {
  width: 26px; height: 6px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(148, 163, 184, .26);
  cursor: pointer;
  transition: background var(--sc-t-fast) var(--sc-ease),
              transform var(--sc-t-fast) var(--sc-ease);
}
.sc-dot:hover { transform: scaleY(1.6); }
.sc-dot.is-done { background: rgba(255, 107, 26, .55); }
.sc-dot.is-current { background: #ff6b1a; }
.sc-btn {
  border: 1px solid rgba(148, 163, 184, .3);
  background: rgba(255, 255, 255, .05);
  color: #cbd5e1;
  border-radius: 9px;
  padding: 7px 14px;
  font: 600 12.5px/1 Inter, system-ui, sans-serif;
  cursor: pointer;
  transition: background var(--sc-t-fast) var(--sc-ease), color var(--sc-t-fast) var(--sc-ease);
}
.sc-btn:hover { background: rgba(255, 255, 255, .12); color: #fff; }
.sc-btn-rate { min-width: 44px; }
/* A way on that leads back to the same demo is a broken promise. It exists only
   inside a set that actually holds more than one, and only once this one has
   finished. */
.sc-btn-next { display: none; }
.sc-set--many .sc-btn-next { display: inline-flex; opacity: .35; pointer-events: none; }
.sc-set--many .sc[data-sc-state="ended"] .sc-btn-next {
  opacity: 1;
  pointer-events: auto;
  border-color: rgba(255, 107, 26, .5);
  color: #ffb787;
}

/* ---- the Telegram screen ------------------------------------------------ */
.sc-tg { display: flex; flex-direction: column; height: 100%; }
/* A real phone reserves the space the Dynamic Island sits in. Without this the
   island lands on top of the conversation header. */
.dv--phone .sc-tg-top { padding-top: calc(var(--dv-w) * .13); }
.sc-tg-top {
  display: flex; align-items: center; gap: 9px;
  padding: 11px 13px;
  background: #0b111c;
  border-bottom: 1px solid rgba(255, 255, 255, .09);
  flex: 0 0 auto;
}
.sc-tg-ava {
  width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center;
  background: linear-gradient(140deg, #2ea6ff, #0d6efd);
  color: #fff; font-size: 13px;
}
.sc-tg-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sc-tg-meta b { font: 600 11.5px/1.2 Inter, system-ui, sans-serif; color: #eef2f8; }
.sc-tg-meta i {
  display: flex; align-items: center; gap: 5px;
  font: 400 9.5px/1 Inter, system-ui, sans-serif; color: #8ba0b8; font-style: normal;
}
.sc-tg-meta em { width: 5px; height: 5px; border-radius: 50%; background: #22e88c; }
.sc-tg-chat {
  flex: 1 1 auto;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: 7px;
  padding: 10px 11px;
  overflow: hidden;
  background: radial-gradient(120% 80% at 50% 0, #101a2a 0, #070c14 70%);
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 7%, #000 100%);
  mask-image: linear-gradient(180deg, transparent 0, #000 7%, #000 100%);
}
.sc-tg-input {
  flex: 0 0 auto;
  margin: 0 11px 14px;
  padding: 9px 12px;
  border-radius: 18px;
  background: #141c28;
  border: 1px solid rgba(255, 255, 255, .08);
  font: 400 10.5px/1 Inter, system-ui, sans-serif;
  color: #8496ad;
}

/* ---- a bubble ----------------------------------------------------------
   Nothing pops. Every bubble grows into place, which is also why max-height
   is animated: the column is anchored to the bottom, so a new message pushes
   the conversation up the way a real one does.
   ------------------------------------------------------------------------ */
.sc-bubble {
  /* Without this, flexbox squashes the conversation to fit the screen and every
     message ends up clipped. A chat overflows off the top instead. */
  flex: 0 0 auto;
  max-width: 84%;
  padding: 8px 11px;
  border-radius: 14px;
  font: 400 10.5px/1.4 Inter, system-ui, sans-serif;
  overflow-wrap: break-word;
  opacity: 0;
  max-height: 0;
  margin-top: -7px;
  transform: translateY(7px) scale(.97);
  overflow: hidden;
  transition: opacity var(--sc-t-base) var(--sc-ease),
              transform var(--sc-t-base) var(--sc-ease),
              max-height var(--sc-t-base) var(--sc-ease),
              margin-top var(--sc-t-base) var(--sc-ease);
}
.sc-bubble.is-on {
  opacity: 1;
  max-height: 320px;
  margin-top: 0;
  transform: none;
}
.sc-in { align-self: flex-start; background: #18202e; color: #e2e9f3; border-bottom-left-radius: 4px; }
.sc-out { align-self: flex-end; background: linear-gradient(140deg, #2ea6ff, #1178e0); color: #fff; border-bottom-right-radius: 4px; }
.sc-bubble.is-photo { padding: 4px; }
.sc-bubble.is-photo img { display: block; width: 100%; border-radius: 11px; }
.sc-bubble .sc-cap { display: block; padding: 4px 5px 2px; font-size: 9px; font-style: normal; color: rgba(255, 255, 255, .84); }
.sc-bubble.is-card { display: flex; flex-direction: column; gap: 2px; }
.sc-bubble.is-doc { display: flex; align-items: center; gap: 8px; }
.sc-doc-ic { font-size: 1.5em; line-height: 1; }
.sc-doc-tx { display: flex; flex-direction: column; min-width: 0; }
/* .72 put this caption at 3.83:1 in both themes; it is still clearly the
   quieter line at .85 and it is now readable. */
.sc-doc-tx i { font-style: normal; opacity: .85; font-size: .92em; }
.sc-bubble.is-typing { display: flex; gap: 4px; align-items: center; padding: 11px 13px; }
.sc-bubble.is-typing i {
  width: 5px; height: 5px; border-radius: 50%; background: #7c8ca3;
  animation: scBlip 1.2s ease-in-out infinite;
}
.sc-bubble.is-typing i:nth-child(2) { animation-delay: .2s; }
.sc-bubble.is-typing i:nth-child(3) { animation-delay: .4s; }
@keyframes scBlip {
  0%, 60%, 100% { opacity: .35; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-2px); }
}

/* ---- the portal screen and the overlays drawn on top of it -------------- */
.sc-portal { position: relative; }
.sc-portal img { display: block; width: 100%; height: auto; aspect-ratio: 1280 / 800; }
.sc-ovl { position: absolute; inset: 0; pointer-events: none; }

/* ---- light mode --------------------------------------------------------- */
[data-bs-theme="light"] .sc-actor-label,
body.light-mode .sc-actor-label { background: rgba(15, 23, 42, .05); border-color: rgba(15, 23, 42, .14); color: #1e293b; }
[data-bs-theme="light"] .sc-caption,
body.light-mode .sc-caption { color: #334155; }
[data-bs-theme="light"] .sc-btn,
body.light-mode .sc-btn { border-color: rgba(15, 23, 42, .18); background: rgba(15, 23, 42, .04); color: #334155; }
[data-bs-theme="light"] .sc-btn:hover,
body.light-mode .sc-btn:hover { background: rgba(15, 23, 42, .09); color: #0f172a; }
[data-bs-theme="light"] .sc-dot,
body.light-mode .sc-dot { background: rgba(15, 23, 42, .18); }

/* A visitor who asked for less motion gets the finished picture and the bar. */
.sc.is-static .sc-bubble.is-on { opacity: 1; max-height: 240px; transform: none; }


/* ---- the green card that closes the story ------------------------------- */
.sc-bubble.is-lead {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: rgba(34, 232, 140, .13);
  border: 1px solid rgba(34, 232, 140, .34);
  color: #9fd9bd;
}
.sc-bubble.is-lead b { color: #7df3bd; }

/* ---- the operator's row, drawn over the real screenshot -----------------
   Every offset is a share of the picture, so it lands in the same place at
   any device size. The numbers come from the captured screen: the chat panel
   occupies 45.8%–74.6% across and opens 13.2% down.
   ------------------------------------------------------------------------ */
/* Anything drawn on a screenshot: the scene places it, this styles it. */
.sc-ovl-item {
  position: absolute;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity var(--sc-t-base) var(--sc-ease),
              transform var(--sc-t-base) var(--sc-ease);
}
.sc-ovl-item.is-on { opacity: 1; transform: none; }

.sc-cq {
  display: flex;
  gap: 3.5%;
  padding: 1.4% 1.6%;
  border-radius: 6px;
  background: #0b1120;
  box-shadow: 0 0 0 2px rgba(245, 158, 11, .55);
}
.sc-cq.is-unread { animation: scCqPulse 2.2s ease-in-out infinite; }
@keyframes scCqPulse {
  0%, 100% { box-shadow: 0 0 0 2px rgba(245, 158, 11, .55); }
  50%      { box-shadow: 0 0 0 7px rgba(245, 158, 11, .10); }
}
.sc-cq-av {
  position: relative;
  flex: 0 0 auto;
  align-self: flex-start;
  width: 10.5%;
  aspect-ratio: 1;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: linear-gradient(135deg, #3b82f6, #6366f1);
  color: #fff;
  font: 800 .8em/1 Inter, system-ui, sans-serif;
}
.sc-cq-av i {
  position: absolute;
  top: -22%; right: -22%;
  min-width: 52%;
  padding: 0 12%;
  border-radius: 999px;
  /* red-500 leaves a count badge at 3.8:1 and the screen's own default text
     colour takes it to 3.15; a deeper red carries the same meaning and reads. */
  background: #c81e1e;
  color: #fff;
  font: 800 .62em/1.7 Inter, system-ui, sans-serif;
  font-style: normal;
  text-align: center;
  box-shadow: 0 0 0 1.5px #0b1120;
}
.sc-cq-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.sc-cq-top { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
.sc-cq-top b { font: 700 1em/1.2 Inter, system-ui, sans-serif; color: #e7eaf0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sc-cq-top i { font: 400 .81em/1 Inter, system-ui, sans-serif; color: #94a3b8; font-style: normal; flex: 0 0 auto; }
.sc-cq-veh { font: 400 .86em/1.3 Inter, system-ui, sans-serif; color: #94a3b8; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sc-cq-snip { font: 400 .94em/1.32 Inter, system-ui, sans-serif; color: #cbd5e1; }
.sc-cq-pill {
  align-self: flex-start;
  margin-top: 3%;
  padding: 1.5% 5%;
  border-radius: 999px;
  font: 800 .74em/1.6 Inter, system-ui, sans-serif;
  letter-spacing: .3px;
  text-transform: uppercase;
  color: #f59e0b;
  background: rgba(245, 158, 11, .12);
  border: 1px solid rgba(245, 158, 11, .3);
}
/* 1em on the overlay is the portal's own 13px text, shrunk by the same amount
   as the screenshot it sits on. The picture is a 1280px-wide interface. */
.sc-portal { font-size: calc(var(--dv-w) / 1280 * 13); }

/* ==========================================================================
   A set: several demos in one place, one at a time.
   ========================================================================== */

.sc-set { max-width: 1180px; margin: 0 auto; }
.sc-set-title {
  margin: 0 0 10px;
  text-align: center;
  font: 700 clamp(24px, 3.2vw, 38px)/1.15 'Space Grotesk', Inter, system-ui, sans-serif;
  color: #f1f5f9;
}
.sc-set-intro {
  margin: 0 auto 26px;
  max-width: 62ch;
  text-align: center;
  color: #9aa6b8;
  font: 400 16px/1.6 Inter, system-ui, sans-serif;
}
.sc-set-tabs {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-bottom: 26px;
}
.sc-set-tab {
  border: 1px solid rgba(148, 163, 184, .26);
  background: rgba(255, 255, 255, .04);
  color: #9aa6b8;
  border-radius: 999px;
  padding: 8px 16px;
  font: 600 13px/1 Inter, system-ui, sans-serif;
  cursor: pointer;
  transition: color var(--sc-t-fast) var(--sc-ease),
              border-color var(--sc-t-fast) var(--sc-ease),
              background var(--sc-t-fast) var(--sc-ease);
}
.sc-set-tab:hover { color: #e2e8f0; border-color: rgba(148, 163, 184, .5); }
.sc-set-tab.is-on {
  color: #ffb787;
  border-color: rgba(255, 107, 26, .55);
  background: rgba(255, 107, 26, .12);
}

/* Only the demo being watched takes up room. The others are not merely
   invisible — they are out of the layout, so nothing reserves a hole. */
.sc-set-item { display: none; }
.sc-set-item.is-on { display: block; }

[data-bs-theme="light"] .sc-set-title,
body.light-mode .sc-set-title { color: #0f172a; }
[data-bs-theme="light"] .sc-set-intro,
body.light-mode .sc-set-intro { color: #475569; }
[data-bs-theme="light"] .sc-set-tab,
body.light-mode .sc-set-tab {
  border-color: rgba(15, 23, 42, .16);
  background: rgba(15, 23, 42, .03);
  color: #475569;
}
[data-bs-theme="light"] .sc-set-tab:hover,
body.light-mode .sc-set-tab:hover { color: #0f172a; border-color: rgba(15, 23, 42, .3); }
[data-bs-theme="light"] .sc-set-tab.is-on,
body.light-mode .sc-set-tab.is-on {
  color: #c2410c;
  border-color: rgba(255, 107, 26, .5);
  background: rgba(255, 107, 26, .10);
}

/* ==========================================================================
   A screen inside a device is a dark surface in BOTH themes — it is a picture
   of a product, not part of the page. The site's own light rules reach into it
   and blacken text sitting on near-black, so the scene's colours are asserted
   here rather than defended one selector at a time.

   Both selectors, deliberately: this site switches light with a class on the
   body, the product switches it with an attribute, and these files are shared.
   ========================================================================== */
[data-bs-theme="light"] .sc .sc-tg-meta b,
body.light-mode .sc .sc-tg-meta b { color: #eef2f8 !important; }
[data-bs-theme="light"] .sc .sc-tg-meta i,
body.light-mode .sc .sc-tg-meta i { color: #8ba0b8 !important; }
[data-bs-theme="light"] .sc .sc-tg-input,
body.light-mode .sc .sc-tg-input { color: #8496ad !important; background: #141c28 !important; }
[data-bs-theme="light"] .sc .sc-in,
body.light-mode .sc .sc-in { background: #18202e !important; color: #e2e9f3 !important; }
[data-bs-theme="light"] .sc .sc-out,
body.light-mode .sc .sc-out { color: #fff !important; }
[data-bs-theme="light"] .sc .sc-bubble.is-card b,
body.light-mode .sc .sc-bubble.is-card b,
[data-bs-theme="light"] .sc .sc-bubble.is-doc b,
body.light-mode .sc .sc-bubble.is-doc b { color: #fff !important; }
[data-bs-theme="light"] .sc .sc-bubble.is-card span,
body.light-mode .sc .sc-bubble.is-card span { color: #9db0c6 !important; }
[data-bs-theme="light"] .sc .sc-bubble.is-card .up,
body.light-mode .sc .sc-bubble.is-card .up { color: #22e88c !important; }
[data-bs-theme="light"] .sc .sc-bubble.is-card .dim,
body.light-mode .sc .sc-bubble.is-card .dim { color: #7d8ea4 !important; }
[data-bs-theme="light"] .sc .sc-bubble .sc-cap,
body.light-mode .sc .sc-bubble .sc-cap { color: rgba(255, 255, 255, .85) !important; }
[data-bs-theme="light"] .sc .sc-bubble.is-lead,
body.light-mode .sc .sc-bubble.is-lead { color: #9fd9bd !important; }
[data-bs-theme="light"] .sc .sc-bubble.is-lead b,
body.light-mode .sc .sc-bubble.is-lead b { color: #7df3bd !important; }
[data-bs-theme="light"] .sc .sc-doc-tx i,
body.light-mode .sc .sc-doc-tx i { color: #8ba0b8 !important; }
[data-bs-theme="light"] .sc .sc-cq,
body.light-mode .sc .sc-cq { background: #0b1120 !important; }
[data-bs-theme="light"] .sc .sc-cq-top b,
body.light-mode .sc .sc-cq-top b { color: #e7eaf0 !important; }
[data-bs-theme="light"] .sc .sc-cq-top i,
body.light-mode .sc .sc-cq-top i,
[data-bs-theme="light"] .sc .sc-cq-veh,
body.light-mode .sc .sc-cq-veh { color: #94a3b8 !important; }
[data-bs-theme="light"] .sc .sc-cq-snip,
body.light-mode .sc .sc-cq-snip { color: #cbd5e1 !important; }
[data-bs-theme="light"] .sc .sc-cq-pill,
body.light-mode .sc .sc-cq-pill { color: #f59e0b !important; }
[data-bs-theme="light"] .sc .sc-cq-av,
body.light-mode .sc .sc-cq-av { color: #fff !important; }


/* Everything unclassed inside a device screen — a <b> in a sentence, a <span>
   in a card — takes its colour from the thing it sits in. The site's light
   rules target bare tags, so listing our own selectors one at a time loses a
   race we cannot win; this defends the surface instead of each element on it. */
[data-bs-theme="light"] .sc .dv-screen *:not([class]),
body.light-mode .sc .dv-screen *:not([class]) { color: inherit !important; }
[data-bs-theme="light"] .sc .sc-doc-ic,
body.light-mode .sc .sc-doc-ic { color: inherit !important; }

/* ---- a callout on a real screenshot --------------------------------------
   Not a pretend interface: an annotation laid over a genuine screen, the way
   you would point at something on a monitor. It never imitates a control the
   product does not have.
   ------------------------------------------------------------------------ */
.sc-ovl-card {
  display: flex;
  flex-direction: column;
  gap: .18em;
  padding: .55em .7em;
  border-radius: .5em;
  background: rgba(8, 12, 20, .93);
  border: 1px solid rgba(255, 255, 255, .16);
  box-shadow: 0 .8em 2em rgba(0, 0, 0, .55);
  max-width: 34%;
}
.sc-ovl-card b { font: 700 1em/1.25 Inter, system-ui, sans-serif; color: #fff; }
.sc-ovl-card span { font: 400 .86em/1.35 Inter, system-ui, sans-serif; color: #b9c6d8; }
.sc-ovl-card.is-good { border-color: rgba(34, 232, 140, .45); }
.sc-ovl-card.is-good b { color: #7df3bd; }
.sc-ovl-card.is-warn { border-color: rgba(245, 158, 11, .5); }
.sc-ovl-card.is-warn b { color: #fbbf24; }
[data-bs-theme="light"] .sc .sc-ovl-card b,
body.light-mode .sc .sc-ovl-card b { color: #fff !important; }
[data-bs-theme="light"] .sc .sc-ovl-card span,
body.light-mode .sc .sc-ovl-card span { color: #b9c6d8 !important; }
[data-bs-theme="light"] .sc .sc-ovl-card.is-good b,
body.light-mode .sc .sc-ovl-card.is-good b { color: #7df3bd !important; }
[data-bs-theme="light"] .sc .sc-ovl-card.is-warn b,
body.light-mode .sc .sc-ovl-card.is-warn b { color: #fbbf24 !important; }


/* Two laptops at their own size do not fit the theatre's width and wrap into a
   column, which reads as two unrelated pictures rather than two sides of one
   story. In a set they are sized to sit beside each other. */
/* The theatre is 1180 wide and was drawing a 455px laptop in it, with four
   hundred pixels of nothing beside it. The screen is the product; give it the
   room. */
.sc-set .dv--laptop { --dv-w-ctx: min(700px, 58vw); }
.sc-set .dv--phone  { --dv-w-ctx: min(268px, 23vw); }
@media (max-width: 900px) {
  .sc-set .dv--laptop { --dv-w-ctx: min(455px, 86vw); }
  .sc-set .dv--phone  { --dv-w-ctx: min(230px, 55vw); }
}

/* ==========================================================================
   Motion that reads as a conversation rather than a slideshow.

   A message does not fade in from nowhere: it springs out of the corner it
   was sent from, it carries the time, and one you sent tells you it landed.
   A photograph arrives compressed and sharpens. The dots roll rather than
   blink in unison. All of it is transition-driven off .is-on, so seeking
   backwards through the step bar still rewinds cleanly.
   ========================================================================== */
.sc-bubble { transform: translateY(9px) scale(.9); transform-origin: 0 100%;
  transition: opacity 240ms var(--sc-ease),
              transform 430ms cubic-bezier(.18, 1.28, .42, 1),
              max-height var(--sc-t-base) var(--sc-ease),
              margin-top var(--sc-t-base) var(--sc-ease); }
.sc-bubble.sc-out { transform-origin: 100% 100%; }

/* the time, and the two ticks */
.sc-meta { display: block; margin-top: 3px; text-align: right; font-style: normal;
  font-size: 8.5px; line-height: 1; opacity: .62; }
.sc-out .sc-meta { opacity: .82; }
.sc-meta b { font-weight: 500; letter-spacing: .02em; }
.sc-ticks { position: relative; display: inline-block; width: 13px; height: 8px;
  margin-left: 5px; vertical-align: -1px; color: currentColor; }
.sc-ticks::before, .sc-ticks::after {
  content: ""; position: absolute; top: 1px; width: 6px; height: 3px;
  border-left: 1.3px solid currentColor; border-bottom: 1.3px solid currentColor;
  transform: rotate(-45deg); }
.sc-ticks::before { left: 0; }
.sc-ticks::after  { left: 5px; opacity: 0; }
.sc-bubble.is-on .sc-ticks::after { animation: scTickTwo .34s ease-out .9s forwards; }
.sc-bubble.is-on .sc-ticks { animation: scTickRead .3s ease-out 1.8s forwards; }
@keyframes scTickTwo  { to { opacity: 1; } }
@keyframes scTickRead { to { color: #a7dcff; } }

/* a picture arrives compressed and sharpens, the way one does over mobile data */
.sc-bubble.is-photo img { filter: blur(9px) saturate(.78); transform: scale(1.05);
  transition: filter 720ms ease .1s, transform 720ms ease .1s; }
.sc-bubble.is-photo.is-on img { filter: none; transform: none; }

/* the dots roll instead of blinking together */
.sc-bubble.is-typing i { animation: scBlipRoll 1.35s cubic-bezier(.4, 0, .2, 1) infinite; }
.sc-bubble.is-typing i:nth-child(2) { animation-delay: .16s; }
.sc-bubble.is-typing i:nth-child(3) { animation-delay: .32s; }
@keyframes scBlipRoll {
  0%, 58%, 100% { opacity: .3; transform: translateY(0) scale(.86); }
  26%           { opacity: 1;  transform: translateY(-3px) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .sc-bubble { transform: none; transition: opacity 1ms linear, max-height 1ms linear; }
  .sc-bubble.is-photo img { filter: none; transform: none; transition: none; }
  .sc-bubble.is-on .sc-ticks, .sc-bubble.is-on .sc-ticks::after { animation: none; opacity: 1; }
  .sc-bubble.is-typing i { animation: none; opacity: .6; }
}

/* ==========================================================================
   The rest of the job.

   A quote is a fifth of what happens. These are the other four fifths as they
   look inside a chat: the questions a tuner has to ask before anyone reads an
   ECU, the file coming back, the run that proves it, and the certificate that
   ends it. Colours that are not body text are claimed per scene in JS, because
   the page-wide light sweep reaches in here with !important.
   ========================================================================== */

/* the questions, with tappable answers */
.sc-bubble.is-ask { display: flex; flex-direction: column; gap: 7px; }
.sc-ask-q { display: block; }
.sc-ask-row { display: flex; flex-wrap: wrap; gap: 5px; }
.sc-ask-chip { display: inline-block; padding: 4px 9px; border-radius: 999px;
  border: 1px solid rgba(120, 190, 255, .38); background: rgba(80, 160, 255, .10);
  font-size: 9.5px; font-weight: 600; line-height: 1.2; }

/* the file coming the other way */
.sc-bubble.is-file { display: flex; align-items: center; gap: 9px; }
.sc-fi-ic { flex: 0 0 auto; width: 26px; height: 26px; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, .12); }
.sc-fi-ic i { width: 10px; height: 13px; border-radius: 1.5px; background: currentColor; opacity: .8;
  clip-path: polygon(0 0, 68% 0, 100% 26%, 100% 100%, 0 100%); }
.sc-fi-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.sc-fi-tx i { font-style: normal; font-size: .9em; }
.sc-fi-bar { display: block; height: 3px; border-radius: 3px; margin-top: 3px;
  background: rgba(255, 255, 255, .16); overflow: hidden; }
.sc-fi-bar s { display: block; height: 100%; width: 0; border-radius: 3px;
  background: currentColor; opacity: .85; }
.sc-bubble.is-file.is-on .sc-fi-bar s { animation: scUp 1.15s cubic-bezier(.3, .8, .3, 1) .18s forwards; }
@keyframes scUp { to { width: 100%; } }

/* the run */
.sc-bubble.is-dyno { display: flex; flex-direction: column; gap: 3px; min-width: 168px; }
.sc-dy-h { font-size: 9px; letter-spacing: .07em; text-transform: uppercase; }
.sc-dy-svg { display: block; width: 100%; height: auto; }
.sc-dy-g { stroke: rgba(148, 163, 184, .16); stroke-width: 1; }
.sc-dy-s, .sc-dy-t { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.sc-dy-s { stroke: #7f8ea4; }
.sc-dy-t { stroke: #ff8a3d; }
.sc-bubble.is-dyno .sc-dy-s, .sc-bubble.is-dyno .sc-dy-t {
  stroke-dasharray: 420; stroke-dashoffset: 420; }
.sc-bubble.is-dyno.is-on .sc-dy-s { animation: scDraw 1.1s ease-out .2s forwards; }
.sc-bubble.is-dyno.is-on .sc-dy-t { animation: scDraw 1.1s ease-out .5s forwards; }
@keyframes scDraw { to { stroke-dashoffset: 0; } }
.sc-dy-k { display: flex; align-items: center; gap: 5px; font-size: 8.5px; }
.sc-dy-k i { width: 9px; height: 2px; border-radius: 2px; display: inline-block; }
.sc-dy-k i.s { background: #7f8ea4; }
.sc-dy-k i.t { background: #ff8a3d; margin-left: 5px; }
.sc-dy-f { font-weight: 700; font-size: 10.5px; }

/* and the paper at the end of it */
.sc-bubble.is-cert { display: flex; flex-direction: column; gap: 3px;
  border: 1px solid rgba(125, 243, 189, .26); }
.sc-ce-h { font-size: 9px; letter-spacing: .07em; text-transform: uppercase; margin-bottom: 2px; }
.sc-ce-r { display: flex; justify-content: space-between; gap: 10px; font-size: 9.5px; }
.sc-ce-r i { font-style: normal; }
.sc-ce-seal { margin-top: 4px; font-size: 9px; font-weight: 700; }

@media (prefers-reduced-motion: reduce) {
  .sc-bubble.is-file.is-on .sc-fi-bar s { animation: none; width: 100%; }
  .sc-bubble.is-dyno .sc-dy-s, .sc-bubble.is-dyno .sc-dy-t {
    stroke-dasharray: none; stroke-dashoffset: 0; animation: none; }
}

/* the job as it lands on the tuner side — one card, three states */
.sc-jb { display: flex; flex-direction: column; gap: 3px; padding: 7px 9px;
  border-radius: 8px; background: rgba(12, 17, 28, .95);
  border: 1px solid rgba(148, 163, 184, .24); font-size: 8px; line-height: 1.35; }
.sc-jb-top { display: flex; align-items: center; justify-content: space-between; gap: 7px; }
.sc-jb-top b { font-size: 8.5px; font-weight: 700; }
.sc-jb-sub, .sc-jb-ans { opacity: .68; }
.sc-jb-file { display: flex; align-items: center; gap: 4px; opacity: .86; }
.sc-jb-file i { width: 6px; height: 8px; border-radius: 1px; background: currentColor; opacity: .7;
  clip-path: polygon(0 0, 68% 0, 100% 26%, 100% 100%, 0 100%); }
.sc-jb-pill { flex: 0 0 auto; font-style: normal; font-weight: 700; font-size: 7px;
  letter-spacing: .06em; text-transform: uppercase; padding: 2px 6px; border-radius: 999px;
  border: 1px solid currentColor; }
.sc-ovl-item[data-state="received"] .sc-jb-pill::after { content: "received"; }
.sc-ovl-item[data-state="tuning"]   .sc-jb-pill::after { content: "tuning"; }
.sc-ovl-item[data-state="done"]     .sc-jb-pill::after { content: "delivered"; }

/* A bubble that has not been said yet must occupy nothing at all.

   max-height clamps the content box and leaves the padding and the border
   standing, so every collapsed bubble was still worth its 16px of padding.
   With ten of them that was invisible; with twenty-one it was 290px of dead
   space under a bottom-anchored feed, and the live conversation was pushed
   clean off the top of the phone. The negative margin cancels the flex gap;
   these cancel the rest. */
.sc-bubble:not(.is-on) {
  padding-top: 0; padding-bottom: 0;
  border-top-width: 0; border-bottom-width: 0;
}
.sc-bubble {
  transition: opacity 240ms var(--sc-ease),
              transform 430ms cubic-bezier(.18, 1.28, .42, 1),
              max-height var(--sc-t-base) var(--sc-ease),
              padding var(--sc-t-base) var(--sc-ease),
              margin-top var(--sc-t-base) var(--sc-ease);
}
