/* =============================================================================
 * SOOM — AMBIENT FUTURISM
 * -----------------------------------------------------------------------------
 * 01 base            06 sensory field
 * 02 primitives      07 portals
 * 03 typography      08 objects (tea)
 * 04 chrome          09 calibration / manifesto / traces / footer
 * 05 hero            10 focus reveals
 *                    11 responsive · reduced motion · no-js
 *
 * Rules this file keeps:
 *   depth comes from luminance and blur, never from a shadow under a card
 *   nothing is a rounded rectangle unless it is a control
 *   colour is light, not fill — accents appear only where something is active
 *   the environment is one continuous field; sections do not own backgrounds
 * ========================================================================== */

/* =============================================================================
 * 01 · Base
 * ========================================================================== */

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + var(--s5));
  transition:
    --ink var(--t-state) var(--ease-soft),
    --ink-2 var(--t-state) var(--ease-soft),
    --ink-3 var(--t-state) var(--ease-soft),
    --accent var(--t-state) var(--ease-soft),
    --line var(--t-state) var(--ease-soft),
    --line-2 var(--t-state) var(--ease-soft);
}

body {
  margin: 0;
  background: var(--carbon);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--t-body);
  font-weight: var(--wt-regular);
  line-height: var(--lh-body);
  letter-spacing: var(--track-ko);
  font-variant-numeric: proportional-nums;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: clip;
}

img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
h1, h2, h3, h4, p, figure, dl, dd { margin: 0; }
ol, ul { margin: 0; padding: 0; }
li { list-style: none; }

::selection { background: var(--accent); color: var(--carbon); }

:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 6px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

.skip {
  position: absolute; top: var(--s3); left: var(--s3); z-index: 200;
  padding: var(--s3) var(--s5);
  background: var(--mineral); color: var(--carbon);
  font-size: var(--t-small);
  transform: translateY(-300%);
  transition: transform var(--t-ui) var(--ease);
}
.skip:focus { transform: none; }

/* ---- The room ---------------------------------------------------------- */

.field {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  display: block;
  pointer-events: none;
}

/* Standing in for the shader when there is no WebGL, no GPU worth using, or a
   reader who asked for less motion. Same palette, same light, no frames. */
.field-fallback {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(90vw 70vh at 62% 22%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 68%),
    radial-gradient(70vw 60vh at 12% 78%, color-mix(in srgb, var(--slate-2) 90%, transparent), transparent 72%),
    linear-gradient(180deg, var(--graphite), var(--carbon) 55%, var(--carbon));
  transition: background var(--t-state) var(--ease-soft);
}
.js .field-fallback { display: none; }
.js.no-gl .field-fallback { display: block; }
.js.no-gl .field { display: none; }

/* Type needs ground. Not a card behind it — a single depth of air between the
   volume and the interface, densest at the edges where the fog is brightest.
   Without this the environment competes with the words and wins. */
.veil {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--carbon) 86%, transparent),
      color-mix(in srgb, var(--carbon) 58%, transparent) 24%,
      color-mix(in srgb, var(--carbon) 58%, transparent) 74%,
      color-mix(in srgb, var(--carbon) 88%, transparent));
}

main, .bar, .foot, .menu { position: relative; z-index: 2; }

/* =============================================================================
 * 02 · Primitives
 * ========================================================================== */

.wrap {
  width: min(100% - (var(--gutter) * 2), var(--maxw));
  margin-inline: auto;
}

/* iOS safe area. In portrait this is a no-op; the case it exists for is a phone
   held sideways, where the notch and the home indicator eat into the left and
   right of the viewport and the gutter alone is not enough to clear them. The
   page declares viewport-fit=cover, so it has to handle those insets itself. */
@supports (padding: max(0px)) {
  .wrap, .bar__in {
    padding-left: max(0px, env(safe-area-inset-left));
    padding-right: max(0px, env(safe-area-inset-right));
  }
  .foot { padding-bottom: max(var(--s6), env(safe-area-inset-bottom)); }
  .menu__in { padding-bottom: max(var(--s8), env(safe-area-inset-bottom)); }
  .breath { padding-bottom: env(safe-area-inset-bottom); }
}

section { padding-block: var(--s-block); position: relative; }

/* A layer sits at a depth in the field rather than on a surface. */
.layer { position: relative; }

.sec-head { max-width: 62ch; margin-bottom: var(--s8); }
.sec-head--row {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--s5); max-width: none;
}

/* =============================================================================
 * 03 · Typography
 * ========================================================================== */

/* Two classes carry the whole rule about monospace, and everything else in the
   file uses one of them rather than reaching for --font-mono directly.

   `.sys` and `.meta` are for what the interface says about *itself*: a state
   name, a scene index, 95°C, 3 min, 150–200 ml. Words a person wrote — a
   product name, an aroma note, a nav item, a button — are never set in this,
   because monospace applied to brand copy is what turns a sensory brand into a
   dashboard. The previous system had 24 monospace declarations; four of them
   were holding Korean, which Plex Mono has no glyphs for, so labels like
   '물 온도' and '바디' were silently rendering in whatever the OS chose. */
.sys, .meta {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: var(--wt-regular);
  letter-spacing: var(--track-mono);
  font-variant-numeric: tabular-nums;
  text-transform: uppercase;
  color: var(--ink-3);
}
.sys {
  display: flex;
  align-items: center;
  gap: var(--s3);
}
.sys__key { color: var(--accent); }
/* The one piece of a .sys line that a person wrote rather than the system
   reported. It keeps the row's size and colour but takes the brand sans, its
   own case, and normal tracking — which is what lets 숨 render at all. */
.sys__text {
  font-family: var(--font);
  font-size: var(--t-small);
  font-weight: var(--wt-regular);
  letter-spacing: var(--track-ko);
  text-transform: none;
  word-break: keep-all;
}
.sys__sep {
  width: clamp(16px, 4vw, 48px); height: 1px;
  background: currentColor; opacity: 0.4; flex: none;
}

/* A label written by the brand rather than reported by the system. Latin gets
   a little tracking so it reads as a caption; Korean gets none, ever. */
.label {
  font-size: var(--t-small);
  font-weight: var(--wt-medium);
  letter-spacing: var(--track-label);
  color: var(--ink-3);
}
.label:lang(ko), .label--ko { letter-spacing: var(--track-ko); }

/* ---- Display -----------------------------------------------------------
   400 across the board, and the size does the work. The old scale ran on 200
   and 300, which on a dark ground at mobile DPR thins the Hangul stems until
   the page reads as a wireframe of itself rather than as a shipping brand. */
.d1 {
  font-size: var(--t-d1);
  font-weight: var(--wt-regular);
  line-height: var(--lh-display);
  letter-spacing: var(--track-display);
  word-break: keep-all;
}
.d2 {
  font-size: var(--t-d2); font-weight: var(--wt-regular);
  line-height: var(--lh-display); letter-spacing: var(--track-display);
  word-break: keep-all;
}
.d3 {
  font-size: var(--t-d3); font-weight: var(--wt-medium);
  line-height: var(--lh-title); letter-spacing: -0.01em;
  word-break: keep-all;
}

/* English display lines get Instrument Sans and slightly negative tracking;
   Korean set beside them keeps Pretendard and zero tracking, so the two scripts
   sit at the same optical weight instead of one shouting over the other. */
.en {
  font-family: var(--font-en);
  font-weight: var(--wt-book);
  letter-spacing: var(--track-display);
}
.ko { letter-spacing: var(--track-ko); word-break: keep-all; }

.lead {
  margin-top: var(--s5);
  font-size: var(--t-lead);
  font-weight: var(--wt-regular);
  line-height: var(--lh-body);
  letter-spacing: var(--track-ko);
  color: var(--ink-2);
  word-break: keep-all;
  max-width: var(--measure-wide);
}
.note {
  font-size: var(--t-small);
  font-weight: var(--wt-regular);
  line-height: var(--lh-body);
  color: var(--ink-3);
  word-break: keep-all;
  max-width: var(--measure);
}
.dim { color: var(--ink-3); }

/* One sentence set as an instrument reading rather than a pull quote. */
.statement {
  margin-top: var(--s8);
  font-size: var(--t-d2);
  font-weight: var(--wt-regular);
  line-height: var(--lh-display);
  letter-spacing: var(--track-display);
  color: var(--ink);
  word-break: keep-all;
}

.link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--t-small);
  font-weight: var(--wt-medium);
  color: var(--ink-2);
  transition: color var(--t-ui) var(--ease);
}
.link::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: -3px;
  height: 1px; background: currentColor; opacity: 0.28;
  transition: opacity var(--t-ui) var(--ease);
}
.link:hover, .link:focus-visible { color: var(--ink); }
.link:hover::after, .link:focus-visible::after { opacity: 0.7; }
/* Hit area without moving the type. */
.link::before, .tool::before { content: ""; position: absolute; inset: -14px -10px; }
.tool { position: relative; }

/* The one emphatic control on the page. A field of light, not a filled pill. */
.cta {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s4) var(--s6);
  /* The one emphatic control, so it is the one place the sans goes to 500 and
     stays there. It reads as a button in Korean and in English alike. */
  font-size: var(--t-small);
  font-weight: var(--wt-medium);
  letter-spacing: var(--track-ko);
  color: var(--ink);
  border: 1px solid var(--line);
  overflow: hidden;
  transition: color var(--t-ui) var(--ease), border-color var(--t-ui) var(--ease);
}
.cta::before {
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(120% 160% at 12% 50%,
    color-mix(in srgb, var(--accent) 34%, transparent), transparent 70%);
  opacity: 0;
  transition: opacity 520ms var(--ease);
}
.cta:hover, .cta:focus-visible { border-color: color-mix(in srgb, var(--accent) 60%, transparent); }
.cta:hover::before, .cta:focus-visible::before { opacity: 1; }
.cta > * { position: relative; }
.cta__mark {
  width: 22px; height: 1px; background: currentColor;
  transition: width 520ms var(--ease);
}
.cta:hover .cta__mark { width: 34px; }

/* =============================================================================
 * 04 · Chrome
 * ========================================================================== */

.bar {
  position: fixed; top: 0; left: 0; right: 0;
  z-index: 60; height: var(--nav-h);
  color: var(--ink);
  transition: background var(--t-ui) var(--ease);
}
.bar.is-lifted {
  background: color-mix(in srgb, var(--carbon) 66%, transparent);
  backdrop-filter: blur(18px) saturate(1.15);
  -webkit-backdrop-filter: blur(18px) saturate(1.15);
}
.bar__in {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4); height: 100%;
  width: min(100% - (var(--gutter) * 2), var(--maxw));
  margin-inline: auto;
}

/* The wordmark is a system indicator, not a logo: it acknowledges that the
   environment is live. */
.mark {
  position: relative;
  display: inline-flex; align-items: center; gap: var(--s3);
  /* The wordmark left monospace. Set in the brand sans at 600 with wide
     tracking it still reads as a live indicator, but it stops reading as a
     string a terminal printed — which is the difference between a concept
     site and a company. */
  font-family: var(--font-en);
  font-size: 0.8125rem;
  font-weight: var(--wt-semi);
  letter-spacing: var(--track-mark);
  padding-right: var(--track-mark);
}
.mark::before { content: ""; position: absolute; inset: -14px -10px; }
.mark__dot {
  width: 5px; height: 5px; flex: none;
  background: var(--accent);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent);
  animation: pulse 5.5s var(--ease-soft) infinite;
}
@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 50%, transparent); opacity: 0.75; }
  50% { box-shadow: 0 0 0 5px transparent; opacity: 1; }
}

.bar__state {
  display: none;
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--track-mono);
  text-transform: uppercase;
  color: var(--ink-3);
}
@media (min-width: 940px) { .bar__state { display: block; } }

.bar__tools { display: flex; align-items: center; gap: clamp(var(--s4), 2vw, var(--s6)); }
.tool {
  display: inline-flex; align-items: center; gap: var(--s2);
  /* Menu, Sound, Shop are things you press, not readings. Sans, 500, and a
     size a thumb can find — 13px in mono with 0.16em tracking was neither. */
  font-size: var(--t-small);
  font-weight: var(--wt-medium);
  letter-spacing: var(--track-label);
  color: var(--ink-2);
  transition: color var(--t-ui) var(--ease);
}
.tool:hover { color: var(--ink); }
.tool__bars { display: inline-flex; align-items: flex-end; gap: 2px; height: 10px; }
.tool__bars i { display: block; width: 1px; height: 3px; background: currentColor; transition: height var(--t-ui) var(--ease); }
.tool[aria-pressed="true"] { color: var(--accent); }
.tool[aria-pressed="true"] .tool__bars i { animation: bar 2400ms var(--ease-soft) infinite; }
.tool[aria-pressed="true"] .tool__bars i:nth-child(2) { animation-delay: 300ms; }
.tool[aria-pressed="true"] .tool__bars i:nth-child(3) { animation-delay: 640ms; }
@keyframes bar { 0%, 100% { height: 2px; } 50% { height: 10px; } }

.bar__progress {
  position: absolute; left: 0; bottom: 0; height: 1px; width: 100%;
  background: var(--accent);
  transform: scaleX(var(--p, 0)); transform-origin: left;
  opacity: 0.6;
}

.noscript-nav { display: flex; flex-wrap: wrap; gap: var(--s5); padding-top: 5rem; }

/* ---- Menu: the whole system at once ------------------------------------ */
.menu {
  position: fixed; inset: 0; z-index: 55;
  display: grid; align-content: center;
  background: color-mix(in srgb, var(--carbon) 88%, transparent);
  backdrop-filter: blur(28px);
  -webkit-backdrop-filter: blur(28px);
  visibility: hidden; opacity: 0;
  transition: visibility 0s linear 320ms, opacity 320ms var(--ease);
}
.menu.is-open { visibility: visible; opacity: 1; transition-delay: 0s; }
.menu__in {
  display: grid;
  grid-template-columns: 1fr;
  max-width: 640px;
  gap: var(--s8);
  padding-top: var(--nav-h);
}
.menu__index ul { margin-top: var(--s5); }
.menu__link {
  display: flex; align-items: baseline; gap: var(--s4);
  padding-block: var(--s2);
  font-size: clamp(1.375rem, 1rem + 2vw, 2.4rem);
  font-weight: var(--wt-regular);
  line-height: var(--lh-title);
  letter-spacing: var(--track-display);
  word-break: keep-all;
  color: var(--ink);
  transition: color var(--t-ui) var(--ease);
}
/* The English name beside the Korean one — a gloss, not a system reading,
   so it takes the brand's English face rather than the monospace. */
.menu__link span {
  font-family: var(--font-en); font-size: var(--t-small);
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-3);
}
.menu__link:hover, .menu__link[aria-current="true"] { color: var(--accent); }

.menu__channels, .menu__scenes { margin-top: var(--s4); }
.menu__channels li, .menu__scenes li {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s4);
  padding-block: var(--s3);
  border-top: 1px solid var(--line-2);
  font-size: var(--t-small); color: var(--ink-2);
}
.menu__channels li b, .menu__scenes li b { font-weight: var(--wt-medium); color: var(--ink); }
.menu__channels li em, .menu__scenes li em {
  font-style: normal; font-family: var(--font-mono);
  font-size: var(--t-mono); letter-spacing: var(--track-mono);
  font-variant-numeric: tabular-nums;
  text-transform: uppercase; color: var(--ink-3); white-space: nowrap;
}
.menu__foot {
  grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: var(--s6);
  padding-top: var(--s6); border-top: 1px solid var(--line-2);
}

/* =============================================================================
 * 05 · Hero
 * ========================================================================== */

.hero {
  position: relative;
  /* vh first for anything that has never heard of the small-viewport unit, then
     svh: the hero must fit the screen with the mobile browser bars *shown*, or
     the CTA sits under the address bar on first paint. Deliberately not dvh —
     dvh changes as the bars retract, which would resize the hero mid-scroll. */
  min-height: 100vh;
  min-height: 100svh;
  display: flex; align-items: center;
  padding-block: calc(var(--nav-h) + var(--s8)) var(--s9);
}
/* `.wrap` owns the width and centres itself. Do not add `flex: 1` here: that
   sets flex-basis to 0 and the gutters go with it. */

/* The statement sits *in* the volume rather than against its left wall. The
   block is centred; the type inside stays left-aligned, so the composition is
   balanced without the headline turning into a centred poster. */
.hero__col {
  max-width: 62rem;
  margin-inline: auto;
}
.hero__sys { margin-bottom: var(--s7); }

.hero__title {
  /* English display: Instrument Sans at 450. Below 400 the counters close up
     at this size on a dark ground and the line reads soft rather than calm. */
  font-family: var(--font-en);
  font-size: var(--t-hero);
  font-weight: var(--wt-book);
  line-height: var(--lh-hero);
  letter-spacing: var(--track-hero);
  max-width: 15ch;
}
/* The Korean line under it: Pretendard 400, no tracking, and a size that holds
   its own beside the English rather than sitting under it as a subtitle. */
.hero__ko {
  margin-top: var(--s5);
  font-size: clamp(1.0625rem, 0.95rem + 0.75vw, 1.4rem);
  font-weight: var(--wt-regular);
  line-height: 1.6;
  letter-spacing: var(--track-ko);
  color: var(--ink-2);
  word-break: keep-all;
  max-width: var(--measure);
}

/* The one line under the Korean slogan that says what SOOM actually does.
   Dimmer than `.hero__ko` — the slogan is the voice, this is the gloss. */
.hero__desc {
  margin-top: var(--s5);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  letter-spacing: var(--track-ko);
  color: var(--ink-3);
  word-break: keep-all;
  max-width: var(--measure);
}

.hero__actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s6);
  margin-top: var(--s8);
}

/* The breath mark: the site's pulse, four seconds in and six out. */
.breath {
  position: absolute; left: var(--gutter); bottom: var(--s6);
  display: flex; align-items: center; gap: var(--s3);
  font-family: var(--font-mono); font-size: var(--t-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--track-mono); text-transform: uppercase; color: var(--ink-3);
}
.breath i { display: block; width: 68px; height: 1px; background: var(--line); overflow: hidden; }
.breath i::after {
  content: ""; display: block; height: 100%; background: var(--accent);
  transform-origin: left; animation: breath 10s var(--ease-soft) infinite;
}
@keyframes breath {
  0% { transform: scaleX(0); transform-origin: left; }
  40% { transform: scaleX(1); transform-origin: left; }
  41% { transform: scaleX(1); transform-origin: right; }
  100% { transform: scaleX(0); transform-origin: right; }
}

.ph__grid {
  display: grid; grid-template-columns: 1.2fr 1fr;
  gap: var(--s7); margin-top: var(--s7);
}

/* Three principles, side by side rather than stacked — a belief read as a
   set, not a list scrolled through one at a time. */
.principles {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--s7); margin-top: var(--s8);
}
.principle h3 { margin-bottom: var(--s3); color: var(--ink); }
.principle p { max-width: none; }

/* =============================================================================
 * 06 · Sensory field
 * ========================================================================== */

.sensory {
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  gap: var(--s8);
  align-items: center;
}

.sensory__field {
  position: relative;
  aspect-ratio: 5 / 4;
  min-height: 340px;
}
.sensory__web { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.sensory__web line {
  stroke: var(--line-2);
  stroke-width: 1;
  transition: stroke var(--t-state) var(--ease-soft), stroke-width var(--t-state) var(--ease-soft);
}
.sensory__web line.is-live { stroke: color-mix(in srgb, var(--accent) 70%, transparent); }

/* A channel is a point in a field, not a row in a list. */
.ch {
  position: absolute;
  left: calc(var(--x) * 1%);
  top: calc(var(--y) * 1%);
  transform: translate(-50%, -50%);
  display: grid;
  justify-items: center;
  gap: var(--s2);
  padding: var(--s3);
  text-align: center;
  transition: transform 620ms var(--ease);
}
.ch__dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--ink-3);
  transition: background var(--t-ui) var(--ease), box-shadow 620ms var(--ease), transform 620ms var(--ease);
}
.ch__name {
  font-family: var(--font-en);
  font-size: clamp(1rem, 0.85rem + 0.6vw, 1.3rem);
  font-weight: var(--wt-book);
  letter-spacing: var(--track-display);
  color: var(--ink-2);
  transition: color var(--t-ui) var(--ease);
}
/* 맛 · 향 · 소리 · 공간 · 몸. Korean, and it was set in a Latin-only
   monospace, so every one of these five was rendering in a system fallback. */
.ch__ko {
  font-size: var(--t-small);
  font-weight: var(--wt-regular);
  letter-spacing: var(--track-ko);
  color: var(--ink-3);
}
.ch:hover .ch__name { color: var(--ink); }
.ch[aria-pressed="true"] { transform: translate(-50%, -50%) scale(1.08); }
.ch[aria-pressed="true"] .ch__dot {
  background: var(--accent);
  box-shadow: 0 0 0 8px color-mix(in srgb, var(--accent) 12%, transparent),
              0 0 26px 4px color-mix(in srgb, var(--accent) 34%, transparent);
}
.ch[aria-pressed="true"] .ch__name { color: var(--ink); }
.ch[aria-pressed="true"] .ch__ko { color: var(--accent); }

.sensory__read { min-height: 260px; }
.read__no {
  font-family: var(--font-mono); font-size: var(--t-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--track-mono); text-transform: uppercase; color: var(--accent);
}
.read__name {
  margin-top: var(--s3);
  font-family: var(--font-en);
  font-size: var(--t-d2); font-weight: var(--wt-book);
  line-height: var(--lh-display); letter-spacing: var(--track-display);
}
/* The one sentence a language is introduced by — larger and brighter than
   the support line under it, so the panel reads as one statement first. */
.read__line {
  margin-top: var(--s4); font-size: var(--t-lead);
  line-height: var(--lh-body); letter-spacing: var(--track-ko);
  color: var(--ink); word-break: keep-all; max-width: var(--measure-wide);
}
.read__body {
  margin-top: var(--s3); font-size: var(--t-body);
  line-height: var(--lh-body); letter-spacing: var(--track-ko);
  color: var(--ink-2); word-break: keep-all; max-width: var(--measure);
}
.read__meta { display: flex; gap: var(--s5); margin-top: var(--s5); flex-wrap: wrap; }
.read__meta dt {
  font-size: var(--t-small); font-weight: var(--wt-medium);
  letter-spacing: var(--track-label); color: var(--ink-3);
}
.read__meta dd { margin-top: 4px; font-size: var(--t-small); color: var(--ink-2); }
.read__link { margin-top: var(--s6); }

/* =============================================================================
 * 07 · Portals
 * ========================================================================== */

/* Five previews, not five posters — this grid is deliberately shorter than
   it was when the wide card also carried a purchase path. A collection
   without a product yet still gets its own light, not a full screen. */
.portals {
  display: grid;
  grid-template-columns: 1.6fr repeat(4, 1fr);
  gap: var(--s3);
  min-height: min(42vh, 380px);
}

.portal {
  position: relative;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: var(--s5);
  overflow: hidden;
  text-align: left;
  border: 1px solid var(--line-2);
  transition: border-color 520ms var(--ease), transform 620ms var(--ease);
}
/* Every scene has its own light, including the ones with nothing to sell. */
.portal::before {
  content: "";
  position: absolute; inset: 0;
  background:
    radial-gradient(120% 90% at 30% 108%, var(--tint-b), transparent 62%),
    linear-gradient(180deg, transparent 20%, var(--tint-a) 130%);
  opacity: 0.42;
  transition: opacity 700ms var(--ease), transform 1400ms var(--ease);
  transform: scale(1.04);
}
.portal:hover::before, .portal:focus-visible::before { opacity: 0.78; transform: scale(1); }
.portal:hover, .portal:focus-visible { border-color: var(--line); }
.portal > * { position: relative; }

/* Chapter 01, 02 … — supplied by the system, so mono stays. */
.portal__no {
  font-family: var(--font-mono); font-size: var(--t-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--track-mono); text-transform: uppercase; color: var(--ink-3);
}
.portal__name {
  margin-top: var(--s3);
  font-family: var(--font-en);
  font-size: clamp(1.125rem, 0.9rem + 0.85vw, 1.75rem);
  font-weight: var(--wt-book); letter-spacing: var(--track-display);
  line-height: var(--lh-display);
}
.portal__ko {
  margin-top: var(--s2); font-size: var(--t-small);
  letter-spacing: var(--track-ko); color: var(--ink-3); word-break: keep-all;
}

/* Scene notes, read like the head of a film slate — never a HUD. */
.portal__notes { margin-top: var(--s5); display: grid; gap: 3px; }
/* dusk · wet asphalt · distant room tone. Someone wrote these — they are the
   head of a film slate, not a sensor log. Sans, lowercase, barely tracked. */
.portal__notes li {
  font-size: var(--t-small);
  font-weight: var(--wt-regular);
  letter-spacing: 0.012em; color: var(--ink-3);
  opacity: 0; transform: translateY(6px);
  transition: opacity 520ms var(--ease), transform 520ms var(--ease);
}
.portal:hover .portal__notes li, .portal:focus-visible .portal__notes li,
.portal--live .portal__notes li, .portal--picked .portal__notes li { opacity: 1; transform: none; }
.portal__notes li:nth-child(2) { transition-delay: 60ms; }
.portal__notes li:nth-child(3) { transition-delay: 120ms; }
.portal__notes li:nth-child(4) { transition-delay: 180ms; }

.portal__state {
  margin-top: var(--s5);
  display: inline-flex; align-items: center; gap: var(--s2);
  font-family: var(--font-mono); font-size: var(--t-mono);
  letter-spacing: var(--track-mono); text-transform: uppercase;
  color: var(--ink-3);
}
.portal--live .portal__state { color: var(--accent); }
.portal--live { border-color: color-mix(in srgb, var(--accent) 32%, transparent); }

/* A tapped scene. This is the only feedback a touch device gets, so it is not
   subtle: the border takes the accent and the scene's own light comes up. */
.portal--picked { border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.portal--picked::before { opacity: 0.8; transform: scale(1); }
.portal--picked .portal__no { color: var(--accent); }
.portal__state i {
  width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; flex: none;
}

/* =============================================================================
 * 08 · Objects — the tea, as material
 * ========================================================================== */

.readout {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--s5);
  padding-block: var(--s6);
  border-top: 1px solid var(--line-2);
  border-bottom: 1px solid var(--line-2);
}
/* 물 온도, 우리는 시간, 물의 양 — Korean, and previously set in a
   Latin-only monospace, which has no Hangul at all. Sans, 500, no tracking. */
.readout dt {
  font-size: var(--t-small); font-weight: var(--wt-medium);
  letter-spacing: var(--track-ko); color: var(--ink-3);
}
/* The value is the reading: 95°C, 3 min, 150–200 ml. This is exactly where
   monospace belongs, and tabular figures keep the three columns aligned. */
.readout dd {
  margin-top: var(--s2);
  font-family: var(--font-mono);
  font-size: clamp(1.375rem, 1rem + 1.5vw, 2.125rem);
  font-weight: var(--wt-regular);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.012em; line-height: 1.1;
}
.readout dd small {
  display: block; margin-top: var(--s3);
  font-family: var(--font); font-size: var(--t-small);
  font-weight: var(--wt-regular);
  letter-spacing: var(--track-ko); text-transform: none;
  color: var(--ink-3); word-break: keep-all;
}

.objects {
  display: grid;
  grid-template-columns: 1fr 1.05fr;
  gap: var(--s8);
  align-items: center;
  margin-top: var(--s9);
}

/* One object that becomes another. Not three cards in a row.

   The frame is 4:5 and so is every tea photograph in it, so `contain` and
   `cover` currently resolve to the same rectangle — but they stop being the
   same the moment someone adds a fourth shot at a different ratio, and `cover`
   fails silently by cutting the product while `contain` fails visibly by
   letting air in. On a page asking someone to buy the object in the picture,
   the visible failure is the correct one. Nothing here is ever cropped. */
.object__stage {
  position: relative;
  aspect-ratio: 4 / 5;
  /* The frame is what holds the space; without a floor the stage collapses to
     nothing on the first paint and the whole section shifts when the images
     arrive. */
  width: 100%;
}
.object__stage figure { position: absolute; inset: 0; }
.object__stage img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}
.object__stage .surface { position: absolute; inset: 0; width: 100%; height: 100%; }
.object__stage.has-gl figure { opacity: 0; }

.object__select {
  display: flex; gap: var(--s2); margin-bottom: var(--s6);
  border-bottom: 1px solid var(--line-2);
}
.object__select button {
  position: relative;
  /* Product tabs: 44px of height, sans, 500. They are the primary control in
     this section and were the smallest thing on the page. */
  padding: var(--s4) var(--s5);
  min-height: 44px;
  font-size: var(--t-small);
  font-weight: var(--wt-medium);
  letter-spacing: var(--track-label);
  color: var(--ink-3);
  transition: color var(--t-ui) var(--ease);
}
.object__select button::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px;
  height: 1px; background: var(--accent);
  transform: scaleX(0); transform-origin: left;
  transition: transform 520ms var(--ease);
}
.object__select button:hover { color: var(--ink-2); }
.object__select button[aria-selected="true"] { color: var(--accent); }
.object__select button[aria-selected="true"]::after { transform: scaleX(1); }

/* Edition number — system. */
.object__no {
  font-family: var(--font-mono); font-size: var(--t-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--track-mono); text-transform: uppercase; color: var(--accent);
}
/* The product name carries the purchase decision, so it is the one heading set
   at 500 rather than 400 — the extra weight reads as certainty, not as shout. */
.object__name {
  margin-top: var(--s3);
  font-size: var(--t-d2); font-weight: var(--wt-medium);
  letter-spacing: var(--track-display); line-height: var(--lh-display);
  word-break: keep-all;
}
.object__sub {
  margin-top: var(--s4); font-size: var(--t-lead);
  line-height: 1.6; letter-spacing: var(--track-ko);
  color: var(--ink); word-break: keep-all; max-width: var(--measure-wide);
}
/* Product description: body size, not caption size. This is where a visitor
   decides whether to trust the brand, and it was set at 13.5px. */
.object__desc {
  margin-top: var(--s4); font-size: var(--t-body);
  line-height: var(--lh-body); letter-spacing: var(--track-ko);
  color: var(--ink-2); max-width: var(--measure); word-break: keep-all;
}

/* Notes sit in the space around the object rather than in a chip row. */
.object__notes { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); margin-top: var(--s6); }
/* Aroma notes are the most sensory words on the page; they were the most
   mechanical thing on it. Sans, sentence case, tracked barely. */
.object__notes li {
  font-size: var(--t-small);
  font-weight: var(--wt-regular);
  letter-spacing: 0.012em; color: var(--ink-3);
}
.object__notes li::before { content: "· "; color: var(--line); }

.spec { margin-top: var(--s6); border-top: 1px solid var(--line-2); }
.spec > div {
  display: grid; grid-template-columns: 9rem 1fr; gap: var(--s4);
  padding-block: var(--s3); border-bottom: 1px solid var(--line-2);
}
/* 바디, 피니시, 카페인 — Korean spec labels, so: sans. */
.spec dt {
  font-size: var(--t-small); font-weight: var(--wt-medium);
  letter-spacing: var(--track-ko); color: var(--ink-3); word-break: keep-all;
}
.spec dd {
  font-size: var(--t-small); letter-spacing: var(--track-ko);
  color: var(--ink-2); word-break: keep-all;
}

.object__buy { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s5); margin-top: var(--s7); }
/* 네이버 스마트스토어 · 3g × 12 — Korean again. */
.object__price {
  font-size: var(--t-small); letter-spacing: var(--track-ko);
  color: var(--ink-3); word-break: keep-all;
}

/* =============================================================================
 * 09 · Calibration · manifesto · traces · footer
 * ========================================================================== */

.calib { display: flex; flex-wrap: wrap; gap: var(--s3); }
.calib button {
  position: relative;
  padding: var(--s5) var(--s6);
  border: 1px solid var(--line-2);
  overflow: hidden;
  text-align: left;
  transition: border-color 420ms var(--ease);
}
.calib button::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(100% 140% at 50% 130%,
    color-mix(in srgb, var(--accent) 40%, transparent), transparent 68%);
  opacity: 0; transition: opacity 520ms var(--ease);
}
.calib button > * { position: relative; }
.calib button:hover { border-color: var(--line); }
.calib button[aria-pressed="true"] { border-color: color-mix(in srgb, var(--accent) 50%, transparent); }
.calib button[aria-pressed="true"]::before { opacity: 1; }
.calib b {
  display: block; font-size: clamp(1.125rem, 0.95rem + 0.85vw, 1.5rem);
  font-weight: var(--wt-regular); letter-spacing: var(--track-ko);
  line-height: var(--lh-title); color: var(--ink-2); word-break: keep-all;
  transition: color 420ms var(--ease);
}
.calib em {
  display: block; margin-top: var(--s2); font-style: normal;
  font-family: var(--font-mono); font-size: var(--t-mono);
  letter-spacing: var(--track-mono); text-transform: uppercase; color: var(--ink-3);
}
.calib button[aria-pressed="true"] b { color: var(--ink); }
.calib button[aria-pressed="true"] em { color: var(--accent); }

.calib__out { margin-top: var(--s8); min-height: 120px; }
.calib__idle { font-size: var(--t-small); color: var(--ink-3); }
.calib__grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: var(--s7); }
.calib__line {
  font-size: var(--t-d3); font-weight: var(--wt-regular);
  line-height: var(--lh-title); letter-spacing: var(--track-ko);
  color: var(--ink); word-break: keep-all; max-width: var(--measure-wide);
}
.calib__pick dt {
  font-size: var(--t-small); font-weight: var(--wt-medium);
  letter-spacing: var(--track-label); color: var(--ink-3);
}
.calib__pick dd { margin-top: var(--s2); font-size: var(--t-lead); color: var(--ink); }
.calib__pick dd + dt { margin-top: var(--s5); }

.manifesto { text-align: center; max-width: var(--measure-wide); margin-inline: auto; }
/* The manifesto is the closest the brand comes to speaking in its own voice,
   so it gets the English face and the most air. Still 400 — a manifesto set in
   a hairline reads as a mood board caption. */
.mf {
  font-family: var(--font-en);
  font-size: clamp(1.25rem, 0.95rem + 1.4vw, 2rem);
  font-weight: var(--wt-regular); line-height: 1.5;
  letter-spacing: var(--track-display);
  word-break: keep-all;
}
.mf + .mf { margin-top: var(--s7); }
.mf--ko {
  font-family: var(--font);
  font-size: clamp(1.0625rem, 0.9rem + 0.85vw, 1.375rem);
  line-height: 1.7; letter-spacing: var(--track-ko); color: var(--ink-2);
}
/* The same mark that opens the page closes it, so it is set the same way. */
.manifesto__sign {
  margin-top: var(--s9); justify-content: center;
  font-family: var(--font-en); font-size: 0.8125rem;
  font-weight: var(--wt-semi); letter-spacing: var(--track-mark);
  padding-left: var(--track-mark);
}

.traces { display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--s2); margin-top: var(--s6); }
.traces a { position: relative; aspect-ratio: 1; overflow: hidden; }
.traces img {
  width: 100%; height: 100%; object-fit: cover;
  filter: grayscale(0.35) brightness(0.72) contrast(1.05);
  transition: filter 620ms var(--ease), transform 900ms var(--ease);
}
.traces a:hover img { filter: grayscale(0) brightness(0.95); transform: scale(1.04); }

.foot { padding-block: var(--s9) var(--s6); }
.foot__in { display: grid; grid-template-columns: 1.4fr 1fr; gap: var(--s8); }
.foot__cols { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s6); align-content: start; }
/* Each of these rows carries one link whose hit area extends 14px past the
   text. At the old 8px of padding those areas met in the middle, so a thumb
   aimed at one row could only reach 40px of it before crossing into the next.
   12px separates them and gives every footer link its 44. */
.foot__cols li { padding-block: var(--s3); }
/* Carries the `.sys` class for its size and dim colour, but the copyright
   line and the disclaimer are sentences a person wrote — not a system
   reading — so the brand sans overrides the monospace `.sys` inherits. */
.foot__base {
  grid-column: 1 / -1; display: flex; flex-wrap: wrap; justify-content: space-between;
  gap: var(--s4); margin-top: var(--s8); padding-top: var(--s5);
  border-top: 1px solid var(--line-2);
  font-family: var(--font);
  font-size: var(--t-small); text-transform: none; letter-spacing: var(--track-ko);
}
.foot__cols a, .foot__cols li { font-size: var(--t-small); }

/* =============================================================================
 * 10 · Focus reveals
 * -----------------------------------------------------------------------------
 * Type does not fade in. It comes into focus, the way a lens finds a subject:
 * out of the blur, forward through a little depth, settling. The resting state
 * is here; js/app.js performs it. `all-clear` drops all of it if the module
 * never boots.
 * ========================================================================== */

[data-focus] .fx { display: block; }
[data-focus] .fx > i { display: block; font-style: inherit; }

.js:not(.all-clear) [data-focus] .fx > i,
.js:not(.all-clear) [data-focus]:not(:has(.fx)) {
  opacity: 0;
  filter: blur(16px);
  transform: translateY(0.12em) scale(1.015);
}

/* The hero focuses on its own, in CSS, the instant the page paints. Everything
   below the fold can wait for the module; the first screen may not — waiting
   for a dynamic import to resolve before showing the headline costs seconds of
   LCP for a reveal nobody has scrolled to yet. */
@keyframes focus-in {
  from { opacity: 0; filter: blur(16px); transform: translateY(0.12em) scale(1.015); }
  to   { opacity: 1; filter: blur(0px);  transform: none; }
}
.js:not(.all-clear) .hero [data-focus] .fx > i,
.js:not(.all-clear) .hero [data-focus]:not(:has(.fx)) {
  animation: focus-in 1.1s var(--ease) both;
  animation-delay: var(--fd, 0ms);
}
.hero__title .fx:nth-child(2) > i { --fd: 90ms; }
.hero__ko      { --fd: 500ms; }
.hero__desc    { --fd: 680ms; }
.hero__actions { --fd: 880ms; }

/* =============================================================================
 * 11 · Responsive
 * ========================================================================== */

@media (max-width: 1080px) {
  .sensory { grid-template-columns: 1fr; gap: var(--s7); }
  .sensory__field { aspect-ratio: 3 / 2; min-height: 300px; }
  .objects { grid-template-columns: 1fr; gap: var(--s7); }
  .portals { grid-template-columns: 1fr 1fr; min-height: 0; }
  .portal { min-height: 200px; }
  .menu__in { gap: var(--s7); }
  .foot__in { grid-template-columns: 1fr; }
  .ph__grid { grid-template-columns: 1fr; gap: var(--s5); }
  .principles { grid-template-columns: 1fr; gap: var(--s6); }
}

@media (max-width: 720px) {
  :root { --nav-h: 54px; }

  /* Not a centring flex row down here: the breath mark becomes a second flex
     item and squeezes the statement off its gutter. */
  .hero {
    display: block;
    min-height: auto;
    padding-block: calc(var(--nav-h) + var(--s8)) var(--s8);
  }
  .hero__title { max-width: none; }
  .breath {
    position: static;
    width: min(100% - (var(--gutter) * 2), var(--maxw));
    margin: var(--s8) auto 0;
  }
  .hero__actions { flex-direction: column; align-items: flex-start; gap: var(--s4); }
  .cta { width: 100%; justify-content: space-between; }

  /* On a small screen the constellation becomes a rail: same language, one
     thumb's reach, no hover required. */
  .sensory__field { aspect-ratio: auto; min-height: 0; display: grid; gap: var(--s2); }
  .sensory__web { display: none; }
  .ch {
    position: static; transform: none;
    grid-template-columns: auto 1fr auto;
    justify-items: start; align-items: center;
    gap: var(--s4);
    padding: var(--s4) var(--s3);
    border-bottom: 1px solid var(--line-2);
    text-align: left;
  }
  .ch[aria-pressed="true"] { transform: none; }
  .ch__name { font-size: 1.15rem; }
  .sensory__read { min-height: 0; }

  .portals { grid-template-columns: 1fr; }
  .portal { min-height: 160px; }
  .readout { grid-template-columns: 1fr; gap: var(--s5); }
  .readout div { border-bottom: 1px solid var(--line-2); padding-bottom: var(--s4); }
  .readout div:last-child { border-bottom: 0; padding-bottom: 0; }
  .spec > div { grid-template-columns: 7rem 1fr; gap: var(--s3); }
  .calib button { flex: 1 1 42%; }
  .calib__grid { grid-template-columns: 1fr; gap: var(--s5); }
  .traces { grid-template-columns: repeat(3, 1fr); }
  .foot__cols { grid-template-columns: 1fr; gap: var(--s5); }
  .sec-head { margin-bottom: var(--s7); }
  .menu { align-content: start; overflow-y: auto; }
  .menu__in { padding-block: calc(var(--nav-h) + var(--s6)) var(--s8); }
}

@media (max-width: 400px) {
  .calib button { flex: 1 1 100%; }
  .object__select { flex-wrap: wrap; }
}

/* No hover here, so nothing may depend on it. Notes are the content of a scene
   card, not a reward for pointing at it: on touch they are simply there. */
@media (hover: none) {
  .portal:hover::before { opacity: 0.42; transform: scale(1.04); }
  .portal--live::before { opacity: 0.7; }
  .portal--picked::before { opacity: 0.8; transform: scale(1); }
  .portal__notes li { opacity: 1; transform: none; transition: none; }
  .traces a:hover img { transform: none; }
  .ch:hover .ch__name { color: var(--ink-2); }
  .portal:hover, .portal:focus-visible { border-color: var(--line-2); }
  .portal--live { border-color: color-mix(in srgb, var(--accent) 32%, transparent); }
  .portal--picked { border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
}

/* =============================================================================
 * Reduced motion — the environment stays, the movement goes
 * ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; transition: none; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .mark__dot, .breath i::after, .tool__bars i { animation: none !important; }
  .breath i::after { transform: scaleX(0.55); }
  .js [data-focus] .fx > i,
  .js [data-focus]:not(:has(.fx)) {
    opacity: 1 !important; filter: none !important; transform: none !important;
  }
}

/* =============================================================================
 * No JavaScript — the environment is CSS, the content is markup
 * ========================================================================== */

.no-js .field { display: none; }
.no-js .bar__tools .tool--menu, .no-js #soundToggle { display: none; }
.no-js .bar { position: absolute; }
.no-js .breath { display: none; }
.no-js [data-focus] .fx > i, .no-js [data-focus] { opacity: 1; filter: none; transform: none; }
.no-js .hero { min-height: auto; }
