/* ==========================================================================
   Miloop AI: style.css
   Design tokens follow the "closed loop" concept: build, verify, deploy,
   iterate. Colors and the ring motif exist to carry that idea, not to
   decorate the page.
   ========================================================================== */

:root {
  /* --- Color tokens --- */
  --bg-void: #0A0B14;
  --bg-panel: #141625;
  --bg-panel-raised: #1B1E33;
  --ink: #E9E7F5;
  --ink-dim: #8D8BA8;
  --ink-faint: #57597A;
  --loop-indigo: #5B6EF5;
  --loop-violet: #A66CF0;
  --signal-mint: #5EE6C8;
  --border-soft: rgba(233, 231, 245, 0.09);
  --border-strong: rgba(233, 231, 245, 0.18);

  /* --- Type --- */
  --font-display: 'Fraunces', 'Noto Serif SC', serif;
  --font-body: 'IBM Plex Sans', 'Noto Sans SC', -apple-system, sans-serif;
  /* Every Latin face in this stack lacks CJK, and so does the generic
     `monospace` it ends in, so Chinese label text used to fall through to
     whatever the OS happened to own the glyph: SimSun on Windows, thin and
     spindly and visibly weaker than the rest of the page at the same size.
     Noto Sans SC is already loaded for body text; naming it here keeps the
     label tier on one face in both languages. */
  --font-mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', 'Noto Sans SC', monospace;

  /* A CJK glyph fills its em box. Latin lowercase only reaches the x-height,
     a little over half of it. Set at the same font-size, a Chinese heading
     therefore reads noticeably larger than the English one it was drawn to
     match — the bigger the type, the more the difference shows. This is the
     correction, applied only to display sizes; body and label text is set
     from the reader's side and stays where the legibility pass put it. */
  --display-scale: 1;

  /* --- Spacing / layout --- */
  --max-width: 1180px;
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.5rem;
  --space-4: 2.5rem;
  --space-5: 4rem;
  --space-6: 6.5rem;
  --radius-sm: 6px;
  --radius-md: 14px;
  --radius-lg: 28px;
}

/* --- Reset --- */
*, *::before, *::after { box-sizing: border-box; }

/* 17px rather than the browser default 16. Every size and every space token
   on this site is in rem, so the whole design scales from here in proportion
   instead of each tier being nudged on its own and the hierarchy collapsing.
   A percentage rather than a px value so it still respects a reader who has
   changed their browser's default text size. */
html { font-size: 106.25%; scroll-behavior: smooth; }

/* Both Chinese versions only. The English pages are the reference the scale
   was drawn against and must not move. `lang` is set on <html> by every one
   of the three language paths, so this needs no help from JS. */
html[lang^="zh"] {
  --display-scale: 0.92;
}
/* 1.12 and 1.15 are Latin values: they work because lowercase leaves air
   above and below. Two lines of Chinese at that leading nearly touch. */
html[lang^="zh"] .hero h1,
html[lang^="zh"] .about-h1,
html[lang^="zh"] .deskloop-h1,
html[lang^="zh"] .driftboard-h1,
html[lang^="zh"] .factloop-h1 {
  line-height: 1.28;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; }
}

body {
  margin: 0;
  background: var(--bg-void);
  background-image:
    radial-gradient(ellipse 900px 500px at 15% -10%, rgba(91, 110, 245, 0.16), transparent 60%),
    radial-gradient(ellipse 700px 500px at 100% 10%, rgba(166, 108, 240, 0.10), transparent 60%);
  background-repeat: no-repeat;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
ul { margin: 0; padding: 0; list-style: none; }
h1, h2, h3, p { margin: 0; }

:focus-visible {
  outline: 2px solid var(--loop-indigo);
  outline-offset: 3px;
  border-radius: 2px;
}

.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--space-3);
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--loop-violet);
}

h1, h2, h3 { font-family: var(--font-display); font-weight: 600; letter-spacing: -0.01em; }

h2.section-title {
  font-size: calc(clamp(1.6rem, 3vw, 2.3rem) * var(--display-scale));
  font-weight: 300;
  margin-top: var(--space-1);
  max-width: 40ch;
}

.section-lede {
  color: var(--ink-dim);
  max-width: 56ch;
  margin-top: var(--space-2);
  font-size: 1.02rem;
}

section { padding: var(--space-6) 0; }
section + section { border-top: 1px solid var(--border-soft); }

/* --- Buttons --- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.4rem;
  border-radius: 999px;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 0.95rem;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.btn-primary {
  background: linear-gradient(120deg, var(--loop-indigo), var(--loop-violet));
  color: #07070C;
  font-weight: 600;
}
.btn-primary:hover { transform: translateY(-1px); }
.btn-ghost {
  background: transparent;
  border-color: var(--border-strong);
  color: var(--ink);
}
.btn-ghost:hover { border-color: var(--loop-indigo); }

/* ==========================================================================
   Nav
   ========================================================================== */
.site-nav {
  position: sticky;
  top: 0;
  z-index: 50;
  backdrop-filter: blur(14px);
  background: rgba(10, 11, 20, 0.72);
  border-bottom: 1px solid var(--border-soft);
}
.site-nav .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 68px;
}
.wordmark {
  display: inline-flex;
  align-items: center;
}
.wordmark-full {
  height: 34px;
  width: auto;
  display: block;
}

.lang-switch { position: relative; }
.lang-switch-btn {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-family: var(--font-mono);
  font-size: 0.88rem;
  color: var(--ink-dim);
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  padding: 0.35rem 0.7rem;
  cursor: pointer;
}
.lang-switch-btn:hover { color: var(--ink); border-color: var(--loop-indigo); }
.lang-caret { transition: transform 0.15s ease; }
.lang-switch.open .lang-caret { transform: rotate(180deg); }

.lang-switch-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 150px;
  background: var(--bg-panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-md);
  padding: 0.4rem;
  display: none;
  flex-direction: column;
  gap: 0.15rem;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.45);
  z-index: 60;
}
.lang-switch.open .lang-switch-menu { display: flex; }
.lang-option {
  text-align: left;
  background: transparent;
  border: none;
  color: var(--ink-dim);
  padding: 0.55rem 0.65rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 0.95rem;
}
.lang-option:hover { background: var(--bg-panel-raised); color: var(--ink); }
.lang-option.active { color: var(--loop-violet); font-weight: 600; }

.nav-actions { display: flex; align-items: center; gap: var(--space-2); }

@media (max-width: 480px) {
  .wordmark-full { height: 26px; }
  .nav-actions { gap: 0.5rem; }
  .nav-actions .btn { padding: 0.55rem 0.9rem; font-size: 0.9rem; }
}

/* ==========================================================================
   Hero
   ========================================================================== */
.hero {
  padding-top: var(--space-6);
  padding-bottom: var(--space-6);
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: var(--space-5);
  align-items: center;
}
.hero h1 {
  font-size: calc(clamp(2.3rem, 4.4vw, 3.6rem) * var(--display-scale));
  font-weight: 300;
  line-height: 1.12;
  margin-top: var(--space-2);
}
.hero h1 em {
  font-style: normal;
  background: linear-gradient(120deg, var(--loop-indigo), var(--loop-violet));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.hero-sub {
  margin-top: var(--space-3);
  color: var(--ink-dim);
  font-size: 1.08rem;
  max-width: 46ch;
}

/* ==========================================================================
   Hero loop
   ==========================================================================
   The path is 786.6 units long and the light takes 9s to travel it, so one
   second is 87.4 units. Everything else here is derived from those two
   numbers rather than picked: the light and the five nodes run on one clock,
   and a node fires because the light reached it, not on a schedule of its
   own. If the geometry is ever redrawn, the --t values in the markup have to
   be recomputed from the new arc lengths or the causality breaks silently.
   ========================================================================== */
.loop-stage-label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  fill: var(--ink-dim);
}

.loop-track { fill: none; stroke: url(#heroTrack); stroke-width: 2; }

/* Two dashes on one path make a comet: a long dim body and, aligned to its
   leading edge, a short bright head. 240 - 125.9 = 114.1 is how far the body
   trails behind the head, and is why the trail keyframes start there. */
.loop-trail {
  fill: none;
  stroke: url(#heroGrad);
  stroke-width: 3.5;
  stroke-linecap: round;
  stroke-dasharray: 240 546.6;
  opacity: 0.3;
}
.loop-glow {
  fill: none;
  stroke: url(#heroGrad);
  stroke-width: 3.5;
  stroke-linecap: round;
  stroke-dasharray: 125.9 660.7;
  /* Reads as light rather than as a painted line. Cheap on a dark ground. */
  filter: drop-shadow(0 0 5px rgba(91, 110, 245, 0.55));
}

@media (prefers-reduced-motion: no-preference) {
  .loop-glow  { animation: loop-travel 9s linear infinite; }
  .loop-trail { animation: loop-trail-travel 9s linear infinite; }
  /* --t is set per node in the markup: the moment the light reaches it. */
  .loop-node-mark   { animation: node-fire 9s ease-out infinite; animation-delay: var(--t); }
  .loop-stage-label { animation: label-fire 9s ease-out infinite; animation-delay: var(--t); }
  .loop-node--verify .loop-stage-label { animation-name: label-fire-verify; }
}
@keyframes loop-travel {
  to { stroke-dashoffset: -786.59; }
}
@keyframes loop-trail-travel {
  from { stroke-dashoffset: 114.1; }
  to   { stroke-dashoffset: -672.5; }
}
/* 16% is the head's own share of the path, so the hold lasts exactly as long
   as the light covers the node. The decay after it is the afterglow. */
@keyframes node-fire {
  0%   { opacity: 0.45; }
  3%   { opacity: 1; }
  16%  { opacity: 1; }
  40%  { opacity: 0.45; }
  100% { opacity: 0.45; }
}
@keyframes label-fire {
  0%   { fill: var(--ink-dim); }
  3%   { fill: var(--ink); }
  16%  { fill: var(--ink); }
  40%  { fill: var(--ink-dim); }
  100% { fill: var(--ink-dim); }
}
@keyframes label-fire-verify {
  0%   { fill: var(--ink-dim); }
  3%   { fill: var(--signal-mint); }
  16%  { fill: var(--signal-mint); }
  40%  { fill: var(--ink-dim); }
  100% { fill: var(--ink-dim); }
}

.loop-node circle.node-dot {
  fill: var(--bg-panel-raised);
  stroke: var(--border-strong);
  stroke-width: 2;
}
.loop-node-icon { stroke: var(--ink); }

/* Verify sits exactly on the crossing of the figure eight, and self-verifying
   systems are what the headline above it claims. It is the only node given the
   metric colour, and the only one drawn larger, so the composition has a
   subject instead of five equal dots. */
.loop-node--verify circle.node-dot { stroke: rgba(94, 230, 200, 0.5); }
.loop-node--verify .loop-node-icon { stroke: var(--signal-mint); }

@media (max-width: 900px) {
  .hero { grid-template-columns: 1fr; }
}

/* ==========================================================================
   Services (interactive selector + detail panel)
   ========================================================================== */
.service-interactive {
  margin-top: var(--space-4);
  display: grid;
  grid-template-columns: 320px 1fr;
  gap: var(--space-4);
  align-items: start;
}

.service-menu { display: flex; flex-direction: column; gap: 0.4rem; }
.service-menu-item {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  padding: 0.9rem 1rem;
  cursor: pointer;
  font-family: var(--font-body);
  color: var(--ink-dim);
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.service-menu-item:hover { background: var(--bg-panel); color: var(--ink); }
.service-menu-item.active {
  background: var(--bg-panel);
  border-color: var(--loop-indigo);
  color: var(--ink);
}
.service-menu-num {
  font-family: var(--font-mono);
  font-size: 0.88rem;
  color: var(--loop-violet);
  flex-shrink: 0;
  width: 24px;
}
.service-menu-title { font-size: 0.98rem; font-weight: 500; line-height: 1.3; }

.service-panel { position: relative; min-height: 220px; }
.service-panel-item {
  display: none;
  background: var(--bg-panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
}
.service-panel-item.active {
  display: block;
  animation: servicePanelIn 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}
@media (prefers-reduced-motion: reduce) {
  .service-panel-item.active { animation: none; }
}
@keyframes servicePanelIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}
.service-panel-item h3 { font-size: calc(1.4rem * var(--display-scale)); font-weight: 600; margin-bottom: 1rem; }
.service-panel-item p { color: var(--ink-dim); font-size: 1.02rem; line-height: 1.7; max-width: 66ch; }
.service-panel-item p strong { color: var(--ink); font-weight: 600; }

@media (max-width: 820px) {
  .service-interactive { grid-template-columns: 1fr; }
}

/* ==========================================================================
   Results (merged proof + work showcase)
   ========================================================================== */
.result-grid {
  margin-top: var(--space-5);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
}
.result-card {
  background: var(--bg-panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.result-card:hover { border-color: var(--loop-indigo); transform: translateY(-2px); }

.result-visual {
  aspect-ratio: 16 / 10;
  position: relative;
  overflow: hidden;
  background: var(--bg-panel-raised);
}
.result-visual img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.result-visual::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(10, 11, 20, 0.18) 0%, rgba(10, 11, 20, 0.28) 55%, rgba(20, 22, 37, 0.96) 100%);
  pointer-events: none;
}

.result-body { padding: var(--space-3); display: flex; flex-direction: column; gap: 0.5rem; flex: 1; }
.result-tag {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.88rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--loop-violet);
}
.result-body h3 { font-size: 1.1rem; font-weight: 600; font-family: var(--font-body); }
.result-body p { color: var(--ink-dim); font-size: 0.95rem; line-height: 1.6; }
.result-body p strong { color: var(--signal-mint); font-weight: 600; }
.result-quote {
  margin: 0.75rem 0 0;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-soft);
}
.result-quote p {
  font-style: italic;
  color: var(--ink);
  font-size: 0.95rem;
  line-height: 1.55;
}
.result-quote cite {
  display: block;
  margin-top: 0.4rem;
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 0.88rem;
  color: var(--ink-dim);
}

@media (max-width: 820px) {
  .result-grid { grid-template-columns: 1fr; }
}

/* --- Live demos ---
   Same card anatomy and weight as .result-card above: the difference is that
   these lead somewhere you can use, so the quote slot becomes a button. */
.live-demo { margin-top: var(--space-5); }

.live-demo-title {
  font-weight: 300;
  font-size: calc(clamp(1.3rem, 2.2vw, 1.7rem) * var(--display-scale));
  line-height: 1.35;
  max-width: 36ch;
}

.live-demo-grid {
  margin-top: var(--space-4);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
}

.live-demo-card {
  position: relative; /* anchor for the whole-card hit area below */
  background: var(--bg-panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.live-demo-card:hover { border-color: var(--loop-indigo); transform: translateY(-2px); }
/* The card lifts and lights up on hover, so the whole thing has to be clickable
   or it promises a target it does not have. Done by stretching the card's one
   real anchor over it rather than with a click handler on the card: there is
   still a single link, it keeps its href, its focus ring and its middle-click
   and open-in-new-tab behaviour, and no JS is involved. Only correct while a
   card holds exactly one link. The cost is that text inside the card cannot be
   selected, which is the accepted trade for this pattern. */
.live-demo-actions a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2; /* over the badge (1) and the visual's scrim */
}
/* Keyboard users tab to the anchor, not the card, so the card has to answer to
   focus the same way it answers to hover. */
.live-demo-card:focus-within { border-color: var(--loop-indigo); }

.live-demo-visual {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--bg-panel-raised);
}
.live-demo-visual img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Much lighter than .result-visual's scrim. That one blends a photograph into
   the card body; this one only has to keep a bright screenshot from clashing
   with the text below, so the screenshot itself stays readable. */
.live-demo-visual::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(10, 11, 20, 0.10) 0%, rgba(10, 11, 20, 0.14) 60%, rgba(20, 22, 37, 0.42) 100%);
  pointer-events: none;
}

.live-demo-badge {
  position: absolute;
  top: 0.7rem;
  right: 0.7rem;
  z-index: 1; /* above the scrim */
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.25rem 0.6rem;
  border-radius: 999px;
  background: rgba(10, 11, 20, 0.72);
  border: 1px solid var(--border-strong);
  backdrop-filter: blur(6px);
  font-family: var(--font-mono);
  font-size: 0.88rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--signal-mint);
}
.live-demo-badge::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--signal-mint);
}
/* This used to live in the hero loop section and was shared with it. The loop
   now runs on delays derived from its own geometry, so the keyframes belong
   to the badge that still uses them. */
@keyframes node-pulse {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}
@media (prefers-reduced-motion: no-preference) {
  .live-demo-badge::before { animation: node-pulse 2.4s ease-in-out infinite; }
}

.live-demo-body { padding: var(--space-3); display: flex; flex-direction: column; gap: 0.5rem; flex: 1; }
.live-demo-tag {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.88rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--loop-violet);
}
/* h4 sits outside the reset's h1-h3 selector, so its UA margin is cleared. */
.live-demo-name {
  margin: 0;
  font-family: var(--font-body);
  font-size: 1.1rem;
  font-weight: 600;
  line-height: 1.35;
}
/* Absorbs the slack so the button lands at the card's foot, level with the
   quote block on the static cards however long the taglines run. */
.live-demo-tagline {
  flex: 1;
  color: var(--ink-dim);
  font-size: 0.95rem;
  line-height: 1.6;
}
.live-demo-actions {
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-soft);
}

/* A lone card would otherwise leave two thirds of the row empty and read as
   unfinished, so on its own it turns into one wide card with the screenshot
   beside the copy. Adding a second card reverts both rules automatically,
   with no edit here. */
.live-demo-grid:has(> :only-child) { grid-template-columns: 1fr; }
.live-demo-grid:has(> :only-child) .live-demo-card { flex-direction: row; }
.live-demo-grid:has(> :only-child) .live-demo-visual {
  flex: 1 1 50%;
  /* Height comes from the row, not the ratio, so the image fills whichever
     of the two columns turns out taller. */
  aspect-ratio: auto;
  min-height: 280px;
}
.live-demo-grid:has(> :only-child) .live-demo-body {
  flex: 1 1 50%;
  justify-content: center;
  padding: var(--space-4);
}

/* Two cards in a three-column track would leave the last column empty, which
   reads as a card that failed to load. They take half each until a third
   arrives and the default rule takes over again. */
.live-demo-grid:has(> :nth-child(2):last-child) { grid-template-columns: repeat(2, 1fr); }

/* Not everything worth linking to has a screen to photograph. This stands in
   for the screenshot with the figures the project is actually judged on. */
.live-demo-visual--figures {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-4);
  background:
    radial-gradient(ellipse 70% 90% at 10% 0%, rgba(91, 110, 245, 0.20), transparent 65%),
    radial-gradient(ellipse 70% 90% at 100% 100%, rgba(166, 108, 240, 0.16), transparent 65%),
    var(--bg-panel-raised);
}
/* The scrim exists to settle a bright screenshot into the card. There is no
   screenshot here, only text that needs its contrast left alone. */
.live-demo-visual--figures::after { display: none; }

.live-demo-figure + .live-demo-figure {
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-soft);
}
.live-demo-figure-value {
  display: block;
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(1.7rem, 3.6vw, 2.4rem);
  line-height: 1.1;
  color: var(--ink);
}
.live-demo-figure-label {
  display: block;
  margin-top: 0.4rem;
  font-family: var(--font-mono);
  font-size: 0.88rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* The pulsing mint dot is the site's "running right now" signal, so a card
   that only leads to a write-up drops it rather than borrowing it. */
.live-demo-badge--study { color: var(--loop-violet); }
.live-demo-badge--study::before { display: none; }

@media (max-width: 820px) {
  .live-demo-grid { grid-template-columns: 1fr; }
  /* Restated because the two-card rule above outranks the line before it on
     specificity, and would otherwise survive into the narrow layout. */
  .live-demo-grid:has(> :nth-child(2):last-child) { grid-template-columns: 1fr; }
  /* Side by side stops working before the cards do, so the wide single-card
     layout folds back to the stacked one here. */
  .live-demo-grid:has(> :only-child) .live-demo-card { flex-direction: column; }
  .live-demo-grid:has(> :only-child) .live-demo-visual { aspect-ratio: 16 / 10; min-height: 0; }
  .live-demo-grid:has(> :only-child) .live-demo-body { padding: var(--space-3); }
}

/* ==========================================================================
   About teaser
   ========================================================================== */
.teaser-panel {
  background: var(--bg-panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  text-align: center;
  max-width: 900px;
  margin: 0 auto;
}
.teaser-panel .eyebrow { display: block; margin-bottom: var(--space-2); }
.teaser-statement {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: calc(clamp(1.4rem, 2.6vw, 1.9rem) * var(--display-scale));
  line-height: 1.4;
  color: var(--ink);
}
.teaser-actions {
  display: flex;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
  flex-wrap: wrap;
}
@media (max-width: 820px) {
  .teaser-panel { padding: var(--space-4); }
}

/* ==========================================================================
   About page
   ========================================================================== */
.about-hero { padding-top: var(--space-5); padding-bottom: var(--space-4); }
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  color: var(--ink-dim);
  margin-bottom: var(--space-3);
}
.back-link:hover { color: var(--ink); }
.about-h1 {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: calc(clamp(1.7rem, 3.2vw, 2.5rem) * var(--display-scale));
  line-height: 1.3;
  max-width: 26ch;
  margin-top: var(--space-2);
}
.about-body { padding-top: var(--space-2); }
.about-content { max-width: 700px; }
.about-content p {
  color: var(--ink-dim);
  font-size: 1.05rem;
  line-height: 1.75;
  margin-bottom: var(--space-3);
}
.about-pullquote {
  margin: var(--space-4) 0;
  padding-left: var(--space-3);
  border-left: 2px solid var(--loop-indigo);
  font-family: var(--font-display);
  font-weight: 300;
  font-style: normal;
  font-size: calc(clamp(1.4rem, 2.4vw, 1.8rem) * var(--display-scale));
  line-height: 1.4;
  color: var(--ink);
}
.about-divider {
  border: none;
  border-top: 1px solid var(--border-soft);
  margin: var(--space-5) 0;
}
.about-content .eyebrow { margin-bottom: var(--space-2); }
.about-aside {
  font-style: italic;
  color: var(--ink-dim);
  font-size: 0.95rem;
  margin-top: var(--space-3);
}

/* --- Scroll reveal --- */
.reveal { opacity: 0; transform: translateY(14px); transition: opacity 0.6s ease, transform 0.6s ease; }
.reveal.in-view { opacity: 1; transform: translateY(0); }
.result-card.reveal:nth-child(1) { transition-delay: 0s; }
.result-card.reveal:nth-child(2) { transition-delay: 0.08s; }
.result-card.reveal:nth-child(3) { transition-delay: 0.16s; }

/* --- Footer --- */
.site-footer {
  border-top: 1px solid var(--border-soft);
  padding: var(--space-4) 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: var(--ink-dim);
  font-size: 0.9rem;
  position: relative;
  overflow: hidden;
}
.footer-mark {
  position: absolute;
  right: -60px;
  top: -140px;
  width: 320px;
  height: 320px;
  opacity: 0.05;
  pointer-events: none;
}
.footer-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
}
.footer-link {
  color: var(--ink-dim);
  transition: color 0.15s ease;
}
.footer-link:hover { color: var(--ink-dim); }
.footer-location {
  font-family: var(--font-mono);
  font-size: 0.88rem;
  margin-left: auto;
}
