/* Ordinary Lab — clickable sitemap prototype
   Two-font system:
     --display (Sofia Sans) — the wordmark + big cover-style headlines only, matching the live ordinary-lab.com placeholder.
     --body    (Inter)      — everything else: nav, buttons, labels, copy, terminal readout. */

@view-transition {
  navigation: auto;
}

:root{
  --paper:#E9ECEC;
  --card:#F5F7F7;
  --ink:#0D1213;
  --graphite:#5B6567;
  --hair:#C6CCCC;
  --ultra:#5B6864;   /* desaturated, almost gray — the faintest sage/blue cast */
  --amber:#8B9591;   /* lighter tint of the same near-gray, for dark backgrounds */

  --display:'Sofia Sans','Helvetica Neue',Arial,sans-serif;
  --body:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  --mono:'JetBrains Mono',ui-monospace,'SF Mono',Menlo,monospace; /* console readout only — not part of the brand type system */

  --pad:clamp(20px,5vw,72px);

  /* shared easing — smooth, no linear/stiff curves anywhere */
  --ease-out:cubic-bezier(.16,1,.3,1);
  --ease-in-out:cubic-bezier(.65,0,.35,1);
  --dur-fast:.28s;
  --dur-page:.7s;
}

*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  margin:0;background:var(--paper);color:var(--ink);
  font-family:var(--body);font-weight:400;font-size:16px;line-height:1.6;
  -webkit-font-smoothing:antialiased;
  animation:pagein var(--dur-page) var(--ease-out);
}
/* opacity-only: a transform here would make body the containing block for any position:fixed
   descendant (the home nav) for the animation's duration, so it would visibly drag/settle late */
@keyframes pagein{from{opacity:0}to{opacity:1}}
a{color:inherit}
img,svg{max-width:100%;display:block}

/* ── cross-document view transitions: soft crossfade + scale.
   Kept as a fallback layer — real navigation is intercepted and animated by assets/site.js,
   this only fires for hard reloads / JS-disabled / modifier-key clicks. ── */
::view-transition-old(root){
  animation:vt-out var(--dur-page) var(--ease-in-out) both;
}
::view-transition-new(root){
  animation:vt-in var(--dur-page) var(--ease-out) both;
}
@keyframes vt-out{to{opacity:0;transform:scale(.985)}}
@keyframes vt-in{from{opacity:0;transform:scale(1.015)}to{opacity:1;transform:none}}

.eyebrow{
  font-family:var(--body);font-weight:600;font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--graphite);margin:0 0 18px;
}
h1,h2{font-family:var(--display);font-weight:700;letter-spacing:-.03em;margin:0}
h3{font-family:var(--body);font-weight:600;font-size:17px;letter-spacing:-.01em;margin:0}
h1{font-size:clamp(44px,7.5vw,92px);line-height:.98}
h2{font-size:clamp(28px,4vw,48px);line-height:1.05}
h2.wide{max-width:26ch}
.lede{font-size:clamp(17px,1.6vw,21px);line-height:1.55;color:#242C32;max-width:36em}
.small{font-family:var(--body);font-size:11.5px;color:var(--graphite);letter-spacing:.02em}

section{padding:clamp(56px,7vw,104px) var(--pad);border-top:1px solid var(--hair)}
section:first-of-type{border-top:none}
/* home page only (these ids don't exist elsewhere): one screen, one idea */
#company,#products{min-height:100vh;display:flex;align-items:center;justify-content:center}
/* #products reads top-aligned instead of vertically centered like #company — explicit request,
   originally with roughly the nav bar's own height held clear above the heading
   ("위쪽정렬해주면 좋겠네 글씨도. 상단nav 여유정도 남겨놓고"). That top padding was cut down later
   — "Three fields 이것도 위쪽으로 올려줘야하겠어 왜냐면 섹션2 아래쪽도 비어있는데 3 위쪽도 너무
   비어있어" (section 2's bottom is already empty, and section 3's top being empty too compounds
   it — pull the heading up). Reduced from `clamp(120px,13vw,168px)` down close to the site's
   normal section padding, don't push it back up to the old "nav-height" value. */
#products{align-items:flex-start;padding-top:clamp(56px,7vw,96px)}
@media(max-width:720px){#company,#products{min-height:auto;padding-top:clamp(72px,10vw,96px);padding-bottom:clamp(72px,10vw,96px)}}
.wrap{max-width:1180px;margin:0 auto}

/* ── top bar ── */
.bar{
  position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:22px;
  padding:14px var(--pad) 0;height:102px;background:rgba(231,234,235,.9);
  backdrop-filter:blur(8px);border-bottom:1px solid var(--hair);
}
.mark{
  font-family:var(--display);font-weight:500;font-size:clamp(28px,3.4vw,40px);letter-spacing:-.02em;
  text-decoration:none;white-space:nowrap;color:var(--ink);
  padding-top:.35em; /* nudges the wordmark down off dead-center, like Jeton's */
}
/* home: the nav floats over the full-bleed hero instead of sitting on a filled bar. Inner pages'
   `.bar` used to be flush at the very top (`top:0`, no margin) while home's floated at `top:14px`
   — a visible vertical mismatch when navigating between them. Fixed by giving the base `.bar` a
   `14px` top `padding` (with `height` bumped from `88px` to `102px` so the *content* area — logo,
   nav-pill — still centers at the same effective height) instead of an external `margin-top`/
   `top` offset: that first fix left a `14px` strip of plain page background exposed above the
   bar's own translucent/blurred fill once it scrolled and stuck ("위쪽이 조금 비는데") — the
   background/blur is part of the box now, so it covers that strip instead of stopping short of
   it. `top:0` (not `14px`) since the visual offset is baked into the box's own height/padding, not
   the sticky threshold, this time.
   Home needs the exact same `padding-top:14px;height:102px;top:0` treatment, not the simpler
   `height:88px;top:14px` it originally had — `initNavFade()` in `site.js` sets this same `.bar`
   element's `background`/`backdrop-filter` progressively via inline styles as you scroll past the
   hero (see "home nav: transparent over the hero, opaque once you scroll past it"), so once
   scrolled it hits the identical gap problem as the inner pages did: a fixed box starting at
   `top:14px` leaves the true top `14px` of the viewport uncovered by that translucent fill. */
body.home .bar{
  position:fixed;left:0;right:0;top:0;height:102px;padding:14px var(--pad) 0;background:transparent;backdrop-filter:none;border-bottom:none;
}
/* one long joined capsule — Company / Products / Contact as segments of a single pill,
   not three separately-styled things. Contact is the filled "primary" segment, like the
   rest of the pill family (.pill--hero, .cta-bubble) uses filled-ink for its primary CTA. */
.nav-pill{
  display:flex;align-items:stretch;height:44px;border-radius:999px;overflow:hidden;
  border:1px solid var(--hair);background:var(--card);
}
.nav-pill a{
  position:relative;display:flex;align-items:center;padding:0 22px;
  font-family:var(--body);font-weight:500;font-size:15px;letter-spacing:0;text-transform:none;
  text-decoration:none;color:var(--ink);white-space:nowrap;
  transition:background var(--dur-fast) var(--ease-out);
}
/* rounded caps eat into the visual margin at the two ends — the straight divider between
   Company/Products doesn't need this, only the two segments touching the pill's curve do. */
.nav-pill a:first-child{padding-left:28px}
.nav-pill a:last-child{padding-right:28px}
.nav-pill a + a{border-left:1px solid var(--hair)}
.nav-pill a:hover{background:rgba(91,104,100,.06)}
/* current-page marker is a bar under the segment, not a text color change — a color shift on only
   one segment (tried twice before) reads as an "irradiation" optical effect where the darker/
   lighter text looks differently tracked even though letter-spacing is pixel-identical on every
   segment. Keeping text color/weight 100% uniform across all segments removes that variable
   entirely. Was a small dot at first, replaced with this line by request ("pill 아래에 줄을 하나
   그어줘") — sits flush with the pill's own bottom edge (`bottom:0`, inset from the segment's
   sides) rather than fully below the pill, since `.nav-pill{overflow:hidden}` (needed to keep
   `.cta`'s fill and the hover backgrounds inside the pill's rounded corners) would clip anything
   drawn past the pill's own box. Muted/desaturated red (`#B33F3A`, the same "dignified" red used
   for the hero vision-gauge speech bubble), not a new hue. A soft left/right `mask-image` fade was
   tried and then dropped by request ("위쪽 mask-image는 그냥 없애줘") — plain solid bar now. */
.nav-pill a.on::after{
  content:'';position:absolute;left:14px;right:14px;bottom:0;
  height:2px;background:#B33F3A;
}
.nav-pill a.cta{background:var(--ink);color:#fff;font-weight:700;border-left-color:var(--ink)}
.nav-pill a.cta:hover{background:var(--ultra)}
.nav-pill a.cta.on::after{background:var(--amber)}
/* leading home icon, its own segment ahead of Company — icon-only, so it gets narrower padding
   than the text segments rather than inheriting `:first-child`'s 28px (sized for a text label
   against the pill's curve). Same divider/hover treatment as the other segments otherwise. */
.nav-pill a.home-link{padding-left:20px;padding-right:16px;color:var(--graphite)}
.nav-pill a.home-link:hover{color:var(--ink);background:rgba(91,104,100,.06)}
.nav-pill a.home-link svg{display:block}
.spacer{flex:1}
.btn{
  display:inline-flex;align-items:center;gap:8px;padding:10px 18px;
  background:var(--ink);color:var(--paper);text-decoration:none;
  font-family:var(--body);font-weight:600;font-size:13px;letter-spacing:.01em;text-transform:none;
  border:1px solid var(--ink);transition:background var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out),border-color var(--dur-fast) var(--ease-out);
}
.btn:hover{background:var(--ultra);border-color:var(--ultra)}
.btn--ghost{background:transparent;color:var(--ink);border-color:var(--ink)}
.btn--ghost:hover{background:var(--ink);color:var(--paper)}
@media(max-width:720px){.nav-pill a:not(.cta){display:none}}

/* ── decorative color wash — soft glows, not the old static concentric-ring pattern.
   transform-only animation (translate+scale on the pseudo-element), so it's compositor-cheap:
   no gradient repaint per frame. ── */
.rings-bg{position:relative;overflow:hidden}
.rings-bg > *{position:relative;z-index:1}
.rings-bg::before{
  content:'';position:absolute;inset:-15%;z-index:0;pointer-events:none;
  background:
    radial-gradient(ellipse 55% 48% at 80% 12%, rgba(91,104,100,.16), transparent 60%),
    radial-gradient(ellipse 50% 45% at 10% 92%, rgba(91,104,100,.12), transparent 58%);
  animation:glow-drift 46s var(--ease-in-out) infinite alternate;
}
.rings-bg--alt::before{
  background:
    radial-gradient(ellipse 55% 48% at 15% 15%, rgba(91,104,100,.16), transparent 60%),
    radial-gradient(ellipse 50% 45% at 90% 88%, rgba(91,104,100,.12), transparent 58%);
}
.rings-bg--dark::before{
  background:
    radial-gradient(ellipse 55% 48% at 80% 12%, rgba(139,149,145,.22), transparent 60%),
    radial-gradient(ellipse 50% 45% at 10% 92%, rgba(139,149,145,.16), transparent 58%);
}
.rings-bg--dark.rings-bg--alt::before{
  background:
    radial-gradient(ellipse 55% 48% at 15% 15%, rgba(139,149,145,.22), transparent 60%),
    radial-gradient(ellipse 50% 45% at 90% 88%, rgba(139,149,145,.16), transparent 58%);
}
/* hero gets a bolder, more saturated wash — this is the one place color can run full */
.rings-bg--bold::before{
  background:
    radial-gradient(ellipse 62% 52% at 80% 12%, rgba(139,149,145,.42), transparent 62%),
    radial-gradient(ellipse 56% 48% at 10% 92%, rgba(91,104,100,.34), transparent 60%);
}
@keyframes glow-drift{
  from{transform:translate(0,0) scale(1)}
  to{transform:translate(-4%,4%) scale(1.08)}
}
@media(prefers-reduced-motion:reduce){.rings-bg::before{animation:none}}

/* ── hero tile grid (company/index.html hero only, opt-in via `.hero-tiles`) — the plain glow
   wash read as too quiet/empty once this hero also carries the vision gauge, so this adds a
   subtle pixel/sensor-grid texture (fitting "vision" thematically) on top of it. Monotone ink
   lines at very low opacity, faded out toward the edges via a radial mask so it doesn't read as
   a harsh full-bleed grid — texture, not a pattern you consciously notice. */
.hero-tiles{
  position:absolute;inset:0;pointer-events:none;
  background-image:
    linear-gradient(rgba(13,18,19,.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(13,18,19,.05) 1px, transparent 1px);
  background-size:44px 44px;
  -webkit-mask-image:radial-gradient(ellipse 75% 65% at 50% 40%, #000 40%, transparent 85%);
  mask-image:radial-gradient(ellipse 75% 65% at 50% 40%, #000 40%, transparent 85%);
}

/* ── hero sinc ridges (company/index.html hero only) — fills the extra bottom whitespace opened
   up when the vision gauge/bubble got pushed further down ("아래가 좀 여유가 너무 많으니까"). Tried
   drifting arrows first ("forward motion") and it didn't work visually; replaced with
   Dirichlet-kernel (periodic sinc) ridge lines, same family of technique as `#products`'
   `.products-sinc-bg` but just **two** curves (not layered "mountains") with **different
   coefficients** — `N=4` (wider main lobe, `.hero-sinc-ridge`) and `N=8` (tighter/denser ripples,
   `.hero-sinc-ridge--b`) in the Dirichlet-kernel formula, so the two read as distinct waves rather
   than duplicates. The two curves are also out of phase by `TILE_W/3` (`400` of `1200` units,
   curve B shifted) — a first pass had both curves peak at the same `x`, reading as one thick line
   rather than two waves ("위상이 너무 똑같아서 겹쳐있어"). Periodic by construction (unlike the
   randomized sum-of-bumps ridges in `#products`), so the doubled-width SVG
   (`viewBox="0 0 2400 280"`, each curve repeating exactly every `1200` units) loops seamlessly on
   `translateX(-50%)` with no manual duplication tricks needed beyond the standard "paint it twice,
   shift by one tile" convention.
   The visible window (`.hero-sinc`) is `140px` tall with `overflow:hidden`, over a `.hero-sinc-track`
   rendered at `280px` (`140/280 = 0.5`, anchored `top:0`) — originally meant to show "only the top
   half" of a tall, sharp, low-baseline curve, but that made almost the whole visible wave a single
   narrow tip poking up out of a mostly-hidden trough ("물결이 위쪽 꽁지밖에 안보여"). Fixed by
   shrinking the curves' own baseline/amplitude (baseline `100`, amplitude `70`, both well inside
   the `140px` window) so essentially the *entire* waveform now renders inside the visible area —
   the window-height-is-half-of-track-height structure is kept for consistency, but with these
   values it's no longer doing much actual cropping. If the curves are regenerated, keep the
   baseline+amplitude comfortably inside `140px` (don't push the baseline back down near `280px`)
   or this regresses to "only a tip visible" again. Stroke `5` / opacity `.2` — went through `10`/
   `.22` (too thick/faint) then `5`/`.5` (too bold once the waveform was fully visible) before
   landing here. `.hero-sinc-track`'s `top:10px` nudges the waveform down slightly within its
   window; `.hero-sinc-track svg{filter:blur(...)}` softens the stroke edges (asked for the line
   itself to read as blurred, not just faint). */
.hero-sinc{position:absolute;left:0;right:0;bottom:0;height:140px;overflow:hidden;pointer-events:none}
.hero-sinc-track{position:absolute;top:10px;left:0;width:200%;height:280px;animation:hero-sinc-drift 46s linear infinite}
.hero-sinc-track svg{display:block;width:100%;height:100%;filter:blur(5px)}
.hero-sinc-ridge{fill:none;stroke:var(--graphite);stroke-width:5;opacity:.2}
@keyframes hero-sinc-drift{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media(prefers-reduced-motion:reduce){.hero-sinc-track{animation:none}}
@media(max-width:720px){.hero-sinc{height:92px}.hero-sinc-track{height:184px}}

/* products/index.html reuses .hero-sinc top-anchored (not bottom-anchored like #company's, which
   sits in empty space below its vision-gauge) — this page's hero has no empty space below the
   title, so the wave needs to run behind the h1 itself instead. Own curve data (N=3/N=7,
   phase-shifted by TILE_W/4 instead of company's N=4/N=8 at TILE_W/3) so it doesn't look like a
   copy-pasted duplicate of the company hero's wave. */
.hero-sinc--title{bottom:auto;top:20px;height:170px}

/* ── ambient wave-mesh background — procedural topographic line field.
   34 sine-built paths, hand-tuned so every path's start and end point match exactly at x=1200 —
   the strip is drawn twice back to back and scrolled by exactly one tile width, so the drift never
   has a seam or a cut. A diagonal mask fades it in, denser toward the bottom-right. */
.wave-mesh{
  position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden;
  -webkit-mask-image:linear-gradient(115deg, transparent 6%, rgba(0,0,0,.45) 42%, #000 76%);
          mask-image:linear-gradient(115deg, transparent 6%, rgba(0,0,0,.45) 42%, #000 76%);
}
.wave-mesh svg{
  position:absolute;top:-25%;left:0;height:150%;width:200%;max-width:none;
  animation:mesh-drift 75s linear infinite;
}
.wave-mesh path{fill:none;stroke:var(--graphite);stroke-width:1}
/* one full X wrap (seamless, content repeats every 50%) with a bounded Y wobble layered on top —
   real infinite diagonal tiling would need the pattern to repeat vertically too, which would fight
   the top/bottom taper, so this bounded sway is the honest way to read as "flowing diagonally". */
@keyframes mesh-drift{
  0%{transform:translate(0,0)}
  25%{transform:translate(-12.5%,14px)}
  50%{transform:translate(-25%,0)}
  75%{transform:translate(-37.5%,-14px)}
  100%{transform:translate(-50%,0)}
}
@media(prefers-reduced-motion:reduce){.wave-mesh svg{animation:none}}

/* ── home hero: full-bleed background, nav floats on top, a second menu line at the bottom ── */
.hero{
  min-height:100vh;min-height:100dvh;display:flex;flex-direction:column;
  border-top:none;padding:clamp(120px,13vw,150px) var(--pad) clamp(28px,4vw,40px);
}
.hero-inner{
  margin-top:auto; /* pushes the whole block down, snug against the bottom menu line */
  display:grid;grid-template-columns:1fr 1.05fr;gap:clamp(28px,5vw,64px);align-items:end;
}
@media(max-width:900px){
  .hero-inner{grid-template-columns:1fr}
  .hero-inner .sensor-viz{order:-1;margin-bottom:8px}
}
/* the tag-pill and the product shot were two separate elements at first (image floating above a
   small pill) — merged into one shape by explicit request: one tall capsule, image on top,
   label at the bottom in its original position. Only the CES 2027 : SonoCop slide has an image
   (`.tag-pill-img`, `display:none` by default like the pill itself); other slides' pills stay
   the original compact single-line shape since the image block collapses to nothing. The base
   padding (`16px 22px 9px`) is asymmetric on purpose *for the image slide* — the image's own
   `margin-bottom:14px` already supplies visual space above the label, so a smaller bottom padding
   keeps the label snug under it. That same asymmetric padding read as the label sitting low/
   off-center on image-less slides (PRODUCT : MOMO series) — `site.js`'s carousel toggles a
   `.no-img` class for those, which resets to symmetric padding so the label centers normally. */
.tag-pill{
  display:inline-flex;flex-direction:column;align-items:center;
  padding:16px 22px 9px;margin-bottom:20px;border-radius:28px;
  background:var(--ink);color:#fff;text-decoration:none;cursor:pointer;
  transition:background var(--dur-fast) var(--ease-out);
}
.tag-pill.no-img{padding:13px 22px}
.tag-pill:hover{background:var(--ultra)}
.tag-pill-img{
  display:block;height:168px;width:auto;margin-bottom:14px;border-radius:10px;
}
.tag-pill-label{
  font-family:var(--body);font-weight:700;font-size:14px;letter-spacing:.1em;text-transform:uppercase;
}
.hero h1{margin:0}
.hero-desc{
  margin:16px 0 0;max-width:38ch;
  font-family:var(--body);font-size:15.5px;line-height:1.55;color:var(--graphite);
}

/* bottom menu line — a second, thinner row under the hero, pills centered; the hint and the
   CES booking bubble float independently, pinned to the true left/right edges of the screen. */
.hero-menu{
  display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:16px;
  padding-top:26px;margin-top:32px;border-top:1px solid rgba(91,104,100,.2);
}
.hero-menu .pills{grid-column:2;margin:0;justify-content:center}
@media(max-width:720px){
  .hero-menu{grid-template-columns:1fr;justify-items:center;gap:14px;text-align:center}
  .hero-menu .pills{grid-column:1;justify-self:center}
}

.pills{display:flex;flex-wrap:wrap;align-items:center;gap:10px}
.pill{
  display:inline-flex;align-items:center;gap:6px;padding:14px 28px;border-radius:999px;
  border:1px solid var(--hair);background:var(--card);color:var(--ink);text-decoration:none;
  font-family:var(--body);font-weight:500;font-size:15.5px;
  transition:border-color var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out);
}
.pill:hover{border-color:var(--ultra);background:#fff}
/* CES 2027 is the headline CTA here — it should read as a real button, not a link chip */
.pill--hero{
  padding:14px 28px;border-radius:999px;border-color:var(--ink);
  background:var(--ink);color:#fff;font-weight:700;font-size:15.5px;
  box-shadow:0 6px 18px rgba(13,18,19,.16);
}
.pill--hero:hover{background:var(--ultra);border-color:var(--ultra)}

/* CES booking bubble, pinned to the far-right edge — mirrors .support-hint on the left */
.cta-bubble{
  position:absolute;right:var(--pad);bottom:clamp(30px,4.2vw,42px);z-index:2;
  display:inline-flex;align-items:center;padding:14px 28px;text-decoration:none;
  background:var(--ink);color:#fff;border-radius:20px 20px 4px 20px;
  font-family:var(--body);font-weight:600;font-size:15.5px;
  box-shadow:0 4px 14px rgba(13,18,19,.18);
  transition:background var(--dur-fast) var(--ease-out);
}
.cta-bubble:hover{background:var(--ultra)}
@media(max-width:720px){.cta-bubble{position:static;margin-top:16px}}

/* a real speech bubble, pinned to the far-left edge of the hero — not part of the pill/nav language */
.support-hint{
  position:absolute;left:var(--pad);bottom:clamp(30px,4.2vw,42px);z-index:2;
}
.support-trigger{
  position:relative;display:inline-flex;align-items:center;gap:0;padding:14px 28px;cursor:pointer;
  border:1px solid var(--hair);background:var(--card);color:var(--ink);
  border-radius:20px 20px 20px 4px; /* sharp corner = the bubble tail's root, no separate triangle needed */
  font-family:var(--body);font-weight:500;font-size:15.5px;
  box-shadow:0 4px 14px rgba(13,18,19,.06);
  transition:border-color var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out);
}
.support-hint:hover .support-trigger,
.support-hint:focus-within .support-trigger{border-color:var(--ultra);background:#fff}
.support-bubble{
  position:absolute;left:0;bottom:calc(100% + 14px);width:min(360px,80vw);
  background:var(--ink);color:var(--paper);padding:18px 20px;border-radius:18px 18px 18px 4px;
  font-family:var(--body);font-size:14.5px;line-height:1.55;
  opacity:0;visibility:hidden;transform:translateY(10px) scale(.96);transform-origin:bottom left;
  transition:opacity var(--dur-fast) var(--ease-out),transform var(--dur-fast) var(--ease-out),visibility var(--dur-fast);
  pointer-events:none;
}
.support-hint:hover .support-bubble,
.support-hint:focus-within .support-bubble{
  opacity:1;visibility:visible;transform:translateY(0) scale(1);
}
@media(max-width:720px){.support-hint{position:static;margin-bottom:16px}}
.scroll-cue{margin-top:34px;font-family:var(--body);font-weight:500;font-size:10.5px;color:var(--graphite);letter-spacing:.1em;text-transform:uppercase}

/* ── sensor-viz: the isometric diorama, paired with a live console readout ── */
.sensor-viz{position:relative;display:flex;flex-direction:column;gap:20px;align-items:stretch;max-width:520px;margin:0 auto;width:100%}
.diorama{width:100%}
.diorama svg{width:100%;height:auto;overflow:visible}

/* diorama solids — cool technical-paper tones, independent of the accent palette */
.g-top{fill:#DEE3E4;stroke:#9CA6AB;stroke-width:.7}
.g-side{fill:#C3CACD;stroke:#9CA6AB;stroke-width:.7}
.field{fill:#D5DBD9;stroke:#AEB7B4;stroke-width:.7}
.track{fill:none;stroke:#AEB7B4;stroke-width:1;stroke-dasharray:4 4}
.f-top{fill:#F7F8F8;stroke:var(--ink);stroke-width:.8;stroke-linejoin:round}
.f-right{fill:#DBE0E2;stroke:var(--ink);stroke-width:.8;stroke-linejoin:round}
.f-left{fill:#C6CDD0;stroke:var(--ink);stroke-width:.8;stroke-linejoin:round}
.detail{stroke:var(--ink);stroke-opacity:.35;stroke-width:.6}
.tree-trunk{stroke:#6B7378;stroke-width:1.6}
.tree-top{fill:#B9C2BE;stroke:var(--ink);stroke-width:.7}

/* the node + live events, in the muted accent */
.pole{stroke:var(--ink);stroke-width:1.6}
.dome{fill:var(--ink)}
.dome-led{fill:var(--amber)}
.node-lbl{font-family:var(--mono);font-size:14px;font-weight:400;fill:var(--ink);letter-spacing:.06em}
.bearing{stroke:var(--amber);stroke-width:1.1;stroke-dasharray:3 3;opacity:.8}
.ripple{fill:none;stroke:#D6493D;stroke-width:1.8;animation:ripple 2.2s var(--ease-out) infinite}
@keyframes ripple{0%{transform:scale(.18);stroke-opacity:.9}100%{transform:scale(1);stroke-opacity:0}}
.ev-x{stroke:#D6493D;stroke-width:2.4;stroke-linecap:round}
.lead{stroke:var(--ink);stroke-opacity:.4;stroke-width:.7}
.dio-lbl{font-family:var(--mono);font-size:8px;fill:var(--ink);letter-spacing:.03em}
.dio-lbl tspan.deg{fill:var(--ultra)}
.ev{opacity:0;animation:evin .5s var(--ease-out) forwards}
@keyframes evin{to{opacity:1}}

/* ── modern console readout — clean dev-tool style, not a CRT prop ── */
.terminal{
  background:#15181a;color:#D7DCDD;border:1px solid #24282b;border-radius:10px;
  padding:0;text-align:left;width:100%;margin:0 auto;overflow:hidden;
  box-shadow:0 12px 28px rgba(13,18,19,.22);
  zoom:.85; /* shrinks the whole box (layout included), not just a visual scale — no leftover gap */
}
.terminal-body{height:198px;padding:14px 16px;overflow:hidden}
.tcard{position:relative;display:flex;flex-direction:column;gap:7px}
.tcard-head{display:flex;align-items:baseline;gap:10px;font-family:var(--mono)}
.tcard-cls{font-size:25px;font-weight:800;letter-spacing:.005em;color:#EAEDEE;text-transform:uppercase;display:inline-block;transition:transform .3s var(--ease-out)}
@media(prefers-reduced-motion:reduce){.tcard-cls{transition:none}}
/* big bold marker breaking out of the terminal's own black box — a sibling of .terminal (not a
   descendant), positioned in JS to straddle its top border, so overflow:hidden/rounded corners
   on .terminal can't clip it. Only shown for X-marked (non-scream) events. */
.viz-caret{
  position:absolute;left:0;top:0;width:0;height:0;z-index:4;pointer-events:none;
  border-left:13px solid transparent;border-right:13px solid transparent;
  border-bottom:22px solid #D6493D;
  filter:drop-shadow(0 3px 6px rgba(13,18,19,.4));
  opacity:0;transition:opacity .25s var(--ease-out),left .3s var(--ease-out);
}
@media(prefers-reduced-motion:reduce){.viz-caret{transition:none}}
/* the raw sensor data — this is the part that matters, so it gets the most contrast and weight */
.tcard-readings{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:5px 12px;
  font-family:var(--mono);font-size:13px;font-weight:600;color:var(--amber);
  padding:6px 0;border-top:1px solid #24282b;border-bottom:1px solid #24282b;
}
.tcard-readings span{white-space:nowrap}
.tcard-analysis,.tcard-action,.tcard-dir{margin:0;font-family:var(--body);font-size:11.5px;line-height:1.4}
.tcard-analysis{color:#AEB4B6}
.tcard-action{color:#C98A5C;font-weight:500}
.tcard-dir{color:#6FAF92;font-weight:500}

/* small top-down "you are here" map — floats over the empty space below-and-right of the header,
   starting comfortably clear of the header's own bottom edge (not at top:0, which used to
   overlap/hide long class names like "GLASS BREAK"; 36px still read as too tight against
   "COLLISION", bumped further) and deliberately overlapping the readings/analysis/action rows
   underneath — explicitly OK'd ("아래쪽 줄들 무시하고 그 위에 그려지면 되니까"). Absolute +
   pointer-events:none costs zero layout height. Sized to fill the space (150x120, close to the
   plot's own 26:20 aspect) rather than a thin strip. Light-toned, not dark like the terminal
   shell — reads as a little paper map inset. */
.tcard-map-row{
  position:absolute;top:44px;right:0;z-index:1;pointer-events:none;
  display:flex;align-items:flex-end;gap:8px;
}
.tcard-map-box{width:150px;height:120px;flex:none;border-radius:6px;overflow:hidden;background:#DEE3E4;border:1px solid rgba(255,255,255,.08)}
.tcard-map{display:block;width:100%;height:100%}
.mini-ground{fill:#DEE3E4}
.mini-field{fill:#CBD3CF;opacity:.65}
.mini-b{fill:#AEB7B4;stroke:#8B9591;stroke-width:.15}
.mini-pin-ring{fill:none;stroke:#D6493D;stroke-width:.35;opacity:.55}
.mini-pin{fill:#D6493D;stroke:#fff;stroke-width:.25}
/* outside the map now, not an overlay strip baked into it — the pin can sit anywhere in the map
   including near the bottom edge, and a bottom label used to cover it there. Bottom-aligned to
   the map box, two lines, sitting on the terminal's own dark background instead of on the map. */
.tcard-loc{
  margin:0;max-width:92px;text-align:right;
  font-family:var(--body);font-size:10.5px;line-height:1.3;color:#AEB4B6;
}

/* ── two/three col ── */
.cols{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.25fr);gap:clamp(28px,4vw,72px)}
@media(max-width:860px){.cols{grid-template-columns:1fr;gap:32px}}

/* #company's illustration: a small square card (sized to the description column's own width
   via `width:100%;aspect-ratio:1/1`, not full-bleed) — a real product shot (momo8's top-down
   technical drawing, `.reference/momo8b_pictures/topview.png`) centered over a moving wave-line
   background, echoing the hero's `.wave-mesh` idea without literally being it. This replaced two
   earlier illustration attempts entirely: a hand-drawn two-robot-arm vector diagram, then a
   version of that using a sourced MDI icon for the arm body — both scrapped in favor of an
   actual product photo ("그림을 줘야겠네" — after Replicate turned out to still be unusable this
   session, the user supplied real device photos instead of asking for more illustration).
   `momo8-topview-halo.png` is a **pre-processed derivative**, not the raw reference photo: the
   raw `topview.png` already has a transparent background, but sitting directly on moving wave
   lines let them show through the device's own transparent center hole/gaps in a way that read
   as noisy, so a white halo was baked in first (Euclidean alpha dilation via
   `scipy.ndimage.distance_transform_edt`, ~16px radius at 4x upscale — hugs the actual circular
   silhouette including the small side connector notch, not a bounding box) so the device reads
   as a solid disc sitting *on top of* the moving background, the same "halo hugging the drawn
   silhouette" idea as the hero diorama's `feMorphology` filter, just done for a raster source
   instead of live SVG. Regenerate via `gen_momo8_card.py`-style script (or re-run just the halo
   step) if a different reference photo needs the same treatment — don't hand-paint a border.
   **First attempt literally reused `.wave-mesh`** (same 34-path field, same diagonal opacity
   mask, same markup, duplicated verbatim as a second instance on the page) — explicitly walked
   back on two grounds: (1) "이게 너무 똑같은데" (too visually identical to the hero, no longer
   reads as its own section) and (2) a real performance question ("브라우저 부담은 어때?") — two
   live instances of a 34-path SVG (68 path elements once each is doubled for seamless tiling)
   each under a `mask-image` is meaningfully heavier than it needs to be for a small decorative
   card: `mask-image` forces its own compositing/masking work per frame beyond a plain
   `transform` animation, and duplicating that plus ~136 total path elements adds real
   rasterization/geometry cost, especially on low-power devices — `transform`-only animation is
   compositor-cheap on its own, but doubling the heaviest background element on the page isn't
   free just because each instance individually is. Fixed by building a **much lighter, visually
   distinct** replacement instead of reusing the hero's:
   - Explicit asks: "웨이브 세 줄만 가운데 지나가면 좋겠는데" (just 3 wave lines through the
     center) and "그라데이션 대신에 그리드가 있으면 좋겠고" (a grid instead of the gradient mask).
   - The grid is **plain CSS**, not SVG — two `repeating-linear-gradient`s (one per axis) as
     `.company-diagram`'s `background-image`, 40px cells, `var(--graphite)` at low alpha. This is
     essentially free (no extra DOM/paint layer, no JS, no animation) — cheaper than the old
     `.diagram-grid` SVG-line approach used by the abandoned arm diagrams, and there's no
     mask-image anywhere in this version.
   - `.company-wave` holds just **3** sine paths (not 34), each built from two summed sine
     components with *integer* periods over one 480px tile width — integer periods guarantee
     `f(0) == f(tile_width)` exactly, so the tile-doubling-and-translate-by-50%-of-element-width
     trick (same technique as `mesh-drift`) loops seamlessly without needing the hero's hand-
     tuned endpoint-matching. Positioned as a thin horizontal band (`top:38%;height:24%`) so the
     3 lines pass through the *middle* of the square, not the full height. Animation is pure
     `translateX` (no vertical wobble, no mask) — `company-wave-drift` 40s linear infinite.
   - Regenerate the 3 paths with a small script (integer-period sine sums, see the technique
     above) if this needs to change — don't hand-plot points, and don't reach for the hero's
     34-path generator or reintroduce a mask-image here.
   **Follow-up attempt, reverted**: "2섹션에 있는 우리 정사각 그림을 직사각형으로 만들어줄 수
   있나? 오른쪽 정렬로, 5:3 정도 느낌으로?" was first read as "make the whole square card a 5:3
   rectangle" (`width:82%;aspect-ratio:5/3;margin-left:auto`) — immediately corrected: "아니 섹션
   전체를 줄이는게 아니고, 그 이미지부분만!" (no, not shrinking the whole section/card — just the
   image part). Reverted the card back to `width:100%;aspect-ratio:1/1`; the actual fix belongs
   on `.momo8-shot` (or a new element around it), not on `.company-diagram` itself — see that
   rule for the current state. */
.company-diagram{
  width:166.667%;margin-left:-66.667%;aspect-ratio:5/3;margin-top:clamp(20px,3vw,32px);
  border-radius:16px;overflow:hidden;box-shadow:inset 0 0 0 1px var(--hair);
  position:relative;display:flex;align-items:center;justify-content:center;
  background-color:var(--card);
  background-image:
    repeating-linear-gradient(0deg, rgba(91,101,103,.16) 0 1px, transparent 1px 40px),
    repeating-linear-gradient(90deg, rgba(91,101,103,.16) 0 1px, transparent 1px 40px);
}
.company-wave{position:absolute;left:0;top:38%;width:200%;height:24%;animation:company-wave-drift 18s linear infinite}
.company-wave svg{display:block;width:100%;height:100%}
.company-wave path{fill:none;stroke:var(--graphite);stroke-width:1.5;opacity:.55}
@keyframes company-wave-drift{0%{transform:translateX(0)}100%{transform:translateX(-50%)}}
@media(prefers-reduced-motion:reduce){.company-wave{animation:none}}
/* momo8-topview-halo.png's real detail tops out at its 223x220 source (4x-upscaled via LANCZOS
   for the halo dilation step, which doesn't add information) — at the previous 58% width the
   interpolation blur from stretching past native resolution was visible ("png가 화질이 좀
   낮구나"). 40% keeps it inside the range where it reads as crisp/pixel-perfect; don't push the
   display size back up without re-deriving the halo from a higher-res source first. */
.company-diagram .momo8-shot{position:relative;z-index:1;height:40%;width:auto;margin-right:15%}
/* directional "sound arrived from here" pulse: 32 pie-slice wedges radiating from the card's
   center (`gen_company_wedges`-style: 480,480 viewBox, cx=cy=240, r=340 so every wedge reaches
   past the square's corners regardless of angle — `.company-diagram`'s own overflow:hidden
   clips them back to the rounded square). All 32 sit at opacity:0 in markup; `site.js`
   (`initCompanyPulse`) darkens a contiguous run of 2-5 wedges at a time via Web Animations
   (0 → ~0.16 → 0, fade-in quick/fade-out slow — see the timing note in `site.js`), roughly
   every 3-4s, never starting the same run twice in a row. Drawn as the topmost layer
   (`z-index:2`, above the momo8 image) so the tint sweeps across the whole card like a
   directional radar hit, not just the background.
   Two finishing touches, both explicit requests once the plain flat-ink wedges were working:
   `fill:url(#pulse-fade)` (a `radialGradient` in the SVG's `<defs>`, `userSpaceOnUse` centered on
   the same `240,240` the wedges radiate from, `r:260` — deliberately less than the `340` wedge
   radius, so every wedge is fully faded to transparent well before it reaches the card's frame,
   "frame쪽으로 가면 좀 투명해지게") replaces the flat `fill:var(--ink)` so each wedge fades on
   its own independent of the opacity keyframe pulsing over it. `filter:url(#pulse-blur)` (a
   `feGaussianBlur` in the same `<defs>`, generous `-60%/-60%/220%/220%` filter region so the
   blur isn't clipped at the wedge's own bounding box) softens the wedge's straight angular edges
   too, "부채꼴의 양쪽도 blur" — without this the two straight sides of each pie slice read as a
   hard-edged cutout.
   **The gradient direction was flipped once, after the above was already live**: originally
   opaque-center/transparent-frame (see the `pulse-fade` stop order at the time); explicit
   follow-up — "그라데이션을 프레임쪽에서 투명하게 말고, 중앙쪽을 투명하게 한 번 바꿔봐줄래" —
   reversed it to transparent-center/opaque-frame instead (stops now `0%→opacity:0`,
   `45%→.3`, `100%→1`), so the device itself stays visually clear and the tint reads instead as
   arriving *from* the card's edge/frame. Don't flip this back without being asked. */
.company-pulse{position:absolute;inset:0;z-index:2;pointer-events:none}
.company-wedge{fill:url(#pulse-fade);filter:url(#pulse-blur);opacity:0}
/* directional beam — a thin line that visibly travels outward from just past the device's own
   edge toward the fired wedge run's bearing, "이미지 가운데부터 빔이 하나... 날아가게". One
   static line at angle 0 (pointing along +x); `initCompanyPulse` in site.js rotates it per-event
   with `transform="rotate(deg 340 240)"` (same center as the wedges/gradient), same "one rotated
   line, not 32 pre-drawn beams" approach as before.
   **First version was opacity-only** (fade in/out in place, no motion) — explicit feedback:
   "빔 날아가는 효과가 너무 빨라서 날아가는걸로 안보인다. 천천히 날아가는데" (the fly-out doesn't
   read as motion at all, it should travel slowly) — so it needed an actual travel animation, not
   just a fade. Also: "처음 날아가는거는 안보이게 해줘. 너무 바디에서 날아가네" (hide the very
   start of the flight — it's launching too obviously from the device body) and "점선말고 얇은
   선이면 될 것 같아" (a thin plain line instead of dotted).
   Fixed with the classic SVG "line draw" technique: `x1,y1` sits at radius 115 from the device
   center (`455,240` — comfortably past the device's own ~96-unit radius, so the line is never
   seen touching/emerging from inside the device) and `x2,y2` at radius 480 (`820,240`, same
   overshoot-and-let-`overflow:hidden`-clip convention as the wedges). Segment length is exactly
   `480-115=365` — `stroke-dasharray:365;stroke-dashoffset:365` in the *base* (non-animated) CSS
   fully hides the line as a single unbroken dash pulled completely off itself. `initCompanyPulse`
   animates `stroke-dashoffset` from `365` down to `0` (the line visibly extends/"flies" outward
   from its near end) alongside `opacity`, both in one WAAPI call — see the keyframe shape in
   site.js for the exact timing. No `pulse-blur` filter, so it reads as a crisp distinct signal
   against the soft wedge tint underneath it.
   **Follow-up: inner end too opaque, and a glow requested** — "빔도 안쪽이 더 옅어야해. 밖으로
   나갈수록 약간 두꺼워질 수 있으면 더 좋고" (the beam should be fainter at its inner end, and
   ideally read as slightly thicker toward the outside) plus, separately, "반투명 하얀 글로우가
   있어도 좋겠다" (a semi-transparent white glow would be nice). A single stroked `<line>` can't
   taper its own width, so rather than build a filled tapering shape (which would've needed a
   different, riskier reveal-animation technique than the proven dasharray/dashoffset trick), both
   asks were solved by keeping the *same* line geometry/reveal animation and layering a second
   line under it: `.company-beam-group` (a plain `<g>`, only used so `initCompanyPulse` can rotate
   both children with one `transform` call, same as before) now holds `.company-beam-glow`
   (stroke-width 5, blurred via a new `#beam-blur` filter, white `#beam-glow-fade` gradient) below
   `.company-beam` (unchanged position/width, but its flat `stroke:var(--ink)` swapped for
   `url(#beam-fade)`). Both gradients are `linearGradient`s, `gradientUnits="userSpaceOnUse"`,
   running along the *same* `455,240 → 820,240` axis as the line geometry itself — `stop-opacity`
   goes `0` at the near end to `.7` (ink) / `.6` (white glow) at the far end, so the beam is
   genuinely faint near the device and reads stronger/"thicker" (the glow widens the visual
   footprint) toward the tip, without literally varying `stroke-width` along the path.
   `#beam-blur`'s filter region is declared with `filterUnits="userSpaceOnUse"` and explicit
   absolute coordinates (`x="440" y="215" width="400" height="50"`), not percentages — the line's
   own geometric bounding box has zero height (`y1===y2`), so a percentage-based region (the
   `pulse-blur` filter's approach) would resolve to a zero-height clip and blur nothing; this
   filter needed real user-space units instead. Both lines share the exact same `dasharray:365`/
   animated `dashoffset` and `opacity` keyframes (two separate `.animate()` calls in
   `initCompanyPulse`, kept in sync by using identical keyframe timing) so the glow and the core
   line travel and fade together. If the beam's endpoints (`455,240`/`820,240`) ever move again,
   both gradients' `x1/y1/x2/y2` and the filter's region need updating to match — they're
   independent SVG elements, nothing recomputes them automatically. */
.company-beam-glow{stroke:url(#beam-glow-fade);stroke-width:13;stroke-linecap:round;filter:url(#beam-blur);stroke-dasharray:365;stroke-dashoffset:365;opacity:0}
.company-beam{stroke:url(#beam-fade);stroke-width:3.6;stroke-linecap:round;stroke-dasharray:365;stroke-dashoffset:365;opacity:0}

/* console-style detection log, top-left of the card — each pulse event (see `initCompanyPulse`
   in site.js) adds one column-aligned line: TIME · SOUND · dB+gauge · BEARING, the bearing taken
   directly from the wedge run's own start/end angle so the log always matches what's on screen.
   `.company-log` is a fixed-height (4 lines × `LOG_LINE_H`) `overflow:hidden` window;
   `.company-log-inner` holds the actual `.terminal-line` stack and is the thing site.js
   transform-scrolls by `-18px` then snaps back to `0` after removing the oldest line — the
   classic "conveyor belt" ticker trick, chosen over deleting the old line outright because the
   explicit ask was "다음 줄이 들어오면 위로 스크롤되서 제일 오래된게 사라지는 느낌" (the newest
   line pushes in and the oldest scrolls away, not just vanishes). `height:72px` with **no
   padding** on `.company-log` itself is deliberate — this box previously had `padding:7px 10px`
   with `box-sizing:border-box` (the site-wide reset), which silently ate into the 72px content
   height and clipped the 4th line ("네 번째 줄이 짤리는 문제"); removing the padding (needed
   anyway once the background pill was dropped, see below) fixed it as a side effect. Each line
   is typed in character-by-character (`site.js`, 22ms/char) rather than appearing all at once —
   "타이핑되는 효과", "콘솔에 뜨는 디버그메시지 느낌". Uses `var(--mono)` since this is exactly the
   kind of console/terminal readout that font is scoped for. `.terminal-line` was previously a
   dead class name (only referenced in the `prefers-reduced-motion` block below with nothing to
   disable) — it now has a real per-line entrance animation. Sound names are drawn from a
   ~100-entry list in `site.js` (`SOUND_TYPES`) — add more there rather than inventing a second
   list, keep entries lowercase (the log line capitalizes the first letter itself). The whole log
   is gated behind the same `reduced` check as the pulse itself in `initCompanyPulse` — under
   reduced motion neither the wedges nor the log run.
   **No background pill** ("뒷 배경은 없어도 돼") — the dark `rgba(13,18,19,.7)` box from the
   first version is gone; text sits directly on the card's own light `--card` background. This
   flips the whole color story: the first version's palette (`#B9C4C2` etc.) was chosen for a
   *dark* backing and would be near-invisible here, so every log color below is a dark-on-light
   variant chosen for contrast against `--card`, not reused from the diorama terminal's
   dark-background palette (that one's `--amber` is explicitly "for dark backgrounds" per the
   design tokens — don't reach for it here).
   **ANSI-style coloring + a text gauge** ("ANSI컬러처럼 글자에 색이나 게이지같은거를") — ported
   from a single plain string to an array of `{text, cls}` segments (`buildLineHTML` in site.js)
   specifically so the typewriter can reveal multiple colors within one line. Columns, in order:
   `.log-time` (dim graphite timestamp, `HH:MM:SS`) · `.log-sound` (ink, left-padded to a fixed
   19 chars so every column lines up — "컬럼으로 정렬돼도 좋겠어") · a dB number + a 10-block
   `[███░░░░░░░]`-style gauge (`dbGauge()` in site.js), both colored by loudness via `dbClass()`
   (`.log-db-lo` <60dB, `.log-db-mid` 60-85dB, `.log-db-hi` ≥85dB) · `.log-bearing` (zero-padded
   3-digit degrees, `var(--ultra)`). */
.company-log{
  position:absolute;top:14px;left:14px;z-index:3;max-width:92%;
  height:72px;overflow:hidden;pointer-events:none;
}
.company-log-inner{display:flex;flex-direction:column}
.terminal-line{
  height:18px;line-height:18px;overflow:hidden;white-space:pre;
  font-family:var(--mono);font-size:11px;
  animation:log-line-in .25s var(--ease-out);
}
.log-time{color:var(--graphite)}
.log-sound{color:var(--ink);font-weight:600}
.log-db-lo{color:#3F8B63}
.log-db-mid{color:#B6672E}
.log-db-hi{color:#C23B30}
.log-bearing{color:var(--ultra)}
@keyframes log-line-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}

.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--hair);border:1px solid var(--hair);margin-top:30px}
.card{background:var(--card);padding:26px 24px;position:relative}
.card h3{margin-bottom:8px}
.card p{margin:8px 0 0;font-size:14.5px;color:#2A3238;line-height:1.5}
.badge{
  display:inline-block;font-family:var(--body);font-weight:600;font-size:10px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--amber);border:1px solid var(--amber);padding:2px 7px;margin-left:8px;white-space:nowrap;vertical-align:middle;
}
@media(max-width:860px){.cards{grid-template-columns:1fr}}

/* Pricing-style product lineup (products/#lineup) — three separated cards (gap between them,
   not the hairline-grid look .cards uses), each with a "stat" line standing in for a price.
   Headers are the loud element (big display-font product name) with one small vision sentence
   under it, not a small eyebrow label — that read as too quiet/passive. Each card carries its own
   line-art tile+shade background (::before) and a soft blurred echo of its own frame (::after) —
   both purely decorative, kept out of the tab order and behind the actual content via z-index. */
.lineup-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;margin-top:40px}
.lineup-card{
  position:relative;overflow:hidden;border-radius:14px;
  background:var(--card);border:1px solid var(--hair);padding:32px 28px;display:flex;flex-direction:column;
}
.lineup-card::before{
  content:'';position:absolute;inset:0;pointer-events:none;
  background-image:
    linear-gradient(rgba(13,18,19,.05) 1px,transparent 1px),
    linear-gradient(90deg,rgba(13,18,19,.05) 1px,transparent 1px),
    radial-gradient(circle at 100% 100%,rgba(13,18,19,.07),transparent 60%);
  background-size:22px 22px,22px 22px,100% 100%;
}
.lineup-card::after{
  content:'';position:absolute;inset:0;pointer-events:none;border-radius:14px;
  border:1px solid var(--hair);filter:blur(4px);opacity:.7;
}
.lineup-card>*{position:relative;z-index:1}
.lineup-card h3{margin-top:0;font-family:var(--display);font-weight:700;font-size:clamp(32px,3.6vw,42px);letter-spacing:-.01em;line-height:1.05;min-height:2.2em}
.lineup-vision{margin-top:10px;font-size:14.5px;line-height:1.6;color:var(--graphite);min-height:4.8em}
.lineup-frame{
  margin-top:18px;aspect-ratio:16/10;border-radius:14px;background:#fff;border:1px solid var(--hair);
  display:flex;align-items:center;justify-content:center;overflow:hidden;
}
.lineup-frame img{width:100%;height:100%;object-fit:contain;padding:14px}
.lineup-frame--empty{background:repeating-linear-gradient(135deg,var(--card),var(--card) 10px,#fff 10px,#fff 20px)}
.lineup-frame--inset img{width:64%;height:auto;object-fit:contain;padding:0}
.lineup-frame--zoom img{width:100%;height:200%;object-fit:cover;padding:0}
.lineup-stat{
  display:inline-flex;align-items:baseline;gap:6px;margin-top:20px;
  padding:12px 20px;border-radius:999px;background:#fff;border:1px solid var(--hair);
  box-shadow:0 2px 8px rgba(13,18,19,.06);
  font-family:var(--display);font-weight:600;font-size:clamp(18px,2vw,22px);color:var(--ink);
}
.lineup-stat span{font-family:var(--body);font-weight:500;font-size:13px;color:var(--graphite);letter-spacing:.02em;text-transform:uppercase}
.lineup-list{margin-top:20px;display:flex;flex-direction:column;gap:10px;list-style:none;padding-left:0}
.lineup-list li{font-size:13.5px;line-height:1.5;color:var(--graphite)}
.lineup-note{margin-top:20px;font-size:12.5px;color:var(--graphite)}
@media(max-width:860px){.lineup-grid{grid-template-columns:1fr}}

/* #products, home page: replaced the 3-card grid with a numbered list of our three fields —
   explicit request to drop the "Products" eyebrow and instead label them "1/2/3"
   ("우리 분야 세 개를 1, 2, 3으로... 큰 글씨로다가"). Content describes the fields themselves
   (acoustic sensing hardware / modular microphone arrays / edge AI & signal processing), not
   specific product SKUs — product names belong on products/index.html, not here.
   **Descriptions dropped, title promoted to match the number** — first pass kept each item's
   card-copy paragraph at a small 15px body size next to the huge number, which read as
   mismatched/uncomposed: "지금은 숫자만 커서 통일감이 없어. 세 분야만 써주면 될 것 같아" (only
   the number was big, no sense of unity — just write the three fields). Fixed by removing the
   `<p>` description entirely (`.numbered-body` wrapper div also dropped, `h3` is now a direct
   flex child next to `.numbered-index`).
   **Editorial/magazine pass** — two more explicit fixes: "왜 1,2,3이랑 글자크기가 다른거임? 중간에
   줄도 없어도 되고, 디자인잡지의 기사처럼 써주면 돼" (why is the number a different size than the
   text? drop the divider lines, write it like a design-magazine article) and "중앙정렬보다는
   2섹션과 비슷한 정렬이 더 낫고" (prefer section 2's flush-left alignment over this centered
   look). Fixes: (1) `.numbered-index` now shares the exact same `font-size`/`line-height` clamp
   as `.numbered-item h3` — number and title read as one continuous editorial line, not a big
   numeral bolted onto a smaller label (weight still differs, 800 vs 700, so the number stays a
   touch heavier as the "marker" without being a different size). (2) `border-top`/`border-bottom`
   removed — no ruled lines, just generous vertical spacing between entries, closer to how a
   magazine feature lists numbered items than a bordered table row. (3) The apparent "center
   alignment" wasn't a text-align rule — `#company,#products{justify-content:center}` centers
   each section's sole flex child, and `#company`'s `.wrap.cols` (two columns of text + the
   diagram) is naturally wide enough to hit its `max-width:1180px` cap and sit flush against the
   section padding, while `#products`' plain `.wrap` (a single narrower text column) shrink-wraps
   to its own content width and centers *that* narrower box on the page instead. Added
   `#products .wrap{width:100%}` so it fills the available width the same way `#company`'s does,
   then its own `max-width`/`margin:0 auto` centers *within* that full width — same left edge as
   section 2's headline. */
.numbered-list{margin-top:40px}
.numbered-item{display:flex;align-items:baseline;gap:clamp(20px,4vw,48px);padding:clamp(18px,2.6vw,28px) 0}
.numbered-index{font-family:var(--display);font-weight:800;font-size:clamp(26px,4.2vw,44px);line-height:1.05;letter-spacing:-.01em;color:var(--ink);flex:none}
.numbered-item h3{font-family:var(--display);font-weight:700;font-size:clamp(26px,4.2vw,44px);line-height:1.05;letter-spacing:-.01em;color:var(--ink);margin:0}
/* the one-line-per-item copy added right after this ("공백이 많아가지고... 한마디씩") was first
   appended straight into `h3` at the same big size as the title — immediate follow-up: "글자도
   중요한거만 큰 글씨여야되는데 지금 다 커서 너무 난삽해" (only the important part should be big;
   with everything big, nothing reads as emphasized and the whole thing feels cluttered). Moved
   into a nested `<span class="numbered-desc">` so the title keeps the big unified number/title
   treatment and the phrase reads as a small secondary caption underneath it instead. */
.numbered-desc{display:block;margin-top:6px;max-width:34em;font-family:var(--body);font-weight:500;font-size:15px;line-height:1.5;letter-spacing:0;color:var(--graphite)}
#products .wrap{width:100%;position:relative;z-index:1}
@media(max-width:720px){.numbered-item{flex-direction:column;align-items:stretch;gap:8px}}

/* #products' background, 3rd design — the diagonal sine-grid (two crossing families of wavy
   lines) was scrapped outright: "세 번째 섹션의 배경화면은 아직도 문제야. 너무 말도 안되는
   웹이긴 해 지금. 각기 다른 sinc function의 산들이 물결치는 느낌이 낫겠어" (still a problem, it
   looks too nonsensical right now — layered "mountains" built from different sinc functions,
   rippling, would be better). Now `.products-sinc-bg` holds 5 stacked ridge-line layers, each a
   full-width horizontal line whose height is the *sum* of a few randomized sinc bumps
   (`sin(pi·t)/(pi·t)`, 2–4 per layer, each with its own random center/amplitude/width) instead of
   a single sine wave — summing several independently-decaying sinc bumps is what gives each
   ridge its organic, uneven "mountain range" silhouette rather than one uniform ripple, and no
   two layers use the same random bumps ("각기 다른"). Baselines are spread across the tile height
   (`TILE_H × (0.18 + 0.16·i)`) and amplitude/stroke-width/opacity all scale with depth (`i /
   (N_LAYERS-1)`) — back layers thin/faint, front layers bolder — for a simple parallax read.
   **Structural note, don't "simplify" this back to one shared SVG**: each layer is its own HTML
   `<div class="products-sinc-layer">` (width:200%, translateX 0→-50% loop) wrapping an SVG whose
   own viewBox spans two tiles side by side — the same split used by `.wave-mesh`/`.company-wave`
   (outer HTML element carries the CSS transform/animation, inner SVG just holds the doubled
   path data). A single shared `<svg>` with per-`<g>` CSS transforms was tried first and doesn't
   reliably behave the same way for percentage-based translateX on bare SVG groups. Because each
   layer's ridge is a sum of randomly-placed sinc bumps (not a periodic function), the loop can
   only be seamless by literally duplicating the exact same path a second time at `+TILE_W` (which
   `gen_products_sinc.py` does), not by relying on any inherent periodicity the way the diagonal
   grid's matched wavelength/spacing did — regenerate via that script (new `random.seed(...)` for
   a different arrangement) rather than hand-editing path data.
   Two speed classes (`products-sinc-slow`/`-fast`, split by depth) instead of one shared duration,
   for a bit of parallax drift. `#products{position:relative;overflow:hidden}` is unchanged from
   the grid version — still needed so the doubled-width layers don't cause horizontal overflow,
   and `#products .wrap{z-index:1}` still keeps the numbered list above this background. */
#products{position:relative;overflow:hidden}
/* a big, faint ghost of the momo8 PCB top-down diagram, bled off the right edge behind the
   ridge-line background — "toptransparent.png 이미지를 왼쪽절반만 보이게 오른쪽에 반투명하게
   배치하는거 어때... 일단 넣어줘봐". `right:0` + `translate(50%,0)` shifts the image right by
   exactly half its own width, so its horizontal center lands on the section's own right edge —
   the left half stays in frame, the right half is clipped away by `#products`'s own
   `overflow:hidden`, giving the "only the left half is visible" crop without any masking math.
   `height:100%;width:auto` sizes it to the *section's* full height, not a fixed px/vw width
   (explicit follow-up: "높이로는 섹션3 전체를 채우는게 좋겠는데" — height-wise it should fill all
   of section 3) — so if `#products`'s own height changes (more/less numbered-item text, viewport
   size), this stays sized to match rather than needing its own clamp() re-tuned by hand.
   **The source PNG needed a real alpha channel, not just low opacity.**
   `.reference/momo8b_pictures/toptransparent.png` has no actual transparency despite the
   filename — checked directly (`Image.info` has no `transparency` key); the "background" is a
   flat mid-gray (`rgb(166,166,166)`) baked into the pixels. A first pass copied it as-is and
   relied on low CSS `opacity` alone — the flat gray rectangle still read as a hard-edged box
   against the card. Fixed by actually generating alpha: flood-fill/connected-component labeling
   (`scipy.ndimage.label`) on a "close to that flat gray" mask, keeping only the components that
   touch the image's own border as "background" — the diagram's internal gray fills (component
   bodies, shading) aren't connected to the border region, so they survive untouched while the
   true background goes transparent. The deployed `assets/momo8-toptransparent.png` is this
   regenerated RGBA version, not the raw reference file — if it ever needs regenerating (different
   crop/source), reuse that flood-fill-from-border approach, not a flat-opacity shortcut.
   Opacity itself started at `.1` (too faint per follow-up: "반투명은 너무 많이했고") and was
   raised to `.22`. No rotation, no drift/animation — the user explicitly noted the part has no
   obvious "up" to rotate around and asked for it placed statically; don't add motion here without
   being asked. First child of `#products` (before `.products-sinc-bg`) purely for DOM-order
   stacking, so the ridge lines paint on top of it ("function들 뒤쪽에" — behind the ridge
   functions).
   **Deliberate mosaic/pixelation was tried, then reverted**: "이미지 처리도 할 수 있나? 약간만
   모자이크 주면 확대하면서 일그러진 픽셀들이 효과처럼 보일 수도 있을 것 같은데" — downscaled
   `assets/momo8-toptransparent.png` to 280×184 with `Image.NEAREST` and set `image-rendering:
   pixelated` so the browser's upscale showed flat blocks instead of blurring them. Walked back
   next round: "좀 너무 뭉개지기는 했네... pixelate보다는 upscale 할 수 있는 방법이 있으면 그거를
   해야겠어" (it got too mushy — if there's a way to upscale rather than pixelate, do that
   instead). No real super-resolution/upscale model is available in this environment (Replicate's
   MCP code-execution is still off, re-confirmed by testing it directly — same `410 Gone` as
   earlier this session; needs the user's own `--code-execution-mode=local` fix, still not done).
   The only actually-available fix was to undo the deliberate downscale: `assets/
   momo8-toptransparent.png` is back to the full flood-filled **704×464** RGBA, and `image-
   rendering:pixelated`/`crisp-edges` were removed so the browser's normal smooth scaling applies.
   This isn't a "sharper" image than before the mosaic experiment — it's the same source at its
   own native resolution, just no longer deliberately blocked-in. If a genuinely higher-resolution
   version is wanted later, that requires either a better source photo or a working super-
   resolution tool — don't reach for `Image.LANCZOS`/similar as a substitute, upscaling a raster
   image without a generative model doesn't add real detail, it would look the same as just
   letting the browser scale the existing 704×464 source (which is what happens now anyway).
   **Also clarified: the aspect ratio didn't actually change.** "w/h 비율이 바뀌었어? 좀 위아래로
   길어진것같은데" — checked directly: 704×464 (ratio 1.5172) vs. the mosaic pass's 280×184 (ratio
   1.5217), a 0.3% difference, not visually perceptible. The "elongated" read came from the crop,
   not the asset: `translate(70%,0)` only leaves `(100-70)=30%` of the image's own width visible,
   and that narrow sliver is stretched to the section's *full height* (`height:100%`) — a
   `0.3×(image's own aspect ratio)`-ish visible slice is inherently tall and narrow regardless of
   the source file's real proportions. If the "too tall/narrow" feeling persists after this
   revert, the fix would be lowering the `70%` translate (showing more width) rather than touching
   the image file.
   **Real upscale, done outside this session**: "내가 업스케일을 다른세션에서 해왔어
   toptransparent_3x_~ 이 파일이야" — with Replicate still inaccessible here (see above), the user
   ran an actual super-resolution/upscale pass themselves elsewhere and supplied
   `.reference/momo8b_pictures/toptransparent_3x_transparent.png`: 2112×1392 (a true 3x of the
   704×464 original), genuine RGBA with real alpha (not the flat-gray-background problem the raw
   reference photo had) — no flood-fill step was needed this time, the file already has correct
   transparency. Deployed as `assets/momo8-toptransparent.png`, but not at full 3x size — the raw
   3x PNG was 3.2MB, too heavy for a decorative background layer, so it's saved at a resized
   **1408×928** (2x-equivalent, `Image.LANCZOS`, alpha preserved) at ~1.36MB. Explicit follow-up
   offered "용량이 너무 크면 jpeg로 줄여도 되고" (JPEG if the file's too big) but JPEG has no alpha
   channel — flattening to JPEG would either reintroduce a hard-edged background box (the exact
   problem the flood-fill fix solved earlier) or require exactly matching the flatten color to
   whatever's behind it, which is fragile if the section's background ever changes. Resizing while
   keeping PNG's alpha was the safer tradeoff; if file size becomes a real problem again, resize
   further down (a 1.5x pass, ~1056×696, came out around 840KB in testing) rather than switching
   formats.
   **Follow-up: still read as too tall, and the outer boundary looked rough**: "아무리봐도
   위아래로 좀 긴데.. 약간만 조절해봐 옆이 좀 길도록. 그리고 원 맨 바깥쪽을 조금만 깍아내면
   깔끔할 것 같아. 지금 경계선이 좀 지저분하거든." Two independent fixes:
   1. `translate(70%,0) → translate(58%,0)` — reveals `42%` of the image's width instead of `30%`,
      widening the visible slice relative to the fixed `height:100%` (see the note above this one
      for the "why 70% reads as tall" mechanism — same math, just a less aggressive crop now).
   2. The image itself got a small alpha erosion to clean up the boundary: binary-erode the
      thresholded alpha mask (`scipy.ndimage.binary_erosion`, **12 iterations** ≈ 12px inward at
      this resolution) then a light `GaussianBlur(1.0)` for a soft re-antialiased edge, taking the
      pixelwise minimum with the original alpha (so this can only ever shrink the silhouette, never
      grow it). 12px was chosen after testing a range — 6px left the AI-upscale's slightly
      discolored edge fringe still visible when zoomed in, 18px started visibly thinning the black
      outer ring at high zoom (though it still read fine at normal/full-image viewing scale — 18px
      just had less margin for error). If this needs redoing, regenerate from
      `assets/momo8-toptransparent.png`'s *current* state (it's already been eroded once — don't
      compound erosions blindly on top of an already-trimmed file without checking how much margin
      is left first). */
.products-diagram-ghost{position:absolute;right:0;top:0;transform:translate(clamp(56%,calc(65% + 0.0078125*(100vw - 1280px)),72%),0);height:min(100%,100vh);width:auto;opacity:1;z-index:0;pointer-events:none}
.products-sinc-bg{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:0}
.products-sinc-layer{position:absolute;inset:0;width:200%;height:100%}
.products-sinc-layer svg{display:block;width:100%;height:100%}
.products-sinc-ridge{fill:none;stroke:var(--graphite)}
.products-sinc-slow{animation:products-sinc-drift 55s linear infinite}
.products-sinc-fast{animation:products-sinc-drift 32s linear infinite}
@keyframes products-sinc-drift{0%{transform:translateX(0)}100%{transform:translateX(-50%)}}
@media(prefers-reduced-motion:reduce){.products-sinc-slow,.products-sinc-fast{animation:none}}

.tlink{
  display:inline-flex;align-items:center;gap:6px;margin-top:24px;text-decoration:none;
  font-family:var(--body);font-weight:600;font-size:13px;letter-spacing:0;text-transform:none;color:var(--ultra);
  border-bottom:1px solid rgba(91,104,100,.35);padding-bottom:2px;transition:border-color var(--dur-fast) var(--ease-out);
}
.tlink:hover{border-color:var(--ultra)}

/* ── stat row (problem / gap section) ── */
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--hair);border:1px solid var(--hair);margin-top:30px}
.stat{background:var(--card);padding:22px 20px}
.stat .n{font-family:var(--display);font-weight:700;font-size:clamp(26px,3vw,36px);letter-spacing:-.02em;line-height:1}
.stat p{margin:10px 0 0;font-size:14px;color:#2A3238;line-height:1.45}
.stat .src{display:block;margin-top:10px}
@media(max-width:720px){.stats{grid-template-columns:1fr}}

/* ── spec table ── */
table{width:100%;border-collapse:collapse;margin-top:24px;font-size:14.5px}
th,td{text-align:left;padding:11px 14px;border-bottom:1px solid var(--hair)}
thead th{font-weight:600;font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--graphite);border-bottom:1px solid var(--graphite)}
td:first-child{width:42%;color:#2A3238}
td.val{font-weight:600}
.flag{
  font-family:var(--body);font-weight:600;font-size:10px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--amber);border:1px solid var(--amber);padding:1px 5px;margin-left:8px;white-space:nowrap;
}

/* ── CES banner (full-bleed, used as the CES 2027 page hero) — solid black on purpose, not
   var(--ink)/rings-bg, so the hero shot's own black background blends in with no visible seam.
   No eyebrow anymore ("메시지만 약해진다") — the h1 carries the whole message on its own.
   Two-column (`.cols`): left is text, right is the hero shot at full column size — a single
   centered column left the photo small no matter how large `max-width` went, since it had to share
   width with a wide block of text stacked above it. `align-items:start` (not `center`) so the image
   anchors to the top of the row instead of being vertically centered against it — centering had
   been pushing the whole left column down with it, squeezing away the visible gap below "Book a
   meeting" ("book a meeting이랑 아래 여백까지 보이게 해줘"). */
.ces-banner{position:relative;overflow:hidden;background:#000;color:var(--paper);padding:clamp(64px,9vw,120px) var(--pad)}
.ces-hero-cols{position:relative;z-index:1;align-items:start;grid-template-columns:1.3fr 1fr}
.ces-banner h1{color:var(--paper);margin:0}
.ces-h1-a,.ces-h1-b{display:block}
.ces-h1-b{margin-top:.22em}
.ces-glow{color:#4DD9D8;text-shadow:0 0 18px rgba(77,217,216,.65),0 0 40px rgba(77,217,216,.35)}
.ces-banner .btn{background:var(--paper);color:var(--ink);border-color:var(--paper)}
.ces-banner .btn:hover{background:var(--amber);border-color:var(--amber);color:var(--ink)}
.ces-caps{display:flex;gap:10px;flex-wrap:wrap;margin-top:28px}
.ces-cap{
  font-family:var(--body);font-weight:600;font-size:11px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--paper);border:1.5px dashed rgba(255,255,255,.6);border-radius:999px;padding:7px 16px;white-space:nowrap;
}
.ces-hero-frame{display:flex;align-items:center;justify-content:center}
.ces-hero-shot{display:block;width:100%;height:auto;max-height:560px;object-fit:contain}
.ces-cta{display:inline-flex;padding:18px 40px;font-size:16px;margin-top:36px;border-radius:999px}
@media(max-width:860px){.ces-hero-frame{margin-top:36px}}

/* Decorative texture over the *whole* hero, including the photo — asked for specifically, since
   putting it behind the photo instead would visibly cut off at the photo's own edge ("히어로 이미지
   뒤에 깔면 영역이 짤려보일테니까 그 위에다가"). A light-gray grid (same idea as `.hero-tiles`, but
   light-on-black instead of dark-on-light) plus a Dirichlet-kernel sinc-wave band (same technique as
   `.hero-sinc` elsewhere, own coefficients `N=5`/`N=9` phase-shifted by `TILE_W/5` so it doesn't
   read as a copy-paste of the company/products pages' waves) — both sit in normal flow order after
   `.ces-hero-cols` but paint on top since they're `position:absolute` with no explicit stacking
   conflict; `.ces-hero-cols` is bumped to `z-index:1` so the actual text/photo still sit above this. */
.ces-hero-grid{
  position:absolute;inset:0;pointer-events:none;
  background-image:
    linear-gradient(rgba(255,255,255,.1) 1px,transparent 1px),
    linear-gradient(90deg,rgba(255,255,255,.1) 1px,transparent 1px);
  background-size:44px 44px;
  filter:blur(1.5px);
  -webkit-mask-image:linear-gradient(to top,#000 0%,#000 50%,transparent 100%);
  mask-image:linear-gradient(to top,#000 0%,#000 50%,transparent 100%);
}
.ces-hero-sinc{position:absolute;left:0;right:0;bottom:2%;height:180px;overflow:hidden;pointer-events:none}
.ces-hero-sinc-track{position:absolute;top:10px;left:0;width:200%;height:320px;animation:hero-sinc-drift 46s linear infinite}
.ces-hero-sinc-track svg{display:block;width:100%;height:100%;filter:blur(6px)}
.ces-hero-sinc-ridge{fill:none;stroke:rgba(255,255,255,.16);stroke-width:5}
.ces-hero-sinc-ridge--b{stroke:rgba(255,255,255,.1)}
@media(prefers-reduced-motion:reduce){.ces-hero-sinc-track{animation:none}}

/* ── home page's closing CES 2027 mention — plain section, not a boxed promo card ──
   Explicitly softened: "마지막 CES2027 카드는 Meet us in CES 2027 정도로 부드럽게 가는게
   좋겠어. 카드 아니고 섹션이어도 되고" — the old `.ces-card` (dark, min-height:88vh, huge
   clamp(44px,9vw,108px) headline, "Come and clap" eyebrow) was a loud "app-download slot"
   treatment; replaced with a plain centered `section` using the same base light background and
   type scale as every other section on the page, no eyebrow, gentler heading copy.
   **Later revisited — deliberately made bold again, but not by bringing back the old card**:
   "Meet us in CES 2027 섹션은 색이 좀 달라도 될 것 같은데" led to a recommendation (subtle
   `--card` background swap, no new hue, respecting the site's monotone rule) which the user then
   explicitly overrode: "음 좀 확실히 튀어도 될 듯.. CES2027의 컨셉컬러가 있다면 그걸 좀 응용해도
   되고, 배경에 gradiation으로 깔아줘도 됨. 예쁜 ripple 모양이 된다면" (fine to actually stand out —
   apply the CES2027 concept color if there is one, a gradient background is fine too, especially
   if it forms a nice ripple shape). "CES2027의 컨셉컬러" in this codebase *is* `ces2027/index.html`'s
   own `.ces-banner` treatment (`--ink` background, `--amber` accent, `#8C979E`/`#C8CFD4` muted
   text) — reused here rather than inventing a new saturated color, so this stays inside the
   existing "no vivid brand color" system while still being the boldest real contrast available on
   the page (dark section against every other section's light `--paper`). The "ripple" is a plain
   CSS `repeating-radial-gradient` — thin `--amber`-tinted rings, no SVG — centered slightly above
   the section's vertical middle (`50% 42%`) so it reads as emanating from roughly where the
   heading sits, which doubles as a literal "sound rippling outward" motif for an acoustic company.
   A `radial-gradient` vignette (`mask-image`, black-to-transparent) fades the rings out toward the
   edges so they concentrate near the center instead of getting hard-cut at the section's boundary
   — same masking technique as `.wave-mesh`'s diagonal fade elsewhere on this page.
   **This is a deliberate, requested exception to the "no static concentric-ring pattern" rule
   documented below (`.rings-bg` / "Background glow, not static rings")** — that rule was about the
   old page-wide `.rings-bg` reading as generic decorative clutter; this ripple is intentional,
   thematically tied to sound, and scoped to one closing section, not a reversion of that decision. */
#ces2027{position:relative;overflow:hidden;background:var(--ink);color:var(--paper);border-top-color:rgba(255,255,255,.08)}
#ces2027::before{
  content:'';position:absolute;inset:0;pointer-events:none;z-index:0;
  background:
    radial-gradient(circle at 50% 42%,rgba(139,149,145,.16),transparent 60%),
    repeating-radial-gradient(circle at 50% 42%,
      rgba(139,149,145,0) 0,rgba(139,149,145,0) 54px,
      rgba(139,149,145,.22) 55px,rgba(139,149,145,.22) 57px,
      rgba(139,149,145,0) 58px,rgba(139,149,145,0) 112px);
  mask-image:radial-gradient(circle at 50% 42%,#000 0%,#000 55%,transparent 88%);
  -webkit-mask-image:radial-gradient(circle at 50% 42%,#000 0%,#000 55%,transparent 88%);
}
#ces2027 .wrap{position:relative;z-index:1}
#ces2027 .lede{color:#C8CFD4}
#ces2027 .sub{font-family:var(--body);font-weight:500;font-size:13px;letter-spacing:.08em;text-transform:uppercase;color:#8C979E;margin-top:16px}
#ces2027 .actions{display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin-top:28px}
#ces2027 .btn{background:var(--paper);color:var(--ink);border-color:var(--paper)}
#ces2027 .btn:hover{background:var(--amber);border-color:var(--amber);color:var(--ink)}
#ces2027 .btn--ghost{background:transparent;color:var(--paper);border-color:var(--paper)}
#ces2027 .btn--ghost:hover{background:var(--paper);color:var(--ink)}

/* ── vision gauge (company/index.html hero, "section 0" — moved here from "Who we are" by
   request, since that's the "1번섹션") — a 3-stage roadmap summary, not a real percentage-
   accurate progress bar. Deliberately not built from absolute-positioned dots at exact 0/50/100%
   (fragile to get pixel-perfect without visual verification); instead each stage is an equal-width
   flex column with its own top-border segment, so the "filled" look comes from coloring the
   current + earlier segments' borders, not from measuring pixels.
   `.vision-here` ("we are here") is a speech-bubble callout (not a plain floating label+arrow,
   which read as too quiet) — solid muted/desaturated red (`#B33F3A`) for impact without going
   saturated/neon, bordered + drop-shadowed like a real popover, with a CSS border-triangle tail
   pointing down at the current stage. Still bobs gently via `@keyframes vision-bob`
   (`prefers-reduced-motion` turns it off, same as the rest of the site's motion). */
.vision-gauge{display:flex;margin-top:96px}
.vision-stage{flex:1;position:relative;padding:20px 24px 0 0}
.vision-stage::before{content:'';position:absolute;top:-2px;left:0;right:24px;height:2px;background:var(--hair)}
.vision-stage:last-child{padding-right:0}
.vision-stage:last-child::before{right:0}
.vision-stage--current::before{background:var(--ultra)}
.vision-num{font-family:var(--display);font-weight:700;font-size:clamp(22px,2.4vw,28px);color:var(--ink)}
.vision-stage--current .vision-num{color:var(--ultra)}
.vision-text{margin:6px 0 0;max-width:22em;font-size:14px;line-height:1.5;color:var(--graphite)}
.vision-here{
  position:absolute;bottom:calc(100% + 26px);left:80%;transform:translateX(-50%);
  display:inline-flex;padding:8px 14px;border-radius:10px;white-space:nowrap;
  background:#B33F3A;border:1px solid #8A2F2A;box-shadow:0 8px 16px rgba(20,8,7,.22);
  animation:vision-bob 2.4s var(--ease-in-out) infinite;
}
.vision-here::before,.vision-here::after{content:'';position:absolute;top:100%;left:16px;border:8px solid transparent}
.vision-here::before{border-top-color:#8A2F2A}
.vision-here::after{margin-top:-1px;left:17px;border:7px solid transparent;border-top-color:#B33F3A}
.vision-here-label{font-family:var(--body);font-weight:700;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#fff}
@keyframes vision-bob{0%,100%{transform:translateX(-50%) translateY(0)}50%{transform:translateX(-50%) translateY(6px)}}
@media(prefers-reduced-motion:reduce){.vision-here{animation:none}}
@media(max-width:720px){
  .vision-gauge{flex-direction:column;gap:32px}
  .vision-stage{padding:0 0 0 22px}
  .vision-stage::before{top:0;left:-2px;right:auto;bottom:0;width:2px;height:auto}
  .vision-here{position:static;display:inline-flex;margin-bottom:10px;animation:none}
  .vision-here::before,.vision-here::after{display:none}
}

/* ── team rows (company/index.html) — no photos yet, no illustrated avatars either
   (dropped the layered-silhouette bust icons, then a letter-in-a-circle gender mark, then a
   plain dot — now a thick vertical bar, wine/steel-navy, leading each row). This is a deliberate,
   scoped exception to the site's monotone-palette rule (see design tokens above) — the two
   gender colors are literal (not derived from the token set), but kept dark/muted rather than
   vivid pink/blue (see below).
   Layout: `.team-grid` is a direct grid item of the section's `.wrap.cols` (not nested inside
   the second column div) with `grid-column:1/-1`, so it spans the full width under both the h2
   and lede columns instead of being squeezed into just the right (1.25fr) column. Each `.team-row`
   is `display:flex;align-items:stretch` with the bar as one flex child and `.team-content` (name,
   role badge, specialty — all wrapped together) as the other, so the bar's `align-self:stretch`
   makes it span the *entire* row's rendered height, including whatever height `.team-spec` ends up
   at once it wraps — **not** a fixed pixel height on the bar itself. (An earlier version gave the
   bar a fixed height centered via `align-self:center`, which floated ambiguously mid-row instead
   of visually anchoring the whole block — "지금 중간에 애매하게 걸쳤잖아" — fixed by making the bar
   a full-height sibling of the content instead of an inline element within it.) Inside
   `.team-content`, name/badge/specialty flow as one wrapping flex line (not a left/right grid
   split — tried that, the specialty read disconnected from the name — and not name-above/spec-
   below stacked either). The gap from the badge to the specialty text is doubled relative to the
   other gaps (`.team-spec` adds its own extra `margin-left` on top of the row's normal `gap`) —
   asked to space it out "뱃지 두 배만큼" so the specialty doesn't read as crammed against the
   badge. No horizontal divider rules between rows — tried `.team-grid`/`.team-row` top/bottom
   borders and they read as "촌스럽게" (tacky/dated), removed; rows are separated by padding alone.
   `.team-role` is a small pill badge, bordered + text colored to match that person's gender bar
   (not a flat gray/monotone badge) — asked to carry the color into the badge too, not just the bar. */
.team-grid{grid-column:1/-1;margin-top:36px}
.team-row{display:flex;align-items:stretch;gap:18px;padding:20px 0}
.team-gender{width:12px;border-radius:1px;flex:none}
.team-gender--f{background:#7A3E52}
.team-gender--m{background:#2F4A63}
.team-content{display:flex;flex-wrap:wrap;align-items:baseline;gap:16px;flex:1}
.team-name{font-family:var(--display);font-weight:600;font-size:clamp(22px,2.4vw,28px);color:var(--ink);margin:0}
.team-role{display:inline-block;font-family:var(--body);font-weight:600;font-size:11px;letter-spacing:.06em;text-transform:uppercase;border:1px solid currentColor;border-radius:999px;padding:3px 12px}
.team-role--f{color:#7A3E52}
.team-role--m{color:#2F4A63}
.team-spec{flex:1 1 280px;margin-left:16px;font-size:16px;line-height:1.6;color:var(--graphite)}

/* ── roadmap log — sized up a step from the site's small default text ("조금만 더 키워줘도
   되겠고") so the timeline reads with a bit more weight, not just a dense reference list.
   No horizontal divider lines between rows (same lesson as the team rows above — full-width
   borders read as "촌스럽게"/tacky) — instead each row gets its own `.log-rail`, a thin vertical
   bar stretching the row's full height, reusing the `.team-gender` bar idiom for visual
   consistency rather than inventing a new timeline-dot treatment. `.log-badge`, on the CES 2027
   entry ("CES2027은 뱃지로"), started as the same bordered-pill shape as `.team-role` — since made
   taller, inverted to a filled dark pill (asked for "색 반전 (검은바탕)"), and turned into a real
   link to `ces2027/`. A second, unlinked, white `.log-badge--tbd` pill sits right after it in the
   same sentence. */
.log{margin-top:0}
.log-row{display:flex;align-items:stretch;gap:20px;padding:18px 0}
.log-rail{width:4px;border-radius:1px;background:var(--hair);flex:none}
.log-row .when{font-family:var(--body);font-weight:600;font-size:14px;color:var(--ultra);flex:none;width:88px;padding-top:2px}
.log-row .what{font-size:17px;line-height:1.4;flex:1}
.log-badge{display:inline-block;vertical-align:middle;position:relative;top:-1px;font-family:var(--body);font-weight:600;font-size:11px;letter-spacing:.06em;text-transform:uppercase;text-decoration:none;border-radius:999px;padding:6px 12px;background:var(--ink);color:#fff;border:1px solid var(--ink);transition:background var(--dur-fast) var(--ease-out)}
.log-badge:hover{background:var(--ultra);border-color:var(--ultra)}
.log-badge--tbd{background:#fff;color:var(--ink);border-color:var(--ink);cursor:default}
.log-badge--tbd:hover{background:#fff;border-color:var(--ink)}
.log-photo{display:block;width:100%;max-width:320px;height:auto;margin-top:28px}
@media(max-width:640px){.log-row{gap:14px}.log-row .when{width:72px}}

/* ── form ── */
.form{max-width:480px;margin-top:0;font-family:var(--display)}
.form label{display:block;font-family:var(--body);font-weight:600;font-size:11px;letter-spacing:.05em;text-transform:uppercase;color:var(--graphite);margin:18px 0 7px}
/* this form sits on .contact's dark background — the generic gray/small label style above
   (tuned for light backgrounds) read as barely visible there, asked to be bigger + white.
   Also switched to the display font (Sofia Sans, same as the wordmark) instead of Inter,
   asked for specifically ("form 내부의 글씨체도 우리 로고에 쓰는 글씨체로") ── */
.contact .form label{color:var(--paper);font-size:15px;letter-spacing:0;text-transform:none;font-family:var(--display)}
.form-req{color:#D6493D;margin-left:4px;font-family:var(--body)}
.form-optional{font-weight:400;text-transform:none;color:#8C979E;font-family:var(--body);font-size:12.5px}
.form input,.form textarea{
  width:100%;background:var(--card);border:1px solid var(--hair);
  color:var(--ink);font:inherit;font-size:16px;padding:11px 13px;font-family:var(--display);
}
.form textarea{min-height:120px;resize:vertical}
.form button{margin-top:22px;cursor:pointer;font-family:var(--display)}
.form button:disabled{opacity:.6;cursor:default}
/* honeypot: hidden from sighted users and keyboard tab order, but present in the DOM so bots
   that fill every field (rather than reading CSS) give themselves away */
.form-hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.form-status{margin:14px 0 0;font-size:13.5px;min-height:1.4em}
.form-status--ok{color:#6FAF92}
.form-status--err{color:#D6493D}

/* ── contact section on dark ── */
.contact{background:var(--ink);color:var(--paper)}
.contact .lede{color:#C8CFD4}
.contact .btn{border-color:var(--paper)}
.contact a.mail{font-family:var(--body);font-weight:500;font-size:14px;color:var(--paper)}

/* ── footer: thick, three columns — brand, sitemap, direct contact ── */
footer{
  background:var(--ink);color:#8C979E;padding:clamp(48px,6vw,72px) var(--pad) 24px;
  border-top:1px solid #262E34;font-family:var(--body);font-size:13px;line-height:1.7;
}
.footer-grid{display:grid;grid-template-columns:1.4fr 1fr;gap:clamp(28px,4vw,56px);padding-bottom:clamp(36px,5vw,56px)}
.footer-brand .mark{color:#fff;font-size:24px;padding-top:0}
.footer-brand p{margin:14px 0 0;color:#8C979E;font-size:13.5px;line-height:1.6;max-width:30ch}
.footer-h{font-family:var(--body);font-weight:600;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#5C6670;margin:0 0 14px}
.footer-nav{display:flex;flex-direction:column;gap:10px}
.footer-nav a,.footer-mail{color:#C8CFD4;text-decoration:none;font-size:14px;transition:color var(--dur-fast) var(--ease-out)}
.footer-nav a:hover,.footer-mail:hover{color:#fff}
.footer-mail{display:block}
.footer-note{margin:12px 0 0;font-size:12.5px;color:#5C6670}
.footer-bottom{
  display:flex;flex-wrap:wrap;gap:8px 32px;justify-content:space-between;
  padding-top:22px;border-top:1px solid #21262B;font-family:var(--mono);font-size:11px;color:#5C6670;
}
@media(max-width:720px){.footer-grid{grid-template-columns:1fr;gap:32px}}

:focus-visible{outline:2px solid var(--ultra);outline-offset:3px}

@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  body{animation:none}
  .ripple{animation:none;stroke-opacity:.4}
  .ev{animation:none;opacity:1}
  .terminal-line{animation:none;opacity:1;transform:none}
}
