/* ==========================================================================
   Lan LLC — corporate LP
   Everything is measured in --cell. The field canvas (js/field.js) uses the
   same 13px module, so the dots and the layout sit on one grid.
   ========================================================================== */

:root {
  --cell: 13px;
  --gutter: clamp(22px, 3.4vw, 52px);
  --maxw: 1196px;

  --paper: #ffffff;
  --paper-soft: #ffffff;
  --ink: #171816;
  --muted: #6e7068;
  --blue: #2146e8;
  --coral: #ff5a3d;
  --lime: #c7e95a;
  --lavender: #b9a8ff;
  --aqua: #a9e7de;
  --line: rgba(23, 24, 22, 0.12); /* hairlines read heavier on pure white */

  --mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  --progress: 0;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

body {
  margin: 0;
  /* clip, not hidden: `overflow-x: hidden` forces overflow-y to compute to
     `auto`, turning body into a scroll container — which clips descenders and
     breaks position:sticky. `clip` contains the overflow without either. */
  overflow-x: clip;
  background: var(--paper);
  color: var(--ink);
  font-family: "Noto Sans JP", "Yu Gothic", sans-serif;
  font-size: 15px;
  line-height: 1.8;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
button, a { -webkit-tap-highlight-color: transparent; }
::selection { background: var(--blue); color: #fff; }
:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }

/* the shared measure: every section's inner block lines up on one column */
/* NOTE: .rail is deliberately absent — the app rail is full-bleed, and only its
   track's padding aligns the first card to this column. Constraining .rail here
   clips the last visible card against the container edge. */
.wrap,
.statement__body,
.work__intro,
.process__head,
.steps,
.about__grid,
.company__grid,
.contact__content,
section > .section-tag {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
}

.skip-link {
  position: fixed;
  z-index: 10000;
  top: 10px;
  left: 10px;
  padding: 10px 16px;
  background: var(--ink);
  color: #fff;
  transform: translateY(-160%);
  transition: transform 0.2s ease;
}
.skip-link:focus { transform: none; }

/* ---------- field ---------------------------------------------------- */

#field {
  position: fixed;
  z-index: 0;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* content floats above the field; sections stay transparent so the dots and
   the cursor trail read as one continuous layer under the whole page */
#main { position: relative; z-index: 2; }
main > section { position: relative; background: transparent; }

/* ---------- loader ---------------------------------------------------- */

/* The loader dismisses itself from CSS. If the module scripts fail to run the
   page must still be reachable — .is-ready only makes the exit earlier. */
.loader {
  position: fixed;
  z-index: 5000;
  inset: 0;
  display: grid;
  place-items: center;
  background: var(--paper-soft);
  transform-origin: top;
  animation: loaderOut .7s cubic-bezier(.77,0,.18,1) 1.15s forwards;
  transition: opacity .6s ease .15s, visibility .6s ease .15s, transform .8s cubic-bezier(.77,0,.18,1) .15s;
}
.is-ready .loader { opacity: 0; visibility: hidden; transform: scaleY(0); }
/* the same wordmark as .logo, just larger — keep the tracking identical so the
   loader and the header read as one mark */
.loader__mark {
  font: 700 clamp(62px, 9vw, 120px)/1 "Syne", "Helvetica Neue", Arial, sans-serif;
  letter-spacing: -0.08em;
  transform: translateX(-0.04em);
}
.loader__mark span { color: var(--coral); }
.loader__bar { position: absolute; width: 170px; height: 3px; margin-top: 150px; overflow: hidden; background: #e8e8e6; border-radius: 10px; }
.loader__bar i { display: block; width: 100%; height: 100%; background: linear-gradient(90deg, var(--coral), var(--blue), var(--lime)); animation: loaderLine .9s cubic-bezier(.7,0,.2,1) both; }

.scroll-line { position: fixed; z-index: 900; top: 0; left: 0; width: 100%; height: 3px; pointer-events: none; }
.scroll-line i { display: block; width: 100%; height: 100%; background: linear-gradient(90deg, var(--coral), var(--blue), var(--lime)); transform: scaleX(var(--progress)); transform-origin: left; }

/* ---------- header ---------------------------------------------------- */

.header {
  position: fixed;
  z-index: 800;
  top: 0;
  left: 0;
  width: 100%;
  min-height: calc(var(--cell) * 6);
  padding: 0 var(--gutter);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 24px;
  border-bottom: 1px solid transparent;
  transition: min-height .35s ease, background .35s ease, border-color .35s ease, backdrop-filter .35s ease;
}
.header.is-scrolled {
  min-height: calc(var(--cell) * 4);
  background: rgba(255, 255, 255, .84);
  border-color: var(--line);
  /* Safari needs the prefix through 17; without it the scrolled header is a
     flat translucent panel and the type behind it shows through */
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
  backdrop-filter: blur(16px) saturate(1.2);
}

/* One wordmark everywhere: "Lan" with a coral a, exactly as the loader draws it.
   text-transform:none matters — the footer sets uppercase on itself, which used
   to turn this into "LAN". */
.logo {
  display: inline-flex;
  width: max-content;
  align-items: baseline;
  gap: 9px;
  color: var(--ink);
  text-transform: none;
}
.logo__word { font: 700 24px/1 "Syne", "Helvetica Neue", Arial, sans-serif; letter-spacing: -0.08em; }
.logo__word i { font-style: normal; color: var(--coral); }
.logo__llc { font: 700 10px/1 "Syne", "Helvetica Neue", Arial, sans-serif; letter-spacing: .14em; }

/* micro-labels are monospace, not Syne — small caps in a display face turn to
   mush, and the mono is what makes them read as engineered */
.nav, .header__contact, .kicker, .section-tag, .hero__index,
.app__num, .app__tag, .app__stores, .step__n, .rail__hint, footer {
  font-family: var(--mono);
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
}

.nav { display: flex; gap: clamp(20px, 2.6vw, 40px); font-size: 11px; }
.nav a, .header__contact { position: relative; padding: 10px 0; }
.nav a::after, .header__contact::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: 4px;
  left: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform .28s ease;
}
.nav a:hover::after, .nav a:focus-visible::after, .header__contact:hover::after { transform: scaleX(1); transform-origin: left; }
.header__contact { justify-self: end; font-size: 11px; }
.header__contact span { display: inline-block; margin-left: 8px; transition: transform .25s ease; }
.header__contact:hover span { transform: translate(4px, -4px); }

/* The header is fixed, so anything the browser scrolls into view — a #work jump,
   the skip link's target, or a link the keyboard has just reached inside the
   rail — lands underneath it unless it reserves the header's height first.
   :where() keeps this at zero specificity so nothing has to fight it. */
:where(main > section, #main, a, button, summary, [tabindex]) {
  scroll-margin-top: calc(var(--cell) * 7);
}

/* ---------- shared type ----------------------------------------------- */

/* Gated on .js — main.js sets that class on <html> as its first statement.
   Without it the hidden state never applies, so if the module scripts fail to
   parse or are blocked, the page renders finished instead of blank. Only JS can
   ever remove .in-view's counterpart, so JS has to be the one that hides it. */
.js .reveal { opacity: 0; transform: translate3d(0, 26px, 0); transition: opacity .8s ease var(--delay,0ms), transform .9s cubic-bezier(.18,.76,.14,1) var(--delay,0ms); }
.js .reveal.in-view { opacity: 1; transform: none; }

/* line-height below 1 means glyph ink overflows the line box; reserve room for
   descenders (the g in "change", the pp in "shipped") and pull it back in */
.display-copy { margin: 0 0 -.12em; padding-bottom: .12em; font: 600 clamp(44px, 6.4vw, 104px)/.94 "Syne", "Helvetica Neue", Arial, sans-serif; letter-spacing: -.055em; }
/* Japanese display type is sized so each authored line FITS its column. A
   full-width character is ~0.95em with this tracking, so the longest line here
   (12 characters) needs ~11.4em — size accordingly or it wraps mid-phrase. */
.display-copy--ja {
  font-family: "Noto Sans JP", sans-serif;
  font-size: clamp(26px, 3.3vw, 58px);
  font-weight: 600;
  line-height: 1.34;
  letter-spacing: -.05em;
}

/* Every section heading carries its second line in an accent colour — the rule
   the hero establishes with "Beautiful change.", applied down the page.
   The order is not arbitrary: it walks the ribbon in the hero artwork from left
   to right (coral → blue → lime → lavender → aqua), so scrolling the page
   travels along the same gradient the key visual does, then closes back on
   coral at the contact block. */
.hl { color: var(--hl, var(--coral)); }
.statement { --hl: var(--blue); }      /* ribbon 2 */
.work      { --hl: var(--lime); }      /* ribbon 3 */
.process   { --hl: var(--lavender); }  /* ribbon 4 */
.about     { --hl: var(--aqua); }      /* ribbon 5 */
.contact   { --hl: var(--coral); }     /* back to the start */

.section-tag { display: flex; gap: 12px; align-items: center; margin: 0 0 calc(var(--cell) * 5); font-size: 10px; }
.section-tag span { display: grid; width: 30px; height: 30px; place-items: center; border: 1px solid currentColor; border-radius: 50%; font-size: 9px; letter-spacing: 0; }

/* ---------- hero ------------------------------------------------------ */

.hero {
  min-height: 100svh;
  padding: calc(var(--cell) * 11) var(--gutter) calc(var(--cell) * 5);
  display: grid;
  align-content: end;
}
.hero > * { width: 100%; max-width: var(--maxw); margin-inline: auto; }

.hero__copy { position: relative; z-index: 2; }
.kicker { display: flex; gap: 24px; justify-content: space-between; width: min(460px, 100%); margin: 0 0 clamp(30px, 6vh, 74px); font-size: 10px; }
/* Real space below the descender — no negative margin pulling the next block
   back over it. .3em covers Syne's ink descent (.208em) with room to spare even
   if a fallback face with deeper descenders is standing in. */
/* The cap is set by the longest authored line, not by taste: "Small shifts."
   measures 5.06em, so 1196px / 5.06 = 236px is the width ceiling. The binding
   limit here is height — three lines have to leave room for the copy below. */
.hero__title { margin: 0; padding-bottom: .3em; font: 600 clamp(46px, 10.4vw, 182px)/.82 "Syne", "Helvetica Neue", Arial, sans-serif; letter-spacing: -.08em; }
.title-line { display: block; }
/* same contract as .reveal: no JS, no hidden state */
.js .title-line { opacity: 0; transform: translateY(.6em); }
.is-ready .title-line { animation: titleIn 1s cubic-bezier(.16,.84,.18,1) .22s forwards; }
.is-ready .title-line:nth-child(2) { animation-delay: .33s; }
.is-ready .title-line:nth-child(3) { animation-delay: .44s; }
.title-line--accent { color: var(--coral); }

/* the title now reserves its own descender room, so this gap is smaller */
.hero__bottom { width: min(560px, 100%); margin-top: clamp(8px, 2.2vh, 32px); display: flex; align-items: end; justify-content: space-between; gap: 28px; }
.hero__bottom p { margin: 0; font-size: 14px; line-height: 1.95; }
.round-link {
  flex: 0 0 auto;
  display: grid;
  width: 56px;
  height: 56px;
  place-items: center;
  border: 1px solid var(--ink);
  border-radius: 50%;
  font: 500 22px/1 "Syne", "Helvetica Neue", Arial, sans-serif;
  transition: background .25s ease, color .25s ease;
}
.round-link:hover { background: var(--ink); color: var(--paper); }

/* the right of the hero is held by the field itself — this is just the
   index that anchors it typographically */
.hero__index {
  margin: calc(var(--cell) * 4) 0 0;
  display: flex;
  gap: 22px;
  align-items: center;
  justify-content: flex-end;
  font-size: 10px;
  color: var(--muted);
}
.hero__index span { position: relative; padding-left: 16px; }
.hero__index span::before { content: ""; position: absolute; left: 0; top: 50%; width: 6px; height: 6px; margin-top: -3px; border-radius: 50%; background: var(--coral); }
.hero__index span:nth-child(2)::before { background: var(--blue); }
.hero__hint::before { background: var(--lime) !important; }

/* ---------- statement -------------------------------------------------- */

.statement { padding: calc(var(--cell) * 12) var(--gutter); }
.statement__body { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(250px, .55fr); gap: clamp(36px, 4.4vw, 80px); align-items: end; }
.statement__aside { max-width: 34ch; padding-bottom: 10px; }
/* balance evens the line lengths so the last line is not a lone 「す。」 */
.statement__aside p { margin: 0; font-size: clamp(15px, 1.1vw, 17px); line-height: 2.1; color: #33352f; text-wrap: balance; }

/* ---------- work / rail ------------------------------------------------ */

.work { padding: calc(var(--cell) * 12) 0; }
.work > .section-tag, .work__intro { padding-inline: var(--gutter); }
/* the heading column has to hold "Small shifts," at ~5.05em — a 1:1 split left
   it 491px against the 525px it needs, and it wrapped to three lines */
.work__intro { display: grid; grid-template-columns: 1.25fr .75fr; column-gap: clamp(36px, 5vw, 88px); align-items: end; margin-bottom: calc(var(--cell) * 6); }
.work__intro .section-tag { grid-column: 1 / -1; }
.work__lead { max-width: 42ch; margin: 0 0 6px; justify-self: end; font-size: 14px; line-height: 2.05; color: #33352f; }

.rail { position: relative; }
.rail__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
  overscroll-behavior-x: contain;
  cursor: grab;
  /* must match .rail__track's inline padding — without it, mandatory snapping
     aligns the first card flush to the scrollport and eats the padding, so the
     rail no longer lines up with the heading above it */
  scroll-padding-inline-start: var(--rail-inset);
}
.rail__viewport::-webkit-scrollbar { display: none; }
/* belt and braces with the dragstart handler in main.js — Safari and Chrome
   honour this, Firefox relies on the JS */
/* -webkit-user-drag only — `user-drag` is not a real property in any engine */
.rail__viewport img, .rail__viewport a { -webkit-user-drag: none; }
.rail__viewport.is-dragging { cursor: grabbing; scroll-snap-type: none; scroll-behavior: auto; -webkit-user-select: none; user-select: none; }
.rail__viewport.is-dragging a { pointer-events: none; }

/* The first card must start exactly where the heading's text starts. That is the
   centred container's left edge plus its own inner gutter — 100% here is the
   scrollport, which excludes the scrollbar, so it stays exact at any width. */
.rail { --rail-inset: calc(max(0px, (100% - var(--maxw)) / 2) + var(--gutter)); }
.rail__track {
  width: max-content;
  margin: 0;
  padding-inline: var(--rail-inset);
  display: flex;
  gap: clamp(14px, 1.6vw, 24px);
  list-style: none;
}
.rail__track > li { display: flex; scroll-snap-align: start; }

.app {
  position: relative;
  display: flex;
  flex-direction: column;
  width: clamp(260px, 72vw, 396px);
  overflow: hidden;
  border-radius: calc(var(--cell) * 2);
  background: var(--app, var(--blue));
  color: var(--app-ink, #fff);
}

/* Hover does not lift the card. Its edge crumbles into chunky pixels on the
   same 13px module as the field — two pseudo-elements rather than one masked
   with mask-composite, so the frame works without composite support.
   ::before holds the top and bottom bands, ::after the left and right. */
.app::before, .app::after {
  content: "";
  position: absolute;
  z-index: 3;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background-image: url(../images/crumble.png), url(../images/crumble.png);
  background-size: calc(var(--cell) * 8) calc(var(--cell) * 8);
  transition: opacity .2s steps(3);
}
/* the band fades inward, so the edge dissolves rather than gaining a white rim */
.app::before {
  -webkit-mask-image: linear-gradient(to bottom, #000, transparent var(--crumble), transparent calc(100% - var(--crumble)), #000);
  mask-image: linear-gradient(to bottom, #000, transparent var(--crumble), transparent calc(100% - var(--crumble)), #000);
}
.app::after {
  -webkit-mask-image: linear-gradient(to right, #000, transparent var(--crumble), transparent calc(100% - var(--crumble)), #000);
  mask-image: linear-gradient(to right, #000, transparent var(--crumble), transparent calc(100% - var(--crumble)), #000);
}
.app { --crumble: calc(var(--cell) * 3); }

@media (hover: hover) {
  .app:hover::before, .app:hover::after { opacity: 1; animation: crumble .48s steps(1) infinite; }
  .app:hover::after { animation-delay: -.24s; }
}


/* Per-app colour and artwork. These live here rather than in a style attribute
   because a url() inside a custom property resolves against the stylesheet that
   declares it — declared in the HTML it would resolve against the document and
   point at LP/css/images/…, which does not exist. */
.app--viralume { --app: #2347df; --app-ink: #f8f4ec; --app-art: url(../images/apps/bg-viralume.webp); }
.app--tsuzuru { --app: #cbe65d; --app-ink: #171816; --app-art: url(../images/apps/bg-tsuzuru.webp); }
.app--lifenote { --app: #baa9ff; --app-ink: #171816; --app-art: url(../images/apps/bg-lifenote.webp); }
/* Moffy's coral is a mid-tone: the off-white it used to carry measured 2.8:1
   against it, so every word on that one card sat below the AA floor. Ink reads
   6.1:1 on the same background and matches the other light cards. */
.app--moffy { --app: #ff6449; --app-ink: #171816; --app-art: url(../images/apps/bg-moffy.webp); }
.app--copyblocks { --app: #a9e6de; --app-ink: #171816; --app-art: url(../images/apps/bg-copyblocks.webp); }

.app__art {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3.4;
  overflow: hidden;
  /* the colour field behind the icon. Swap for a generated image by setting
     --app-art on the card. */
  background:
    radial-gradient(120% 90% at 22% 8%, rgba(255,255,255,.42), transparent 62%),
    radial-gradient(90% 80% at 88% 96%, rgba(0,0,0,.16), transparent 58%),
    var(--app-art, none);
  background-size: cover;
  background-position: center;
}
/* The app icon, set on the colour field like an object on a plinth. Sized from
   the width and squared by aspect-ratio, so no percentage ever resolves against
   this box's height — the screenshot it replaced did that, and the height could
   not be resolved without the frame's height, which the frame took from its own
   content. Each engine broke that loop its own way: Blink inflated the image to
   139% of the frame and clipped the bottom 40%, WebKit pushed it out of the
   frame entirely. Keep the size on the width axis and that cannot recur. */
.app__icon {
  width: 44%;
  height: auto;
  aspect-ratio: 1;
  /* The source files are square and unrounded, so the corner comes from here.
     22.5% is as close as a border-radius gets to the iOS superellipse. */
  border-radius: 22.5%;
  object-fit: cover;
  /* Load-bearing, not decoration: measured against the artwork behind them the
     icons separate by only 1.3–2.1:1 in luminance, so this edge is what holds
     the tile apart from the field. Tight rather than wide and soft. */
  box-shadow: 0 8px 24px rgba(0, 0, 0, .34);
}
/* If an icon is missing the card stays a finished colour panel. No dashed
   outline: a placeholder that looks like a broken layout is worse than a plain
   one, and it made a caching problem look like a design decision. */
.app__art.is-empty .app__icon { display: none; }

/* The whole card navigates to the App Store: this stretches over it. The
   per-store links sit above it (z-index 5) so they stay individually clickable.
   .app__meta must NOT set z-index — that would trap its children in a stacking
   context below the overlay. */
.app__link { position: absolute; inset: 0; z-index: 4; }
/* The card clips its own overflow for the crumble frame, so the global
   focus-visible ring — drawn 3px OUTSIDE the link — was cut away entirely and
   the keyboard user saw nothing. A negative offset draws it inside the card
   instead, and currentColor keeps it visible on every card colour (the blue
   ring was invisible against Viralume's blue). */
.app__link:focus-visible {
  outline: 3px solid currentColor;
  outline-offset: -7px;
  border-radius: var(--cell);
}
.app__meta { position: relative; display: grid; gap: 10px; padding: calc(var(--cell) * 2); }
.app__meta > * { margin: 0; }
/* opacity is what sets the contrast here: at .7 the number measured 4.2:1 on
   the darkest card, under the 4.5:1 the 10px size needs */
.app__num { font-size: 10px; opacity: .85; }
.app__tag { font-size: 9px; opacity: .92; }
.app__name { font: 600 clamp(30px, 3.2vw, 42px)/1 "Syne", "Helvetica Neue", Arial, sans-serif; letter-spacing: -.05em; }
.app__name em { font-family: "Noto Sans JP", sans-serif; font-style: normal; font-size: .8em; }
.app__desc { margin-top: 4px; font-size: 13px; line-height: 1.95; opacity: .92; }

/* one row per card, holding however many stores that app ships on */
.app__stores { position: relative; z-index: 5; margin-top: 10px; display: flex; flex-wrap: wrap; gap: 6px 20px; font-size: 10px; }
.app__stores a { display: inline-flex; align-items: center; gap: 7px; padding-bottom: 4px; border-bottom: 1px solid currentColor; }
.app__stores i { font-style: normal; transition: transform .3s ease; }
.app__stores a:hover i { transform: translate(4px, -4px); }

.rail__controls {
  max-width: var(--maxw);
  margin: calc(var(--cell) * 3) auto 0;
  padding: 0 var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}
.rail__hint { margin: 0; font-size: 10px; color: var(--muted); }
.rail__buttons { display: flex; gap: 10px; }
.rail__btn {
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border: 1px solid var(--ink);
  border-radius: 50%;
  background: transparent;
  color: var(--ink);
  font-size: 16px;
  cursor: pointer;
  transition: background .25s ease, color .25s ease, opacity .25s ease;
}
.rail__btn:hover:not(:disabled) { background: var(--ink); color: var(--paper); }
.rail__btn:disabled { opacity: .28; cursor: default; }

/* the gutter matches .rail__controls above it, so the bar starts and ends on
   the same column as the heading and the buttons */
.rail__progress { width: calc(100% - var(--gutter) * 2); max-width: calc(var(--maxw) - var(--gutter) * 2); margin: calc(var(--cell) * 2) auto 0; height: 2px; background: var(--line); }
.rail__progress i { display: block; width: 100%; height: 100%; background: var(--ink); transform: scaleX(var(--rail, .3)); transform-origin: left; transition: transform .2s ease; }

/* ---------- process ---------------------------------------------------- */

.process { padding: calc(var(--cell) * 12) var(--gutter); }
.process__head { margin-bottom: calc(var(--cell) * 6); }
.process__lead { max-width: 46ch; margin: calc(var(--cell) * 2) 0 0; font-size: 15px; line-height: 2; color: #33352f; }

/* hairlines come from borders, not from a background showing through a 1px
   gap — the steps must stay transparent so the reactive dots read through them */
.steps { margin: 0; padding: 0; display: grid; grid-template-columns: repeat(4, 1fr); list-style: none; }
.step {
  position: relative;
  padding: calc(var(--cell) * 3) calc(var(--cell) * 2);
  display: grid;
  gap: 12px;
  align-content: start;
  background: transparent;
  transition: background .35s ease;
}
/* Source order, not grid position — so at the narrower column counts below the
   first card of each new row inherited a hairline hanging off its left edge.
   The per-breakpoint resets are in the responsive block. */
.step + .step { border-left: 1px solid var(--line); }
@media (hover: hover) {
  .step:hover { background: rgba(23, 24, 22, .035); }
}
.step__n { font-size: 10px; color: var(--muted); }
.step h3 { margin: 0; display: flex; flex-direction: column; gap: 4px; font: 600 clamp(22px, 2vw, 30px)/1.05 "Syne", "Helvetica Neue", Arial, sans-serif; letter-spacing: -.04em; }
.step h3 em { font-family: "Noto Sans JP", sans-serif; font-style: normal; font-size: 12px; font-weight: 500; letter-spacing: .06em; color: var(--muted); }
.step p { margin: 0; font-size: 13px; line-height: 1.95; color: #33352f; }

/* ---------- about ------------------------------------------------------ */

.about { padding: calc(var(--cell) * 12) var(--gutter) 0; overflow: hidden; }
.about__grid { display: grid; grid-template-columns: 1.2fr .8fr; gap: clamp(40px, 7vw, 140px); align-items: end; padding-bottom: calc(var(--cell) * 9); }
.about__copy { max-width: 44ch; padding-bottom: 8px; }
.about__copy p:first-child { margin: 0 0 24px; font-size: clamp(20px, 1.8vw, 28px); font-weight: 600; letter-spacing: -.04em; }
.about__copy p:last-child { margin: 0; line-height: 2.05; color: #33352f; }

.ticker { --tick: 13px; width: calc(100% + (var(--gutter) * 2)); margin-left: calc(var(--gutter) * -1); overflow: hidden; background: var(--lime); }
.ticker > div { width: max-content; padding: 22px 0; display: flex; align-items: center; gap: calc(var(--tick) * 2); animation: ticker 30s linear infinite; }
/* WCAG 2.2.2 — moving content needs a way to stop it */
.ticker:hover > div, .ticker:focus-within > div { animation-play-state: paused; }
.ticker span { font: 700 clamp(20px, 2.6vw, 38px)/1 "Syne", "Helvetica Neue", Arial, sans-serif; letter-spacing: -.05em; }
.ticker i { color: var(--blue); font-style: normal; font-size: 18px; }

/* ---------- company ----------------------------------------------------- */

.company { padding: calc(var(--cell) * 12) var(--gutter); }
.company__grid { display: grid; grid-template-columns: .8fr 1.2fr; gap: clamp(40px, 6vw, 100px); align-items: start; }
.company .display-copy { position: sticky; top: calc(var(--cell) * 9); color: var(--blue); }
.company-table { margin: 0; border-top: 1px solid var(--ink); }
.company-table > div { display: grid; grid-template-columns: 150px 1fr; gap: 28px; padding: calc(var(--cell) * 2.2) 0; border-bottom: 1px solid var(--line); }
.company-table dt { font-family: var(--mono); font-size: 10px; font-weight: 500; letter-spacing: .1em; line-height: 1.9; text-transform: uppercase; }
.company-table dd { margin: 0; font-size: 14px; }
.company-table small { color: var(--muted); font-size: 11px; }
.company-table ol { margin: 0; padding: 0; list-style: none; counter-reset: business; }
.company-table li { position: relative; margin-bottom: 13px; padding-left: 36px; counter-increment: business; }
.company-table li::before { content: "0" counter(business); position: absolute; left: 0; top: .3em; color: var(--muted); font-family: var(--mono); font-size: 9px; }
.company-table a { border-bottom: 1px solid currentColor; }

/* ---------- smear band --------------------------------------------------- */
/* The band itself is empty on purpose: its text lives in the field canvas, so
   scrolling drags the stamp through the decaying buffer and the letters trail. */

.smear { height: calc(var(--cell) * 14); overflow: hidden; }
.smear span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* ---------- contact ------------------------------------------------------ */

.contact { padding: calc(var(--cell) * 8) var(--gutter) calc(var(--cell) * 14); }
.contact__content { margin-top: calc(var(--cell) * 6); text-align: center; }
.contact__content > p { margin: 0 0 24px; font-family: var(--mono); font-size: 10px; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); }
.contact .display-copy { font-size: clamp(52px, 9vw, 150px); }
.contact__mail {
  display: inline-flex;
  align-items: center;
  gap: 18px;
  margin-top: calc(var(--cell) * 5);
  padding: 14px 0 10px;
  border-bottom: 1px solid var(--ink);
  font: 600 clamp(17px, 2vw, 32px)/1 "Syne", "Helvetica Neue", Arial, sans-serif;
  letter-spacing: -.03em;
}
.contact__mail span { transition: transform .3s ease; }
.contact__mail:hover span { transform: translate(6px, -6px); }

/* ---------- footer -------------------------------------------------------- */

footer {
  position: relative;
  z-index: 2;
  min-height: calc(var(--cell) * 9);
  padding: 0 var(--gutter);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 26px;
  font-size: 9px;
}
footer p { margin: 0; }
footer > a:last-child { justify-self: end; }

/* ---------- keyframes ------------------------------------------------------ */

@keyframes loaderLine { 0% { transform: translateX(-100%); } 58% { transform: translateX(0); } 100% { transform: translateX(100%); } }
@keyframes loaderOut { to { opacity: 0; visibility: hidden; transform: scaleY(0); } }
@keyframes titleIn { to { opacity: 1; transform: none; } }
/* The track holds the phrase twice with a gap between every item, so one full
   loop is half the track PLUS half a gap — at a plain -50% the band jumped 13px
   on every repeat. --tick is that half gap. */
@keyframes ticker { to { transform: translateX(calc(-50% - var(--tick))); } }
/* stepped, ~8fps: the crumble should read as chunky frames, not as a smooth pan */
@keyframes crumble {
  0%   { background-position: 0 0, calc(var(--cell) * 4) calc(var(--cell) * 3); }
  25%  { background-position: calc(var(--cell) * 1) calc(var(--cell) * 2), calc(var(--cell) * 3) calc(var(--cell) * 1); }
  50%  { background-position: calc(var(--cell) * 2) calc(var(--cell) * 1), calc(var(--cell) * 1) calc(var(--cell) * 4); }
  75%  { background-position: calc(var(--cell) * 3) calc(var(--cell) * 3), calc(var(--cell) * 2) calc(var(--cell) * 2); }
}

/* ==========================================================================
   Responsive
   ========================================================================== */

/* wide but short — the common laptop shape. Keep the first view on one screen
   instead of letting the 3-line title push the copy below the fold. */
@media (min-width: 761px) and (max-height: 860px) {
  .hero { padding-top: calc(var(--cell) * 8); padding-bottom: calc(var(--cell) * 3); }
  .hero__title { font-size: clamp(52px, 7.2vw, 118px); }
  .kicker { margin-bottom: clamp(18px, 3vh, 38px); }
  .hero__bottom { margin-top: clamp(4px, 1vh, 16px); }
  .hero__index { margin-top: calc(var(--cell) * 2); }
}

@media (max-width: 1080px) {
  .statement__body { grid-template-columns: 1fr; gap: calc(var(--cell) * 4); }
  .statement__aside { max-width: 52ch; }
  .work__intro { grid-template-columns: 1fr; gap: calc(var(--cell) * 3); }
  .work__lead { justify-self: start; }
  .steps { grid-template-columns: repeat(2, 1fr); }
  /* two per row: step 03 opens row two, so it must not carry a left hairline */
  .step:nth-child(odd) { border-left: 0; }
  .step:nth-child(n + 3) { border-top: 1px solid var(--line); }
  .about__grid, .company__grid { grid-template-columns: 1fr; gap: calc(var(--cell) * 4); }
  .company .display-copy { position: static; }
}

@media (max-width: 760px) {
  :root { --gutter: 22px; }

  body { font-size: 14px; }

  .header { min-height: calc(var(--cell) * 5); grid-template-columns: auto 1fr; gap: 12px; }
  .header.is-scrolled { min-height: calc(var(--cell) * 4); }
  .nav { display: none; }
  .logo__word { font-size: 20px; }
  .header__contact { font-size: 9px; letter-spacing: .1em; }

  .section-tag { margin-bottom: calc(var(--cell) * 3); }

  /* Content-driven height on phones. At 100svh the copy is bottom-aligned and
     everything left over piles up as dead space under the header — 338px of it
     on a 390x844 screen, against 389px of actual content. */
  .hero { min-height: 68svh; padding: calc(var(--cell) * 9) var(--gutter) calc(var(--cell) * 5); }
  .kicker { flex-direction: column; gap: 4px; width: 100%; margin-bottom: 34px; }
  /* "Small shifts." is 5.06em at the desktop tracking, but this block loosens it
     to -.06em, which pushes the line to ~5.32em. 16vw / 66px is the largest that
     still fits between the gutters from 381px up. */
  .hero__title { font-size: clamp(46px, 16vw, 66px); line-height: .88; letter-spacing: -.06em; }
  .hero__bottom { flex-direction: row; align-items: flex-end; margin-top: 34px; gap: 18px; }
  .hero__bottom p { font-size: 12.5px; line-height: 1.9; }
  .round-link { width: 48px; height: 48px; font-size: 18px; }
  .hero__index { margin-top: calc(var(--cell) * 3); flex-wrap: wrap; gap: 10px 18px; justify-content: flex-start; font-size: 9px; }
  .hero__hint { display: none; }

  .statement, .work, .process, .about, .company { padding-block: calc(var(--cell) * 7); }
  .about { padding-bottom: 0; }
  .about__grid { padding-bottom: calc(var(--cell) * 6); }
  .display-copy { font-size: clamp(38px, 11vw, 60px); }
  .display-copy--ja { font-size: clamp(24px, 6.9vw, 38px); line-height: 1.42; letter-spacing: -.04em; }

  /* rail: swipe is the primary control on touch, so the buttons go away and
     the cards get a peek of the next one */
  .app { width: 76vw; }
  .app__art { aspect-ratio: 4 / 3.6; }
  .app__meta { padding: calc(var(--cell) * 1.6); gap: 8px; }
  .app__name { font-size: clamp(26px, 7vw, 34px); }
  .app__desc { font-size: 12.5px; line-height: 1.9; }
  .rail__buttons { display: none; }
  .rail__controls { justify-content: flex-start; margin-top: calc(var(--cell) * 2); }

  /* one per row: the divider becomes horizontal */
  .steps { grid-template-columns: 1fr; }
  .step + .step { border-left: 0; border-top: 1px solid var(--line); }
  .step { padding: calc(var(--cell) * 2.4) calc(var(--cell) * 1.6); }
  .step h3 { flex-direction: row; align-items: baseline; gap: 10px; }

  .ticker { --tick: 10px; }  /* the gap follows it, so the loop stays seamless */
  .ticker > div { padding: 16px 0; }
  .ticker span { font-size: clamp(18px, 5.6vw, 26px); }

  .company-table > div { grid-template-columns: 1fr; gap: 10px; padding: calc(var(--cell) * 1.8) 0; }
  .company-table dt { color: var(--muted); }
  .company-table li { padding-left: 30px; }

  .smear { height: calc(var(--cell) * 10); }

  .contact { padding-bottom: calc(var(--cell) * 9); }
  .contact__content { margin-top: calc(var(--cell) * 3); }
  .contact .display-copy { font-size: clamp(40px, 13vw, 68px); }
  .contact__mail { margin-top: calc(var(--cell) * 3); font-size: 15px; gap: 12px; }

  footer { min-height: calc(var(--cell) * 12); grid-template-columns: 1fr 1fr; gap: 14px; padding-block: calc(var(--cell) * 2); }
  footer p { justify-self: end; }
  footer > a:last-child { justify-self: start; grid-column: 1 / -1; }
}

@media (max-width: 380px) {
  .app { width: 82vw; }
  .hero__title { font-size: clamp(40px, 14.5vw, 54px); }
}

/* the field is a cursor effect first; on touch it stays but much quieter */
@media (hover: none), (pointer: coarse) {
  #field { opacity: .55; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    scroll-behavior: auto !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .loader { display: none; }
  /* the .js variants have to be repeated or they out-specify this reset */
  .reveal, .title-line, .js .reveal, .js .title-line { opacity: 1; transform: none; }
  .ticker > div { transform: translateX(-8%); }
  #field { opacity: .35; }
}
