/* Hallmark · genre: modern-minimal · macrostructure: Feature Stack · design-system: design.md
 * theme: Graphite Blue · tone: quiet modern-luxury · nav: quiet floating · footer: Ft5 Statement
 * enrichment: existing local mountain profile photograph · pre-emit critique: P5 H5 E5 S5 R5 V5
 * contrast: pass (40–41) · slop: 58/58 · mobile: pass (34, 49, 50–57)
 * honest: pass (46) · chrome: pass (47) · tokens: pass (48) · icons: pass (30)
 */

html,
body {
  overflow-x: clip;
}

body {
  background: var(--color-paper);
}

main,
.site-footer {
  position: relative;
  z-index: var(--z-base);
}

.site-header {
  inset-block-start: var(--space-sm);
  width: calc(100% - (var(--space-sm) * 2));
  min-height: var(--header-height);
  margin-inline: auto;
  padding: var(--space-2xs) var(--space-xs);
  background: var(--color-paper-2);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-float);
}

.site-header.is-compact {
  background-color: var(--color-surface);
  box-shadow: var(--shadow-cinematic);
}

.wordmark {
  min-height: 44px;
  padding-inline: var(--space-2xs);
  font-size: var(--text-md);
  font-weight: 700;
  font-variation-settings: "wght" 700;
  letter-spacing: 0.015em;
  text-transform: none;
}

.wordmark-mark {
  width: 0.625rem;
  height: 0.625rem;
  border: 0;
  border-radius: var(--radius-pill);
  box-shadow: none;
}

.menu-toggle {
  background: var(--color-transparent);
  color: var(--color-ink);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius-pill);
}

.js-enabled .nav-menu {
  inset-block-start: calc(var(--header-height) + var(--space-lg));
  inset-inline: max(var(--space-sm), env(safe-area-inset-left))
    max(var(--space-sm), env(safe-area-inset-right));
  max-block-size: max(
    0px,
    calc(
      100dvh - var(--header-height) - var(--space-lg) - var(--space-sm) -
        env(safe-area-inset-bottom)
    )
  );
  padding: var(--space-xs);
  overflow-y: auto;
  overscroll-behavior: contain;
  scroll-padding-block: var(--space-xs);
  background: var(--color-paper-2);
  color: var(--color-ink);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-float);
}

.js-enabled .nav-menu :where(a, button) {
  scroll-margin-block: var(--space-xs);
}

.nav-menu a,
.language-toggle {
  padding-inline: var(--space-sm);
  border: var(--rule-hair) solid var(--color-transparent);
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 700;
  font-variation-settings: normal;
  letter-spacing: 0.01em;
  text-transform: none;
}

html:lang(ja) :where(.nav-menu a, .language-toggle) {
  font-family: var(--font-body-ja);
}

.language-toggle {
  background: var(--color-surface-raised);
  color: var(--color-ink);
  border-color: var(--color-rule);
}

.nav-menu a[aria-current="location"] {
  background: var(--color-accent);
  color: var(--color-accent-ink);
  border-color: var(--color-accent);
}

.hero {
  width: 100%;
  max-width: none;
  min-height: auto;
  padding: var(--space-3xl) var(--page-gutter) var(--space-2xl);
  display: block;
}

.hero-sticky {
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  display: grid;
  gap: var(--space-xl);
  opacity: var(--hero-opacity, 1);
  transform: translateY(var(--hero-depth, 0)) scale(var(--hero-scale, 1));
  transform-origin: center;
}

.hero h1 {
  max-width: 12ch;
  font-family: var(--font-heading);
  font-size: clamp(3rem, 6vw + 1rem, 5.5rem);
  font-weight: 650;
  font-variation-settings: "wght" 650;
  letter-spacing: -0.035em;
  line-height: 1.02;
  overflow-wrap: break-word;
  text-transform: none;
  text-wrap: balance;
}

html:lang(ja) .hero h1 {
  font-family: var(--font-heading-ja);
  font-size: clamp(2.5rem, 6vw, 4.75rem);
  font-weight: 700;
  line-height: 1.08;
}

.hero-title-accent {
  color: var(--color-ink);
}

.hero-visual {
  padding: 0;
  overflow: clip;
  background: var(--color-surface);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-cinematic);
}

.hero-visual img {
  border: 0;
  border-radius: var(--radius-lg) var(--radius-lg) var(--radius-none) var(--radius-none);
  filter: saturate(0.82) contrast(1.02);
}

.hero-visual figcaption {
  padding: var(--space-xs) var(--space-sm) var(--space-sm);
  color: var(--color-ink-2);
}

.hero-support {
  max-width: 42rem;
  padding-inline-start: 0;
}

.hero-role {
  color: var(--color-accent);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 700;
  font-variation-settings: normal;
  letter-spacing: 0.06em;
}

html:lang(ja) .hero-role {
  font-family: var(--font-body-ja);
}

.hero-lede {
  margin-block-start: var(--space-sm);
  color: var(--color-ink-2);
  line-height: 1.75;
}

.hero-actions {
  gap: var(--space-xs);
}

.button,
.text-link {
  min-height: 48px;
  padding: var(--space-xs) var(--space-md);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 700;
  font-variation-settings: normal;
  letter-spacing: 0.01em;
  text-transform: none;
}

html:lang(ja) :where(.button, .text-link) {
  font-family: var(--font-body-ja);
}

.button-primary {
  background: var(--color-ink);
  color: var(--color-paper);
  box-shadow: none;
}

.text-link {
  background: var(--color-transparent);
}

.section-shell {
  max-width: var(--content-width);
  padding-block: var(--space-3xl);
}

.section-heading h2,
.projects-intro h2,
.contact h2 {
  font-family: var(--font-heading);
  font-size: clamp(2.75rem, 7vw, 5.25rem);
  font-weight: 600;
  font-variation-settings: "wght" 600;
  letter-spacing: -0.03em;
  line-height: 1.03;
  overflow-wrap: break-word;
  text-transform: none;
  text-wrap: balance;
}

html:lang(ja) :where(.section-heading h2, .projects-intro h2, .contact h2) {
  font-family: var(--font-heading-ja);
  font-weight: 700;
}

.about-copy {
  display: grid;
  gap: var(--space-lg);
}

.about-copy p,
.about-copy p + p {
  margin: 0;
}

.projects {
  background: var(--color-transparent);
  color: var(--color-ink);
  border-block: 0;
}

.projects-intro h2,
.projects-intro p {
  color: var(--color-ink);
}

.contact {
  padding-block: var(--space-4xl);
}

.contact-panel h2,
.contact-panel > div > p,
.contact-links a {
  color: var(--color-ink);
}

.contact-panel :where(a, button):focus-visible {
  outline-color: var(--color-focus);
}

.site-footer {
  background: var(--color-paper);
  border-block-start: var(--rule-hair) solid var(--color-rule);
}

.footer-meta {
  border-block-start: var(--rule-hair) solid var(--color-rule);
}

.footer-meta a {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 700;
  font-variation-settings: normal;
  letter-spacing: 0.01em;
  text-transform: none;
}

html:lang(ja) .footer-meta a {
  font-family: var(--font-body-ja);
}

@media (hover: hover) and (pointer: fine) {
  .nav-menu a:hover,
  .language-toggle:hover,
  .button-primary:hover,
  .text-link:hover {
    background: var(--color-surface-raised);
    color: var(--color-ink);
  }

  .button-primary:hover {
    transform: translateY(-1px);
  }

  .contact-links a:hover {
    background: var(--color-paper-2);
    color: var(--color-ink);
  }

}

@media (max-width: 30rem) {
  :where(
    .hero h1,
    .section-heading h2,
    .projects-intro h2,
    .contact h2
  ) {
    min-width: 0;
    overflow-wrap: anywhere;
  }

  html:lang(ja) .hero h1 span {
    white-space: normal;
    line-break: strict;
    word-break: normal;
  }

  html:lang(ja) .hero h1 {
    font-size: min(2.5rem, 18vw);
  }

  @supports (word-break: auto-phrase) {
    html:lang(ja) .hero h1 span {
      word-break: auto-phrase;
    }
  }
}

@media (min-width: 48rem) {
  .site-header {
    width: min(calc(100% - (var(--space-lg) * 2)), 68rem);
    padding-inline: var(--space-sm);
  }

  .js-enabled .nav-menu,
  .nav-menu {
    max-block-size: none;
    padding: 0;
    overflow-y: visible;
    overscroll-behavior: auto;
    background: var(--color-transparent);
    color: var(--color-ink);
    border: 0;
    border-radius: var(--radius-none);
    box-shadow: none;
  }

  .hero-sticky {
    grid-template-columns: minmax(0, 1.08fr) minmax(18rem, 0.72fr);
    grid-template-areas:
      "stage visual"
      "support visual";
    align-items: center;
    column-gap: clamp(2rem, 6vw, 6rem);
    row-gap: var(--space-lg);
  }

  .hero-stage {
    grid-area: stage;
  }

  .hero-visual {
    grid-area: visual;
  }

  .hero-support {
    grid-area: support;
  }

}

@media (prefers-reduced-motion: reduce) {
  .hero-sticky {
    opacity: 1 !important;
    transform: none !important;
  }

  .button-primary {
    transform: none !important;
  }
}

@media print {
  @page {
    margin: 12mm;
  }

  :root {
    color-scheme: light;
  }

  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }

  html,
  body {
    width: auto !important;
    min-width: 0 !important;
    overflow: visible !important;
    background: var(--color-print-paper) !important;
    color: var(--color-print-ink) !important;
    scroll-behavior: auto !important;
  }

  body {
    font-size: 9.5pt;
    line-height: 1.5;
    print-color-adjust: economy;
    -webkit-print-color-adjust: economy;
  }

  main,
  .site-footer {
    position: static;
    z-index: auto;
  }

  .scroll-progress,
  .skip-link,
  .sr-only,
  .menu-toggle,
  .nav-menu,
  .wordmark-mark,
  .hero-actions,
  .hero-visual,
  .footer-meta > a {
    display: none !important;
  }

  .site-header {
    position: static !important;
    inset: auto !important;
    width: auto;
    min-height: 0;
    margin: 0 0 5mm;
    padding: 0 0 3mm;
    display: block;
    background: var(--color-print-paper);
    border: 0;
    border-block-end: var(--rule-hair) solid var(--color-print-rule);
    border-radius: var(--radius-none);
    box-shadow: none;
  }

  .wordmark {
    min-height: 0;
    padding: 0;
    color: var(--color-print-ink);
    font-size: 14pt;
    line-height: 1;
    text-decoration: none;
  }

  .hero,
  .about,
  .projects-intro {
    min-height: 0 !important;
  }

  .hero {
    padding: 0 0 7mm;
  }

  .hero-sticky,
  .about .section-heading {
    position: static !important;
    inset: auto !important;
  }

  .hero-sticky {
    width: auto;
    max-width: none;
    min-height: 0;
    margin: 0;
    display: block;
    opacity: 1 !important;
    transform: none !important;
  }

  .js-enabled .hero h1 span,
  .js-enabled .hero-support > * {
    opacity: 1 !important;
    transform: none !important;
  }

  .hero h1 {
    max-width: none;
    margin: 0;
    color: var(--color-print-ink);
    font-size: 24pt;
    line-height: 1.05;
  }

  html:lang(ja) .hero h1 {
    font-size: 21pt;
    line-height: 1.15;
  }

  .hero-support {
    max-width: none;
    margin-block-start: 4mm;
  }

  .hero-role,
  .hero-lede {
    color: var(--color-print-ink);
  }

  .hero-role {
    margin: 0;
    font-size: 9pt;
  }

  .hero-lede {
    max-width: 72ch;
    margin-block-start: 2mm;
    line-height: 1.5;
  }

  .section-shell,
  .projects .section-shell,
  .projects-intro,
  .footer-meta {
    width: auto;
    max-width: none;
    margin-inline: 0;
    padding-inline: 0;
  }

  .section-shell {
    padding-block: 7mm;
  }

  .section-heading h2,
  .projects-intro h2,
  .contact h2 {
    margin: 0;
    color: var(--color-print-ink);
    font-size: 18pt;
    line-height: 1.1;
  }

  .about {
    padding-block: 7mm;
    display: block;
    background: var(--color-print-paper);
    border-block-start: var(--rule-hair) solid var(--color-print-rule);
  }

  .about-copy {
    max-width: 80ch;
    padding-block: 0 !important;
    display: block;
  }

  .about-copy p {
    margin-block-start: 2.5mm;
    color: var(--color-print-ink);
  }

  .projects {
    background: var(--color-print-paper);
    color: var(--color-print-ink);
  }

  .projects-intro {
    padding-block: 7mm 4mm;
    border-block-start: var(--rule-hair) solid var(--color-print-rule);
  }

  .projects-intro h2,
  .projects-intro p {
    color: var(--color-print-ink);
  }

  .projects-intro p {
    max-width: 80ch;
    margin-block: 2mm 0;
  }

  .contact-links a[href^="http"]::after {
    content: " — " attr(href);
    display: block;
    margin-block-start: 0.75mm;
    color: var(--color-print-muted);
    font-family: var(--font-body);
    font-size: 7.5pt;
    font-weight: 400;
    line-height: 1.35;
    text-decoration: none;
    white-space: normal;
    overflow-wrap: anywhere;
  }

  .contact {
    padding-block: 7mm;
  }

  .contact-panel {
    padding: 7mm 0 0;
    display: block;
    background: var(--color-print-paper);
    color: var(--color-print-ink);
    border: 0;
    border-block-start: var(--rule-hair) solid var(--color-print-rule);
    border-radius: var(--radius-none);
    box-shadow: none;
    break-inside: avoid;
    page-break-inside: avoid;
  }

  .contact-panel h2,
  .contact-panel > div > p,
  .contact-links a {
    color: var(--color-print-ink);
  }

  .contact-panel > div > p {
    max-width: 80ch;
    margin-block-start: 2mm;
    font-size: 9.5pt;
  }

  .contact-links,
  .contact-links {
    margin-block-start: 4mm;
  }

  .contact-links a {
    min-height: 0;
    padding-block: 2.5mm;
    border-color: var(--color-print-rule);
    break-inside: avoid;
  }

  .contact-links span {
    font-size: 8pt;
  }

  .contact-links strong {
    font-size: 11pt;
    line-height: 1.2;
  }

  .site-footer {
    background: var(--color-print-paper);
    color: var(--color-print-ink);
    border-block-start-color: var(--color-print-rule);
  }

  .footer-meta {
    padding-block: 5mm 0;
    display: block;
    border-block-start: 0;
  }

  .footer-meta p {
    color: var(--color-print-ink);
    font-size: 8pt;
    line-height: 1.4;
  }

  .footer-meta p + p {
    margin-block-start: 2mm;
  }

  .footer-disclaimer {
    max-width: 80ch;
  }
}

/* ============================================================
 * 夜藍×白妙 theme layer — Alpine Ink · Decode
 * Additive palette + component layer for the 2026-08 redesign.
 * tokens.css stays single-palette (夜藍, the canonical dark set)
 * so the quality-contract OKLCH parser reads one token universe;
 * the 白妙 (light) and hidden 暁 palettes live here and override
 * the same slots via data-theme attributes or the OS scheme.
 * Slot re-interpretation in 白妙: --color-on-dark carries the
 * content color for nav/menu chrome, so it resolves to the light
 * theme's ink; --color-accent-2 becomes a light tinted fill so
 * that pairing stays >= 4.5:1. All three palettes were verified
 * against the same WCAG math the contrast contracts use.
 * ============================================================ */

:root {
  color-scheme: dark;
}

/* --color-paper-2 is the raised-surface slot: cards, panels, tool groups, the
 * mobile nav sheet. In 白妙 it was 93% against a 96% page, so every card sat
 * *below* its own ground — measured rgb(235,232,221) on rgb(244,242,233) —
 * and the warm 0.015 chroma at that lightness reads khaki, which is what made
 * the project grid look heavy in the light theme. Raised surfaces catch light,
 * so this now sits above the page and keeps a step clear of --color-surface
 * (98%) and --color-paper-3 (99%), which still caps the ladder at panel heads.
 * The dark palettes already run the ladder the right way and are untouched. */
:root[data-theme="shirotae"] {
  color-scheme: light;
  --color-paper: oklch(96% 0.012 95);
  --color-paper-2: oklch(97.5% 0.009 95);
  --color-paper-3: oklch(99% 0.005 95);
  --color-surface: oklch(98% 0.007 95);
  --color-surface-raised: oklch(91% 0.02 95);
  --color-ink: oklch(28% 0.03 260);
  --color-ink-2: oklch(38% 0.035 260);
  --color-muted: oklch(47% 0.04 260);
  --color-rule: oklch(72% 0.03 260);
  --color-accent: oklch(50% 0.19 265);
  --color-accent-2: oklch(88% 0.055 265);
  --color-accent-ink: oklch(98% 0.005 95);
  --color-on-dark: oklch(28% 0.03 260);
  --color-focus: oklch(45% 0.19 265);
  --color-ok: oklch(56% 0.13 156);
  --color-scene-hero: oklch(95% 0.014 95);
  --color-scene-about: oklch(94% 0.02 90);
  --color-scene-projects: oklch(94% 0.016 100);
  --color-scene-contact: oklch(93% 0.02 95);
  --service-lightness: 45%;
  --service-chroma: 0.15;
  --color-project-a: oklch(92% 0.02 95);
  --color-project-b: oklch(95% 0.014 95);
  --shadow-hard: 0 14px 34px -16px oklch(35% 0.03 260 / 0.4);
  --shadow-float: 0 0.75rem 2.5rem oklch(35% 0.03 260 / 0.22);
  --shadow-cinematic: 0 1.5rem 5rem oklch(35% 0.03 260 / 0.26);
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    color-scheme: light;
    --color-paper: oklch(96% 0.012 95);
    /* Mirrors the 白妙 raised-surface value above — see the note there. */
    --color-paper-2: oklch(97.5% 0.009 95);
    --color-paper-3: oklch(99% 0.005 95);
    --color-surface: oklch(98% 0.007 95);
    --color-surface-raised: oklch(91% 0.02 95);
    --color-ink: oklch(28% 0.03 260);
    --color-ink-2: oklch(38% 0.035 260);
    --color-muted: oklch(47% 0.04 260);
    --color-rule: oklch(72% 0.03 260);
    --color-accent: oklch(50% 0.19 265);
    --color-accent-2: oklch(88% 0.055 265);
    --color-accent-ink: oklch(98% 0.005 95);
    --color-on-dark: oklch(28% 0.03 260);
    --color-focus: oklch(45% 0.19 265);
    --color-ok: oklch(56% 0.13 156);
    --color-scene-hero: oklch(95% 0.014 95);
    --color-scene-about: oklch(94% 0.02 90);
    --color-scene-projects: oklch(94% 0.016 100);
    --color-scene-contact: oklch(93% 0.02 95);
  --service-lightness: 45%;
  --service-chroma: 0.15;
    --color-project-a: oklch(92% 0.02 95);
    --color-project-b: oklch(95% 0.014 95);
    --shadow-hard: 0 14px 34px -16px oklch(35% 0.03 260 / 0.4);
    --shadow-float: 0 0.75rem 2.5rem oklch(35% 0.03 260 / 0.22);
    --shadow-cinematic: 0 1.5rem 5rem oklch(35% 0.03 260 / 0.26);
  }
}

:root[data-theme="akatsuki"] {
  color-scheme: dark;
  --color-paper: oklch(18% 0.035 310);
  --color-paper-2: oklch(22% 0.04 308);
  --color-paper-3: oklch(28% 0.045 306);
  --color-surface: oklch(25% 0.042 307);
  --color-surface-raised: oklch(32% 0.05 305);
  --color-ink: oklch(94% 0.012 40);
  --color-ink-2: oklch(84% 0.025 310);
  --color-muted: oklch(73% 0.04 310);
  --color-rule: oklch(50% 0.05 310);
  --color-accent: oklch(76% 0.11 10);
  --color-accent-2: oklch(44% 0.09 5);
  --color-accent-ink: oklch(16% 0.03 310);
  --color-on-dark: oklch(95% 0.01 40);
  --color-focus: oklch(87% 0.07 15);
  --color-ok: oklch(78% 0.093 161);
  --color-scene-hero: oklch(20% 0.038 310);
  --color-scene-about: oklch(23% 0.042 305);
  --color-scene-projects: oklch(21% 0.045 300);
  --color-scene-contact: oklch(25% 0.042 308);
  --color-project-a: oklch(27% 0.048 300);
  --color-project-b: oklch(23% 0.04 306);
}

@media screen and (prefers-contrast: more) {
  :root[data-theme="shirotae"] {
    --color-ink-2: oklch(30% 0.03 260);
    --color-muted: oklch(36% 0.035 260);
    --color-rule: oklch(52% 0.04 260);
    --color-focus: oklch(35% 0.16 265);
  }

  :root[data-theme="akatsuki"] {
    --color-ink-2: oklch(91% 0.012 40);
    --color-muted: oklch(87% 0.02 310);
    --color-rule: oklch(71% 0.04 310);
    --color-focus: oklch(97% 0.02 20);
  }
}

@media screen and (prefers-contrast: more) and (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --color-ink-2: oklch(30% 0.03 260);
    --color-muted: oklch(36% 0.035 260);
    --color-rule: oklch(52% 0.04 260);
    --color-focus: oklch(35% 0.16 265);
  }
}

/* Type scale ported from preview-site/index.html. The previous scale was
 * tuned for a condensed Latin display face; the maru families are much
 * wider, so the prototype's smaller display sizes are required here — at
 * the old 5.5vw a Latin project title overflowed its column and broke
 * mid-word. The maru families also carry weight in font-weight rather
 * than the variable-font axis the old face used. */
/* The maru families are static faces: any leftover variable-font axis pin
 * would cap their weight once the webfonts are self-hosted. */
.hero h1 {
  font-size: clamp(2.7rem, 7.6vw, 6.2rem);
  font-weight: 900;
  font-variation-settings: normal;
  letter-spacing: 0.04em;
  line-height: 1.2;
}

html:lang(ja) .hero h1 {
  font-size: clamp(2.7rem, 7.6vw, 6.2rem);
  font-weight: 900;
  font-variation-settings: normal;
  /* Re-declared here because the lang-scoped rule in styles.css outranks
   * the bare class above and would otherwise keep the old tight tracking. */
  letter-spacing: 0.04em;
  line-height: 1.2;
}

.section-heading h2,
.projects-intro h2,
.contact h2 {
  font-size: clamp(2rem, 4.8vw, 3.5rem);
  font-weight: 900;
  font-variation-settings: normal;
  letter-spacing: 0.03em;
  line-height: 1.3;
}

/* Contact is the largest type on the prototype's page. */
.contact h2 {
  font-size: clamp(2.6rem, 7vw, 5.4rem);
}

.hero-lede,
.about-copy p,
.projects-intro > p {
  line-height: 1.85;
}

/* Kicker / eyebrow: mono, wide tracking, accent, with the prototype's
 * 34px leading rule. */
/* The lang-scoped body-font rule earlier in this file outranks a bare
 * class selector, so the kicker's mono stack is declared at the same
 * specificity for both routes. */
.hero-role,
html:lang(ja) .hero-role {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--color-accent);
  display: flex;
  align-items: center;
  gap: 0.8rem;
}

.hero-role::before {
  content: "";
  flex: 0 0 auto;
  width: 34px;
  height: 1px;
  background: var(--color-accent);
}

/* Primary CTA: pill, mono, with the arrow that advances on hover. The
 * lang-scoped form is required — a lang-scoped rule earlier in this file
 * sets the Japanese body face and outranks a bare class selector. */
.button-primary,
.text-link,
html:lang(ja) :where(.button-primary, .text-link) {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  padding: 0.95rem 1.7rem;
  border-radius: var(--radius-pill);
}

.button-primary::after {
  content: "→";
  margin-inline-start: 0.6rem;
  transition: transform var(--dur-short) var(--ease-out);
}

.button-primary:hover::after {
  transform: translateX(4px);
}

.hero-title-accent {
  color: var(--color-accent);
}

.button-primary {
  background: var(--color-accent);
  color: var(--color-accent-ink);
  box-shadow: 0 10px 30px -12px color-mix(in oklab, var(--color-accent) 55%, transparent);
}

.button-primary:hover {
  background: color-mix(in oklab, var(--color-accent) 82%, var(--color-ink));
  color: var(--color-accent-ink);
}

.wordmark {
  font-family: var(--font-mono);
  font-weight: 500;
  letter-spacing: 0.02em;
}

/* Decorative terminal caret lives in CSS (not the DOM) so the wordmark's
 * textContent stays exactly "himiyosh" for print, copy, and search. The
 * second content declaration supplies empty alternative text where the
 * alt syntax is supported. */
.wordmark::after {
  content: "▌";
  content: "▌" / "";
  color: var(--color-accent);
  animation: caret-blink 1.15s steps(1) infinite;
}

@keyframes caret-blink {
  50% {
    opacity: 0;
  }
}

/* Contour-line backdrop and the drifting amber blob, ported from the
 * prototype's .hero-bg. It lives in the fixed viewport stage — the
 * production site's decorative aria-hidden layer — so the texture is
 * painted once rather than per section. Stroke alphas match the
 * prototype's --topo / --topo-hl exactly. */
.site-backdrop-topo {
  position: absolute;
  inset: -10%;
  width: 120%;
  height: 120%;
  opacity: 0.85;
}

.site-backdrop-topo path {
  fill: none;
  stroke: color-mix(in oklab, var(--color-ink) 5.5%, transparent);
  stroke-width: 1.1;
}

.site-backdrop-topo path.topo-hl {
  stroke: color-mix(in oklab, var(--color-accent) 12%, transparent);
}

.site-backdrop-glow {
  position: absolute;
  inset-block-start: -10vw;
  inset-inline-end: -14vw;
  width: 56vw;
  height: 56vw;
  max-width: 760px;
  max-height: 760px;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    color-mix(in oklab, var(--color-accent) 14%, transparent),
    transparent 62%
  );
  filter: blur(10px);
  animation: topo-drift 26s ease-in-out infinite alternate;
}

@keyframes topo-drift {
  to {
    transform: translate(-5%, 7%) scale(1.08);
  }
}

/* 夜藍 ⇄ 白妙 mode toggle (3-second hold wakes the hidden 暁). */
.theme-toggle {
  /* The mobile menu is a grid, so a stretched toggle would render as a
   * full-width bar; the icon button keeps its square 44px target. */
  inline-size: 44px;
  justify-self: start;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-transparent);
  color: inherit;
  border: var(--rule-strong) solid var(--color-rule);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition:
    border-color var(--dur-micro) var(--ease-out),
    color var(--dur-micro) var(--ease-out),
    transform var(--dur-short) var(--ease-out);
}

.theme-toggle:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
  transform: rotate(20deg);
}

.theme-toggle[hidden] {
  display: none;
}

.theme-toggle-icon {
  width: 18px;
  height: 18px;
}

.theme-toggle-icon--sun {
  display: none;
}

:root[data-theme="shirotae"] .theme-toggle-icon--sun {
  display: block;
}

:root[data-theme="shirotae"] .theme-toggle-icon--moon {
  display: none;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .theme-toggle-icon--sun {
    display: block;
  }

  :root:not([data-theme]) .theme-toggle-icon--moon {
    display: none;
  }
}

.theme-toggle-charging {
  animation: theme-charge 3s linear forwards;
}

@keyframes theme-charge {
  to {
    border-color: var(--color-accent);
    color: var(--color-accent);
    box-shadow: 0 0 0 5px color-mix(in oklab, var(--color-accent) 30%, transparent);
  }
}

/* Theme announcements surface as a bottom toast; role=status carries
 * the same text to assistive technology. */
.theme-status {
  position: fixed;
  inset-block-end: var(--space-lg);
  inset-inline-start: 50%;
  z-index: var(--z-toast);
  max-inline-size: calc(100% - var(--space-lg));
  margin: 0;
  padding: var(--space-xs) var(--space-md);
  background: var(--color-paper-3);
  color: var(--color-ink);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-float);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  opacity: 0;
  /* The region stays in the accessibility tree between announcements, so
   * it must not intercept pointer events while it is invisible. */
  pointer-events: none;
  transform: translate(-50%, 140%);
  transition:
    transform var(--dur-long) var(--ease-out),
    opacity var(--dur-long) var(--ease-out);
}

.theme-status[hidden] {
  display: none;
}

.theme-status-on {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Mode-change crossfade (View Transitions API; script.js skips the
 * transition entirely under reduced motion). */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.45s;
}

@media (prefers-reduced-motion: reduce) {
  .wordmark::after {
    animation: none !important;
    opacity: 1 !important;
  }

  .site-backdrop-glow {
    animation: none !important;
  }

  .button-primary:hover::after {
    transform: none !important;
  }

  .theme-toggle {
    animation: none !important;
    transition: none !important;
  }

  .theme-toggle:hover {
    transform: none !important;
  }

  .theme-status {
    transition: none !important;
    transform: translate(-50%, 140%);
  }

  .theme-status-on {
    transform: translate(-50%, 0);
  }

  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none !important;
  }
}

@media print {
  .theme-toggle,
  .theme-status,
  .site-backdrop-topo,
  .site-backdrop-glow {
    display: none !important;
  }

  .button-primary::after {
    content: none !important;
  }

  .wordmark::after {
    content: none !important;
  }

  /* This theme layer is emitted after the main print block, so the screen
   * type scale and accent colours would otherwise follow the page onto
   * paper. Restate the ink-efficient print treatment here. */
  :root,
  :root[data-theme] {
    color-scheme: light;
  }

  .hero h1 {
    font-size: 24pt;
    line-height: 1.05;
  }

  html:lang(ja) .hero h1 {
    font-size: 21pt;
    line-height: 1.15;
  }

  .section-heading h2,
  .projects-intro h2,
  .contact h2 {
    font-size: 18pt;
    line-height: 1.1;
  }

  .hero-lede,
  .about-copy p,
  .projects-intro > p {
    line-height: 1.5;
  }

  .hero-role,
  html:lang(ja) .hero-role,
  .hero-title-accent {
    color: var(--color-print-ink);
  }

  .hero-role::before {
    display: none;
  }
}

/* ============================================================
 * Prototype sections and components — ported from
 * preview-site/index.html so the production page carries the
 * design's full composition, not only its palette and type.
 * ============================================================ */

/* Eyebrow: mono, tracked, accent, with the 34px leading rule. Shares its
 * treatment with the hero kicker; both need the lang-scoped form because a
 * lang-scoped body-font rule earlier in this file outranks a bare class. */
.section-eyebrow,
html:lang(ja) .section-eyebrow {
  margin: 0 0 1.3rem;
  display: flex;
  align-items: center;
  gap: 0.8rem;
  color: var(--color-accent);
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
}

.section-eyebrow::before {
  content: "";
  flex: 0 0 auto;
  width: 34px;
  height: 1px;
  background: var(--color-accent);
}

.hero-role,
html:lang(ja) .hero-role {
  margin-block-end: 1.6rem;
  font-weight: 500;
}

/* About facts. Deliberately not <p>, so the printed About narrative stays
 * exactly the three paragraphs its contract pins. */
.facts {
  margin-block-start: 2rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
}

.fact {
  padding: 0.5rem 1rem;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  color: var(--color-ink);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: 0.76rem;
  letter-spacing: 0.06em;
}

.fact b {
  color: var(--color-accent);
  font-weight: 500;
}

/* STACK / 道具箱 */
.toolbox {
  margin-block-start: 2.6rem;
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.4rem;
}

.tool-group {
  padding: 1.6rem;
  background: var(--color-paper-2);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius-md);
}

.tool-group h3,
html:lang(ja) .tool-group h3 {
  margin: 0 0 1.2rem;
  color: var(--color-accent);
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-weight: 500;
  letter-spacing: 0.24em;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.chips span,
html:lang(ja) .chips span {
  padding: 0.4rem 0.75rem;
  color: var(--color-ink);
  border: var(--rule-hair) solid
    color-mix(in oklab, var(--color-rule) 55%, transparent);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 0.74rem;
}

/* Hero photo: the prototype's tilted frame with corner ticks. */
.hero-visual {
  position: relative;
  overflow: visible;
  transform: rotate(1.4deg);
  transition: transform 0.6s var(--ease-out);
}

.hero-tick {
  position: absolute;
  z-index: var(--z-raised);
  width: 16px;
  height: 16px;
  border: var(--rule-hair) solid var(--color-accent);
}

.hero-tick-tl {
  inset-block-start: -6px;
  inset-inline-start: -6px;
  border-inline-end: 0;
  border-block-end: 0;
}

.hero-tick-tr {
  inset-block-start: -6px;
  inset-inline-end: -6px;
  border-inline-start: 0;
  border-block-end: 0;
}

.hero-tick-bl {
  inset-block-end: -6px;
  inset-inline-start: -6px;
  border-inline-end: 0;
  border-block-start: 0;
}

.hero-tick-br {
  inset-block-end: -6px;
  inset-inline-end: -6px;
  border-inline-start: 0;
  border-block-start: 0;
}

.hero-visual figcaption,
html:lang(ja) .hero-visual figcaption {
  margin-block-start: 1rem;
  color: var(--color-muted);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-align: end;
}

/* Scroll cue. Absolutely positioned so it cannot open a fourth implicit
 * row against the pinned hero grid areas. */
.hero {
  position: relative;
}

/* 稜線 — the WebGL ridgeline behind the hero.
 *
 * The canvas ships in the markup so it never causes a layout shift, but it
 * stays display:none until hero-3d.js has a live GL context and has drawn.
 * That single gate covers no-JS, no-WebGL, and driver failures at once: if
 * the class is never added, the hero is exactly the composition that
 * shipped before. It never takes pointer input and is never announced, so
 * keyboard and screen-reader paths are untouched. */
.hero-canvas {
  display: none;
}

.hero-3d-ready .hero-canvas {
  display: block;
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* Legibility mask. The headline column occupies the top of the hero, so
     the scene is held back there and only reaches full strength below the
     text, where it reads as a horizon rather than as noise behind copy.
     This is what keeps the hero at AA without dimming the whole scene. */
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(0, 0, 0, 0.1) 42%,
    rgba(0, 0, 0, 0.45) 68%,
    rgba(0, 0, 0, 1) 80%,
    rgba(0, 0, 0, 1) 96%,
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    transparent 0%,
    rgba(0, 0, 0, 0.1) 42%,
    rgba(0, 0, 0, 0.45) 68%,
    rgba(0, 0, 0, 1) 80%,
    rgba(0, 0, 0, 1) 96%,
    transparent 100%
  );
  /* The scene resolves its own entrance in the shader; this only covers
     the first compiled frame so the canvas never flashes in. */
  animation: hero-canvas-in var(--dur-long) var(--ease-out) both;
}

@keyframes hero-canvas-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* Hero content keeps its own stacking context above the scene. */
.hero-sticky {
  position: relative;
  z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
  .hero-3d-ready .hero-canvas {
    animation: none;
  }
}

.scroll-cue {
  position: absolute;
  inset-block-end: 1rem;
  inset-inline-start: 50%;
  margin: 0;
  translate: -50% 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  color: var(--color-muted);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.3em;
}

.scroll-cue::after {
  content: "";
  width: 1px;
  height: 44px;
  background: linear-gradient(var(--color-muted), transparent);
  transform-origin: top;
  animation: scroll-cue-drain 2s var(--ease-out) infinite;
}

@keyframes scroll-cue-drain {
  0% {
    transform: scaleY(0);
  }
  45% {
    transform: scaleY(1);
  }
  100% {
    transform: scaleY(1);
    opacity: 0;
  }
}

/* Marquee band under the hero. */
.hero-marquee {
  overflow: clip;
  padding-block: 1.05rem;
  background: var(--color-paper-2);
  border-block: var(--rule-hair) solid
    color-mix(in oklab, var(--color-rule) 45%, transparent);
}

.hero-marquee-track {
  width: max-content;
  display: flex;
  animation: hero-marquee-slide var(--dur-marquee) linear infinite;
}

.hero-marquee-set {
  display: inline-flex;
  align-items: center;
  gap: 3rem;
  padding-inline-end: 3rem;
  color: var(--color-muted);
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  white-space: nowrap;
}

.hero-marquee-set i {
  color: var(--color-accent);
  font-style: normal;
}

@keyframes hero-marquee-slide {
  to {
    transform: translateX(-50%);
  }
}

.footer-meta a,
html:lang(ja) .footer-meta a {
  font-family: var(--font-mono);
  font-size: 0.76rem;
  letter-spacing: 0.14em;
}

@media (min-width: 48rem) {
  .toolbox {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .scroll-cue {
    inset-block-end: 2rem;
  }
}

/* --- Independent-review fixes -------------------------------------- */

/* Hover affordances must not stick on touch: these five were the only
 * ungated :hover rules in the file. */
@media (hover: hover) and (pointer: fine) {
  .button-primary:hover {
    background: color-mix(in oklab, var(--color-accent) 82%, var(--color-ink));
    color: var(--color-accent-ink);
  }

  .button-primary:hover::after {
    transform: translateX(4px);
  }

  .theme-toggle:hover {
    border-color: var(--color-accent);
    color: var(--color-accent);
    transform: rotate(20deg);
  }

  .hero-visual:hover {
    transform: rotate(0deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-cue::after,
  .hero-marquee-track {
    animation: none !important;
  }

  .hero-visual,
  .hero-visual:hover {
    transform: none !important;
    transition: none !important;
  }
}

@media print {
  .scroll-cue,
  .hero-marquee,
  .hero-tick {
    display: none !important;
  }

  .toolbox {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .tool-group {
    background: var(--color-print-paper);
    border-color: var(--color-print-rule);
    break-inside: avoid;
  }

  .chips span,
  .fact {
    color: var(--color-print-ink);
    border-color: var(--color-print-rule);
  }

  .fact b,
  .tool-group h3 {
    color: var(--color-print-ink);
  }

  .hero-visual {
    transform: none !important;
  }
}

/* ============================================================
 * Prototype composition — the page structure now IS
 * preview-site/index.html, so these rules carry its layout:
 * the fixed backdrop, featured cards, the Network+ panel,
 * the sticky About grid, the centred contact, and the footer.
 * ============================================================ */

.site-backdrop {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: clip;
  background: var(--color-paper);
  pointer-events: none;
}

/* Sections breathe on the prototype's rhythm and width. The prototype's
 * .container is width:min(1160px,92vw) with zero inline padding — the
 * 92vw clamp itself is the gutter — so the legacy --page-gutter padding
 * must not narrow the shell. */
.section-shell {
  max-width: min(1160px, 92vw);
  padding-block: clamp(5rem, 12vh, 8.5rem);
  padding-inline: 0;
}

.hero {
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-block: clamp(7rem, 14vh, 10rem) clamp(3rem, 8vh, 5rem);
}

.hero-sticky {
  max-width: min(1160px, 92vw);
}

/* Works section header: heading left, lede bottom-right. */
.section-head {
  margin-block-end: 2.8rem;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem;
}

.section-lede {
  max-width: 30em;
  margin: 0;
  color: var(--color-muted);
  font-size: 0.95rem;
}

/* Featured cards. */
.featured {
  margin-block-end: 4rem;
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.4rem;
}

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  /* A grid item defaults to min-width: auto, so the card refuses to shrink
   * below its content's minimum and pushes past its track. At 320px with
   * WCAG 1.4.12 letter spacing applied that pushed the card 47px wider than
   * the shell, and `overflow: clip` below silently cut the text off. */
  min-width: 0;
  overflow: clip;
  background: var(--color-paper-2);
  border: var(--rule-hair) solid
    color-mix(in oklab, var(--color-rule) 55%, transparent);
  border-radius: var(--radius-md);
  transition:
    transform var(--dur-long) var(--ease-out),
    border-color var(--dur-long) var(--ease-out),
    box-shadow var(--dur-long) var(--ease-out);
}

.card .thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: clip;
  background: var(--color-paper-3);
}

.card .thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(0.9);
  transition:
    transform 0.8s var(--ease-out),
    filter 0.8s var(--ease-out);
}

.card .thumb .badge {
  position: absolute;
  inset-block-start: 1rem;
  inset-inline-start: 1rem;
  z-index: var(--z-raised);
  padding: 0.35rem 0.85rem;
  color: var(--color-ink);
  background: color-mix(in oklab, var(--color-paper) 78%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: var(--rule-hair) solid
    color-mix(in oklab, var(--color-rule) 55%, transparent);
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
}

.card .body {
  flex: 1;
  padding: 1.6rem 1.6rem 1.7rem;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.card h3,
html:lang(ja) .card h3 {
  margin: 0;
  font-family: var(--font-body);
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1.4;
}

.card .body > p {
  flex: 1;
  margin: 0;
  color: var(--color-muted);
  font-size: 0.92rem;
  line-height: 1.85;
  /* Safety net for the one description that carries an unbreakable
   * Prompt→Chain→… run: with reader letter spacing it cannot wrap and
   * overhangs the 320px viewport on its own. */
  overflow-wrap: break-word;
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
}

.tags span {
  padding: 0.28rem 0.6rem;
  color: var(--color-muted);
  border: var(--rule-hair) solid
    color-mix(in oklab, var(--color-rule) 45%, transparent);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.05em;
}

.card .links {
  display: flex;
  gap: 1.4rem;
  padding-block-start: 0.4rem;
}

.link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  color: var(--color-accent);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-decoration: none;
}

.link::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-end: -3px;
  width: 0%;
  height: 1px;
  background: var(--color-accent);
  transition: width var(--dur-short) var(--ease-out);
}

.link .ext {
  font-size: 0.72rem;
  translate: 0 -1px;
}

/* Network+ homage panel. */
.panel {
  overflow: clip;
  background: var(--color-paper-2);
  border: var(--rule-hair) solid
    color-mix(in oklab, var(--color-rule) 55%, transparent);
  border-radius: var(--radius-md);
}

.panel-head {
  padding: 0.85rem 1.4rem;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  color: var(--color-muted);
  background: var(--color-paper-3);
  border-block-end: var(--rule-hair) solid
    color-mix(in oklab, var(--color-rule) 55%, transparent);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
}

.panel-head .dots {
  display: flex;
  gap: 6px;
}

.panel-head .dots i {
  display: block;
  width: 9px;
  height: 9px;
  border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--color-rule) 55%, transparent);
}

.panel-head .dots i:first-child {
  background: var(--color-accent);
}

.row {
  position: relative;
  display: grid;
  grid-template-columns: 5.2rem 1.15fr 0.8fr 1.2fr auto;
  align-items: center;
  gap: 1.2rem;
  padding: 1.15rem 1.4rem;
  border-block-start: var(--rule-hair) solid
    color-mix(in oklab, var(--color-rule) 45%, transparent);
  transition: background-color var(--dur-micro) var(--ease-out);
}

.panel .row:first-of-type {
  border-block-start: 0;
}

.row .status {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--color-ok);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
}

.row .status::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: var(--radius-pill);
  background: var(--color-ok);
  box-shadow: 0 0 8px color-mix(in oklab, var(--color-ok) 70%, transparent);
}

.row .status.src {
  color: var(--color-muted);
}

.row .status.src::before {
  background: var(--color-muted);
  box-shadow: none;
}

/* The name is the row's one link; its pseudo-element stretches the click
 * surface across the whole row, exactly like the prototype's row anchor. */
.row-link {
  min-width: 0;
  color: inherit;
  text-decoration: none;
  outline-offset: -3px;
}

.row-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.row .name {
  display: block;
  overflow: hidden;
  font-size: 1.02rem;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: color var(--dur-micro) var(--ease-out);
}

.row .type,
.row .stack {
  overflow: hidden;
  color: var(--color-muted);
  font-family: var(--font-mono);
  letter-spacing: 0.06em;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row .type {
  font-size: 0.74rem;
}

.row .stack {
  font-size: 0.72rem;
  opacity: 0.75;
}

.row .go {
  color: var(--color-accent);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  white-space: nowrap;
  transition: transform var(--dur-micro) var(--ease-out);
}

/* Sticky About grid. */
.about-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
}

.about-copy p {
  max-width: 36em;
  color: var(--color-muted);
}

.about-copy p strong {
  color: var(--color-ink);
  font-weight: 700;
}

/* Centred contact. */
.contact {
  text-align: center;
}

/* The panel's row gap is the only separation between the lede and the tiles:
 * the heading block and the list are its two grid children, and every margin
 * the lede could carry is zeroed by `.contact-panel > div > p` in styles.css
 * (0-1-2, so a `.contact-lede` margin never lands). Keep the space here. */
.contact-panel {
  display: grid;
  gap: 2rem;
  background: none;
  border: 0;
  box-shadow: none;
}

.section-eyebrow--center {
  justify-content: center;
}

/* The contact list is a stack of equal rows, each closed by its own rule.
 *
 * `justify-items: center` used to sit here, which made every row shrink to
 * its own content — a 1160px container held a 609px row above a 161px one,
 * so each row's bottom rule drew at a different length and the section read
 * as broken. Rows stretch now and the centring happens inside each row, so
 * the rules line up whatever a row contains. The gap goes with it: rules
 * that touch their neighbours read as one list, not as floating fragments. */
.contact-links {
  display: grid;
  gap: 0;
  justify-items: stretch;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Footer. */
.foot {
  max-width: min(1160px, 92vw);
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.5rem;
}

.foot-left,
.foot-right {
  display: grid;
  gap: 0.9rem;
}

.foot-right {
  justify-items: end;
  text-align: end;
}

.footer-copyright,
.footer-disclaimer {
  max-width: 34em;
  margin: 0;
  color: var(--color-muted);
  font-size: 0.78rem;
}

/* The disclaimer is a single 40-character sentence and reads as one line.
 * The shared 34em cap above broke it at 424px, two lines short of its own
 * length, while the footer had 1160px to give. It still wraps below roughly
 * 540px of footer width — a viewport that narrow cannot hold the sentence
 * without shrinking it past legibility, and wrapping beats overflowing. */
.footer-disclaimer {
  max-width: 42em;
}

.footer-statement {
  margin: 0;
  color: var(--color-muted);
  font-size: 0.78rem;
}

.top-link,
html:lang(ja) .top-link {
  color: var(--color-muted);
  font-family: var(--font-mono);
  font-size: 0.76rem;
  letter-spacing: 0.14em;
  text-decoration: none;
  transition: color var(--dur-micro) var(--ease-out);
}

/* Hover states, gated away from touch. */
@media (hover: hover) and (pointer: fine) {
  .card:hover {
    transform: translateY(-6px);
    border-color: color-mix(in oklab, var(--color-accent) 45%, transparent);
    box-shadow: 0 26px 60px -30px oklch(6% 0.02 253 / 0.8);
  }

  .card:hover .thumb img {
    transform: scale(1.045);
    filter: saturate(1.05);
  }

  .link:hover::after {
    width: 100%;
  }

  .row:hover,
  .row:focus-within {
    background: color-mix(in oklab, var(--color-accent) 5%, transparent);
  }

  .row:hover .name {
    color: var(--color-accent);
  }

  .row:hover .go {
    transform: translateX(4px);
  }

  .top-link:hover {
    color: var(--color-accent);
  }
}

/* Desktop widths. */
@media (min-width: 60rem) {
  .featured {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* A card ending an odd-sized category group has no partner and would sit
     in a half-empty row; spanning closes the grid. The generator marks it,
     because :nth-child parity would count the group headings sharing this
     grid and land on the wrong card. */
  .featured .card.is-alone {
    grid-column: 1 / -1;
  }

  .featured .card.is-alone .thumb {
    aspect-ratio: 21 / 9;
  }

  .about-grid {
    grid-template-columns: 1fr 1fr;
  }

  .about-sticky {
    position: sticky;
    inset-block-start: 7rem;
  }
}

/* The prototype's responsive row collapse. */
@media (max-width: 60rem) {
  .row {
    grid-template-columns: 4.6rem 1fr auto;
    gap: 0.9rem;
  }

  .row .type,
  .row .stack {
    display: none;
  }
}

@media (max-width: 35rem) {
  .row {
    grid-template-columns: 1fr auto;
    padding: 1rem 1.1rem;
  }

  .row .status {
    display: none;
  }

  /* One track is not enough for the longest name once a reader applies the
     WCAG 1.4.12 text-spacing override, and the ellipsis would then eat the
     end of a project name — content the success criterion requires to
     survive. Wrapping costs a line and keeps the whole name. */
  .row .name {
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
  }
}

@media (prefers-reduced-motion: reduce) {
  .card,
  .card .thumb img,
  .row,
  .row .go,
  .row .name,
  .link::after {
    transition: none !important;
  }

  .card:hover {
    transform: none !important;
  }

  .card:hover .thumb img {
    transform: none !important;
  }

  .row:hover .go {
    transform: none !important;
  }
}

@media print {
  .site-backdrop,
  .panel-head .dots {
    display: none !important;
  }

  .card,
  .panel,
  .row {
    background: var(--color-print-paper);
    border-color: var(--color-print-rule);
    break-inside: avoid;
  }

  .card .body > p,
  .row .type,
  .row .stack,
  .section-lede,
  .about-copy p {
    color: var(--color-print-ink);
  }
}

/* Reveal priming: applied by script.js only while motion is allowed and
 * IntersectionObserver exists, and cleared by a timeout backstop, so the
 * resting state is always fully visible. */
@media (prefers-reduced-motion: no-preference) {
  .js-enabled .is-priming {
    opacity: 0;
    transform: translateY(26px);
  }

  .js-enabled .card,
  .js-enabled .panel,
  .js-enabled .tool-group,
  .js-enabled .about-grid > *,
  .js-enabled .section-head,
  .js-enabled .hero-visual,
  .js-enabled .contact-panel > * {
    transition:
      opacity 0.8s var(--ease-out),
      transform 0.8s var(--ease-out);
  }
}

/* ============================================================
 * Premium motion layer — page loading, cursor presence
 * Driven by motion.js. Both elements are injected at runtime, so nothing
 * here has a resting state to degrade: no JavaScript means no rail and no
 * ring, and the page is unchanged.
 * ============================================================ */

/* Page loading: a top rail, never a curtain. Content stays readable
 * underneath from the first paint. */
.page-load-rail {
  position: fixed;
  inset-block-start: 0;
  inset-inline-start: 0;
  z-index: var(--z-progress);
  block-size: 2px;
  inline-size: 0;
  background: linear-gradient(
    90deg,
    color-mix(in oklab, var(--color-accent) 40%, transparent),
    var(--color-accent)
  );
  pointer-events: none;
  /* Eases toward 88% and waits there: the rail must never claim to be
     finished before `load` actually fires. */
  animation: page-load-advance 2.4s var(--ease-out) forwards;
}

.page-load-rail.is-complete {
  inline-size: 100%;
  opacity: 0;
  animation: none;
  transition:
    inline-size 220ms var(--ease-out),
    opacity 240ms var(--ease-out) 180ms;
}

@keyframes page-load-advance {
  from {
    inline-size: 0;
  }
  to {
    inline-size: 88%;
  }
}

/* Cursor presence: a trailing ring that widens over interactive targets.
 * The native cursor is never hidden — this rides alongside it. */
.cursor-ring {
  position: fixed;
  inset-block-start: 0;
  inset-inline-start: 0;
  z-index: var(--z-tooltip);
  inline-size: 26px;
  block-size: 26px;
  margin: 0;
  border: 1px solid color-mix(in oklab, var(--color-accent) 70%, transparent);
  border-radius: var(--radius-pill);
  opacity: 0;
  pointer-events: none;
  /* The `translate` property is written by motion.js; `transform` centres
     the ring on that point, since the two compose in that order. */
  transform: translate(-50%, -50%);
  transition:
    opacity var(--dur-short) var(--ease-out),
    inline-size var(--dur-short) var(--ease-out),
    block-size var(--dur-short) var(--ease-out),
    background-color var(--dur-short) var(--ease-out);
}

.cursor-ring.is-visible {
  opacity: 0.55;
}

.cursor-ring.is-over-target {
  inline-size: 44px;
  block-size: 44px;
  background: color-mix(in oklab, var(--color-accent) 12%, transparent);
  opacity: 0.85;
}

/* Reduced motion removes both outright rather than shortening them: a rail
 * and a trailing ring are pure decoration with no content to preserve. */
@media (prefers-reduced-motion: reduce) {
  .page-load-rail,
  .cursor-ring {
    display: none;
  }
}

/* Forced colours: a decorative ring must not spend a system colour slot. */
@media (forced-colors: active) {
  .cursor-ring {
    display: none;
  }
}

/* ============================================================
 * Contact list rows
 * Each row is one profile: service name on the left, handle on the right,
 * with the external-link tick trailing the handle. The two-column rhythm
 * is what makes the list scannable by service rather than by URL.
 * ============================================================ */
/* Tiles, not a bare list. Each profile gets a mark, its service name, and
 * the handle, so the section reads as four destinations rather than four
 * lines of text.
 *
 * The marks are this site's own line drawings — a branch, a book, an
 * article, a nib — in the same hairline language as the topographic
 * background. They are deliberately not the services' brand logos: an
 * approximated trademark is worse than an honest glyph, and drawing one
 * from memory is exactly how that happens. */
/* The four profiles stay side by side. A tile with its mark beside the text
 * needed ~218px to avoid clipping "@studio344", which forced a 2x2 break
 * as soon as the viewport dipped under ~985px. Stacking the mark above the
 * text drops that to ~128px, so four fit from the layout's 48rem tablet
 * width up, and two — never one — below it. */
.contact-links {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-xs);
}

@media (min-width: 48rem) {
  .contact-links {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.contact-links a {
  position: relative;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 0.35rem;
  min-height: 132px;
  padding: var(--space-sm) var(--space-2xs);
  background: var(--color-surface);
  border: var(--rule-hair) solid var(--color-rule);
  border-radius: var(--radius-md);
  text-align: center;
}

.contact-mark {
  display: grid;
  place-items: center;
  inline-size: 46px;
  block-size: 46px;
  margin-block-end: 0.15rem;
  color: var(--service-accent, var(--color-accent));
  border: var(--rule-hair) solid
    color-mix(in oklab, var(--service-accent, var(--color-accent)) 45%, transparent);
  border-radius: var(--radius-sm);
  transition:
    background-color var(--dur-short) var(--ease-out),
    border-color var(--dur-short) var(--ease-out);
}

.contact-mark svg {
  inline-size: 23px;
  block-size: 23px;
}

/* Each service takes a different hue at one shared lightness and chroma, so
 * the four read as one family rather than four borrowed brand palettes. */
.contact-links a[data-service="github"] {
  --service-accent: oklch(var(--service-lightness) var(--service-chroma) 250);
}

.contact-links a[data-service="zenn"] {
  --service-accent: oklch(var(--service-lightness) var(--service-chroma) 220);
}

.contact-links a[data-service="qiita"] {
  --service-accent: oklch(var(--service-lightness) var(--service-chroma) 150);
}

.contact-links a[data-service="note"] {
  --service-accent: oklch(var(--service-lightness) var(--service-chroma) 190);
}

.contact-service {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.16em;
  color: var(--color-muted);
}

.contact-handle {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.01em;
  overflow-wrap: anywhere;
}

/* The tick sits in the corner so it never competes for the tile's width. */
.contact-links a .ext {
  position: absolute;
  inset-block-start: var(--space-2xs);
  inset-inline-end: var(--space-2xs);
  color: var(--color-muted);
  font-size: var(--text-xs);
}

@media (hover: hover) and (pointer: fine) {
  .contact-links a:hover {
    border-color: color-mix(in oklab, var(--service-accent) 55%, transparent);
  }

  .contact-links a:hover .contact-mark {
    background: color-mix(in oklab, var(--service-accent) 14%, transparent);
    border-color: var(--service-accent);
  }

  .contact-links a:hover .contact-handle,
  .contact-links a:hover .ext {
    color: var(--service-accent);
  }
}


/* ============================================================
 * Featured category groups
 * Each heading labels the pair of cards beneath it. It spans the grid so a
 * group always starts on its own row, which is what makes the grouping
 * visible rather than implied by order alone.
 * ============================================================ */
.featured-group {
  grid-column: 1 / -1;
  margin: 0;
  padding-block-end: var(--space-2xs);
  border-block-end: var(--rule-hair) solid var(--color-rule);
  color: var(--color-muted);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* Groups after the first need air above them so the heading reads as
   belonging to the cards below, not the cards above. */
.featured-group:not(:first-child) {
  margin-block-start: var(--space-xl);
}
