/* Space Grotesk/Inter/Noto Sans JP used to be pulled in here via @import --
   a render-blocking pattern (CSS @import forces this whole stylesheet's
   parsing to stall on a second network round-trip, instead of loading in
   parallel like a <link> tag does). Consolidated into the single Google
   Fonts <link> in _includes/head_meta.php instead. */
:root {
  --bg: #000000;
  --bg-2: #0a0a14;
  --ink: #f4f2ff;
  --ink-dim: rgba(244, 242, 255, 0.62);
  --ink-faint: rgba(244, 242, 255, 0.36);
  --accent: #3d8bff;
  --accent-2: #1e4fd6;
  --accent-3: #52e6ff;
  --glass: rgba(255, 255, 255, 0.045);
  --glass-border: rgba(255, 255, 255, 0.09);
  --glass-hover: rgba(255, 255, 255, 0.08);
  --radius: 20px;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

* { box-sizing: border-box; }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Structural placeholders for blocking out the page before real copy goes in.
   Thin outline, centered label, full viewport tall so scroll/parallax timing
   can be judged at real scale. Delete once each block gets real content.
   svh, not vh: vh on iOS Safari is pinned to the toolbar-collapsed (tallest)
   viewport, which fights the real usable height while the address bar is
   animating collapsed/expanded. */
.debug-block {
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 40px 24px;
  color: rgba(255, 255, 255, 0.42);
  font-size: 13px;
  letter-spacing: 0.06em;
  line-height: 1.9;
}
/* Small tracked-tight English label sitting above a big JP title. */
.title-eyebrow {
  display: block;
  font-family: 'Space Grotesk', sans-serif;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: #ffffff;
  opacity: 0.8;
  text-transform: uppercase;
  margin-bottom: 16px;
}

/* Section heading, same size as .reports-title, sitting above the link
   list. */
.spiral-heading {
  margin: 0;
  font-family: 'Space Grotesk', 'Noto Sans JP', sans-serif;
  font-size: clamp(48px, 15vw, 132px);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-shadow: 0 0 16px rgba(0, 0, 0, 0.8);
  /* Same fix as .reports-title/.debug-block__label: at this size a wide
     viewport can still make the text's own rendered width exceed its
     container, and default CJK line-breaking (a break allowed between any
     two characters) then splits "活動内容" mid-word. */
  white-space: nowrap;
}

/* Small centred Luminelabo mark for the still-unbuilt spiral block. The SVG
   source is already solid white (fill:#fff), so unlike the old PNG
   placeholder this needs no recolour filter. */
.spiral-logo {
  width: clamp(64px, 16vw, 110px);
  height: auto;
  opacity: 0.85;
}

.debug-block__label {
  display: inline-block;
  opacity: 0;
  /* This element alone (not <main>, which is what broke real-device scroll
     earlier today) is a fine will-change candidate: small, bounded, and it's
     what's actually being animated every frame. Without it, Safari repaints
     the blurred text-shadow from scratch on every transform change instead
     of just moving a pre-rendered layer, which is what read as jerky. */
  will-change: transform, opacity;
}

/* Sample of the "entrance -> hold -> exit" choreography for one block, at
   real scale instead of a debug caption. Big, bold, and quiet everywhere
   else -- the Apple product-page trick is that only one thing is allowed to
   be loud on screen at a time. */
.debug-block--poem {
  border: none;
}
.debug-block--poem .debug-block__label {
  font-family: 'Space Grotesk', 'Noto Sans JP', sans-serif;
  font-size: clamp(48px, 15vw, 132px);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-shadow: 0 0 16px rgba(0, 0, 0, 0.8);
  /* Same fix as .reports-title: at this font-size a wide viewport can still
     make a line's rendered width exceed its container, and default CJK
     line-breaking (a break allowed between any two characters) then splits
     "意識を探す" mid-word on top of the intended <br>. The only line breaks
     here should be the explicit <br>s in the markup. */
  white-space: nowrap;
}

/* Same trick as the hero title: take the maru out of flow so it doesn't
   count toward the centered line's width, and hang it off the right side
   of the last glyph instead. */
.debug-block__line2 {
  position: relative;
  display: inline-block;
}
.debug-block__dot {
  position: absolute;
  left: 100%;
  top: 0;
}

.debug-block__stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(28px, 6vw, 56px);
}

/* Each line fades/drifts in on its own, staggered by blocks.js -- not a
   single paragraph fading as one block. Opacity/transform only, and each
   line carries its own will-change (small, bounded elements, same reasoning
   as the title label). The bright nebula core sits roughly under the title,
   so the body is pushed further down (extra margin-top) and weighted up
   (400 -> 600) to survive reading over it instead of a blur plate, which is
   exactly the kind of thing today's scroll-jank turned out to be about. */
.debug-block__body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 560px;
  margin: 18vh 0 0;
  font-size: clamp(20px, 5vw, 26px);
  font-weight: 600;
  line-height: 1.9;
  color: var(--ink);
}
.debug-block__line {
  display: block;
  opacity: 0;
  will-change: transform, opacity;
}

/* ---------- reports block: "decoding transmission" -------------------------
   A giant blurred echo of the title sits behind everything and drifts at its
   own (slow) parallax speed -- real depth separation, not just a fade. The
   title itself launches in from off-screen left/right per character with a
   spring landing. The body decodes like a signal locking on: each glyph
   flickers through random katakana/symbols before resolving to the real
   character, timed left-to-right across the paragraph. */
.debug-block--reports {
  border: none;
}
.debug-block--reports .debug-block__stack {
  position: relative;
  perspective: 1200px;
}
/* Eyebrow + echo + title grouped as one flex item so the eyebrow's own
   margin-bottom governs the gap to the title (same rule as poem/spiral),
   instead of the stack's larger inter-item gap stacking on top of it.
   No position:relative here -- .reports-echo's percentage offsets must
   keep resolving against .debug-block__stack (already positioned above),
   not shrink down to just this wrapper's own box. */
.reports-echo {
  position: absolute;
  top: 46%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-family: 'Space Grotesk', 'Noto Sans JP', sans-serif;
  font-weight: 800;
  font-size: clamp(120px, 32vw, 380px);
  line-height: 1.02;
  letter-spacing: -0.02em;
  color: var(--accent);
  white-space: nowrap;
  text-align: center;
  opacity: 0;
  filter: blur(4px);
  pointer-events: none;
  z-index: 0;
  will-change: transform, opacity;
}
.reports-title {
  position: relative;
  z-index: 1;
  /* Prevents the browser from ALSO breaking mid-word (default CJK
     line-breaking allows a break between any two characters) when this
     span's natural width exceeds its container -- the intended line
     breaks are the explicit <br> in the markup only, never an extra one
     splitting "レポート" into "レポー"/"ト". */
  white-space: nowrap;
  font-family: 'Space Grotesk', 'Noto Sans JP', sans-serif;
  font-size: clamp(48px, 15vw, 132px);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-shadow: 0 0 16px rgba(0, 0, 0, 0.8);
  will-change: transform;
}
/* Per-character entrance: each glyph launches in from off-screen (alternating
   left/right) with depth/blur/rotation, then overshoots past its landing
   point and springs back -- opacity starts at 0 inline (set at split-time)
   so there's no flash of solid text before the first animation frame runs. */
.reports-title .char {
  display: inline-block;
  transform-origin: 50% 100%;
  will-change: transform, opacity, filter;
}
/* Body now matches the poem block's size and per-line fade/slide-in motion
   (see .debug-block__body / .debug-block__line and blocks.js) instead of
   its own per-glyph flicker -- same font-size, same reveal feel across
   both blocks. */
.reports-body {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: 700px;
  margin: 14vh 0 0;
  font-size: clamp(20px, 5vw, 26px);
  font-weight: 600;
  line-height: 1.9;
  color: var(--ink);
  will-change: transform;
}
.reports-line {
  display: block;
  opacity: 0;
  will-change: transform, opacity;
}
.reports-cta {
  position: relative;
  z-index: 1;
  display: inline-block;
  margin-top: 12vh;
  padding: 11px 22px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.02em;
  opacity: 0;
  will-change: transform, opacity, box-shadow;
}
/* Shockwave ring: a single expanding/fading ring fired once the CTA lands,
   driven by blocks.js toggling --ring-t from 0 to 1. */
.reports-cta::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 999px;
  border: 1.5px solid rgba(120, 170, 255, 0.9);
  transform: scale(calc(1 + var(--ring-t, 0) * 1.6));
  opacity: calc(1 - var(--ring-t, 0));
  pointer-events: none;
}

/* ---------- link spiral: 6 boxes at the hub label's own position, each
   spinning a full 360deg (eased) into place as the section scrolls into
   view -- see blocks.js. They land at exactly this resting layout; the
   animation only ever touches opacity/transform on top of it. */
.debug-block--spiral {
  border: none;
  flex-direction: column;
  gap: 32px;
}
.spiral-lead {
  max-width: 760px;
  margin: 0;
  font-size: clamp(12px, 2.6vw, 15px);
  font-weight: 600;
  line-height: 1.9;
  color: var(--ink);
  text-align: center;
}
.spiral-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  width: 100%;
  perspective: 900px;
}
.spiral-step {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: min(92vw, 460px);
  padding: 18px 24px;
  border-radius: 10px;
  /* Clearly mizuiro (water blue) at rest, not a washed-out grey -- higher
     tint opacity than the first pass, plus a visible inner glow instead of
     a hard edge, so it never reads as a stiff flat plate even at rest. */
  background: rgba(126, 200, 235, 0.10);
  border: 1px solid rgba(150, 215, 240, 0.30);
  box-shadow: inset 0 0 26px rgba(150, 215, 240, 0.12);
  color: var(--ink);
  text-decoration: none;
  opacity: 0;
  will-change: transform, opacity, box-shadow;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.03em;
  /* Landing flash (see blocks.js): --flash charges up gently while the
     border is still resolving, then spikes to 1 the instant the step fully
     lands and decays back to 0 on its own. Kept to the rim on purpose --
     a short inset blur so it lights the edge and fades out well before
     reaching the centre, plus the border itself and an outward bleed.
     The fill barely brightens at all now, so it doesn't read as the whole
     box blowing out white. */
  background: color-mix(in srgb, rgba(126, 200, 235, 0.10) calc(100% - var(--flash, 0) * 100%), rgba(180, 225, 255, 0.30) calc(var(--flash, 0) * 100%));
  box-shadow:
    inset 0 0 26px rgba(150, 215, 240, 0.12),
    inset 0 0 calc(6px + var(--flash, 0) * 10px) rgba(220, 245, 255, calc(var(--flash, 0) * 0.95)),
    0 0 calc(var(--flash, 0) * 34px) calc(var(--flash, 0) * 5px) rgba(160, 220, 255, calc(var(--flash, 0) * 0.75));
  border-color: color-mix(in srgb, rgba(150, 215, 240, 0.30) calc(100% - var(--flash, 0) * 100%), rgba(255, 255, 255, 0.95) calc(var(--flash, 0) * 100%));
}
/* Particle-assemble canvas (see blocks.js): dust converges into the box's
   own outline as it lands, then fades once the solid box beneath has taken
   over. A lightweight 2D canvas per button -- independent of the WebGL
   scene, so it's cheap to remove/tune without touching scene.js. */
.spiral-step__particles {
  position: absolute;
  /* left/top/width/height are set inline by blocks.js -- the canvas is
     deliberately larger than the box itself, so it must not be pinned to
     inset:0. */
  pointer-events: none;
}
.spiral-step__label {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  /* The flash is a rim/edge effect now (see .spiral-step above), and the
     label sits away from the edge -- so no more full colour inversion,
     which read as the centre going dark too. A soft glow is enough to tie
     it to the same moment. */
  color: var(--ink);
  text-shadow: 0 0 calc(var(--flash, 0) * 14px) rgba(220, 245, 255, calc(var(--flash, 0) * 0.9));
}
.spiral-step__label i {
  font-size: 1.15em;
  color: var(--accent-3);
}

/* ---------- fixed CTA: fades up from nothing (opacity 0, sunk below the
   viewport) -- transparent at first, gaining both position and colour as it
   rises, rather than a fully-opaque pill just sliding into place. Retreat
   is the same motion in reverse: fades back to transparent as it sinks.
   See blocks.js for when each of the two (blue/red) is shown. */
.fixed-cta {
  position: fixed;
  left: 50%;
  /* Recent iOS Safari resizes the *visual* viewport (not the layout one) as
     its bottom toolbar shows/hides, which leaves plain `bottom: 22px`
     sitting under the toolbar or floating mid-page depending on scroll
     state -- a fixed element is positioned against the layout viewport, so
     it doesn't track that. --vv-offset is measured every frame from
     window.visualViewport in blocks.js and added in here; on browsers
     without that quirk (or without the API at all) it's just 0. */
  bottom: calc(14px + env(safe-area-inset-bottom, 0px) + var(--vv-offset, 0px));
  z-index: 60;
  opacity: 0;
  transform: translate(-50%, 90%);
  display: inline-block;
  padding: 16px 34px;
  border-radius: 999px;
  background: linear-gradient(135deg, #3f1273, #c81ea0);
  color: #fff;
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.02em;
  text-decoration: none;
  white-space: nowrap;
  box-shadow: 0 10px 34px rgba(150, 30, 140, 0.4);
  transition: transform 0.75s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.6s ease;
  will-change: transform, opacity;
  pointer-events: none;
}
.fixed-cta.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
  pointer-events: auto;
}
/* Second CTA (see blocks.js): same shape/motion, swaps in for the one above
   once the portrait is fully formed. Both share .fixed-cta positioning, so
   they occupy the exact same spot -- only one is ever visible at a time. */
.fixed-cta--red {
  background: linear-gradient(135deg, #ff1f8f, #ff8a00);
  box-shadow: 0 10px 34px rgba(255, 110, 60, 0.4);
}

/* "もどる" -- see js/return-to-source.js. Small, bottom-right, present on
   every page site-wide (not just while a scroll-driven CTA happens to be
   showing), so a visitor who arrived from aimesen.jp/bizcommu.jp/
   luminelabo.me can always get back, no matter how far they wander. */
.return-cta {
  position: fixed;
  right: calc(14px + env(safe-area-inset-right, 0px));
  bottom: calc(14px + env(safe-area-inset-bottom, 0px) + var(--vv-offset, 0px));
  z-index: 61;
  display: inline-flex;
  align-items: center;
  padding: 9px 16px;
  border-radius: 999px;
  background: rgba(10, 10, 16, 0.72);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--glass-border);
  color: var(--ink-dim);
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.2s, border-color 0.2s, transform 0.2s var(--ease);
}
.return-cta:hover {
  color: var(--ink);
  border-color: var(--accent-3);
  transform: translateY(-2px);
}

/* ---------- about tagline: sits above the portrait's head, fades in from a
   heavy blur (see blocks.js) the instant the red CTA takes over at the
   bottom of the page. Fixed to the VIEWPORT, not #about's own box -- by the
   time you're actually scrolled to the bottom of the page (which is what
   triggers this), #about itself has scrolled up out of view, so anything
   positioned relative to its box would be off-screen too even at
   opacity:1. This is the same reasoning as the fixed CTAs below it. */
.about-tagline {
  position: fixed;
  top: 12vh;
  left: 50%;
  transform: translateX(-50%);
  z-index: 55;
  width: 88%;
  max-width: 520px;
  margin: 0;
  text-align: center;
  color: #fff;
  font-size: clamp(16px, 4.2vw, 22px);
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1.6;
  opacity: 0;
  filter: blur(16px);
  transition: opacity 1.6s ease, filter 1.6s ease;
  pointer-events: none;
}
.about-tagline.is-visible {
  opacity: 1;
  filter: blur(0);
}

html {
  scroll-behavior: smooth;
  background: var(--bg);
  overflow-x: hidden;
  max-width: 100%;
  touch-action: pan-y;
  overscroll-behavior-x: none;
}

body {
  margin: 0;
  min-height: 100vh;
  max-width: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: 'Inter', 'Noto Sans JP', -apple-system, BlinkMacSystemFont, sans-serif;
  font-weight: 400;
  line-height: 1.6;
  overflow-x: hidden;
  overscroll-behavior-x: none;
  touch-action: pan-y;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

h1, h2, h3, .display {
  font-family: 'Space Grotesk', 'Noto Sans JP', sans-serif;
  letter-spacing: -0.01em;
  margin: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

#scene-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 0;
  touch-action: pan-y;
  background:
    radial-gradient(ellipse 120% 80% at 50% -10%, rgba(20, 80, 255, 0.06), transparent 55%),
    radial-gradient(ellipse 100% 70% at 80% 100%, rgba(20, 80, 255, 0.03), transparent 55%),
    var(--bg);
  filter: saturate(1.4) contrast(1.18);
}

#scene-canvas.is-loading { opacity: 0; }
#scene-canvas { transition: opacity 1.4s var(--ease); }

.grain {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

.site {
  position: relative;
  z-index: 2;
}

/* ---------- nav ---------- */

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 18px clamp(20px, 5vw, 56px);
  /* backdrop-filter deliberately NOT in this transition -- animating a
     blur is expensive (the browser has to recompute the blurred
     composite every single frame of the 0.4s ramp), and since
     .is-scrolled toggles right as the user starts actively scrolling,
     that per-frame blur cost was competing with the scroll compositor
     for GPU time at exactly the worst moment -- confirmed via the
     stuck-scroll jank profiler (scrollY frozen mid-touchmove, always
     with navScrolled:true). Snapping it on/off instantly instead still
     reads fine (the colour/border keep their own smooth transition). */
  transition: background 0.4s var(--ease), border-color 0.4s var(--ease);
  border-bottom: 1px solid transparent;
}

.nav.is-scrolled {
  /* backdrop-filter itself removed now, not just its transition -- even
     static (non-animating) blur still forces the browser to recomposite
     the blurred sample of whatever's scrolling underneath on every
     scroll frame, and the jank profiler kept showing frame-gaps with
     navScrolled:true even after the transition was removed. A plain
     higher-opacity solid background reads almost the same and costs
     nothing extra during scroll. */
  background: rgba(5, 5, 8, 0.86);
  border-bottom-color: var(--glass-border);
}

/* Logo sits in the centre column regardless of the hamburger's width, with
   an empty spacer on the right balancing it out. */
.nav__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-self: center;
}

.nav__logo {
  display: block;
  height: 34px;
  width: auto;
}

.nav__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent-3);
  box-shadow: 0 0 12px 2px var(--accent-3);
  animation: pulse-dot 2.4s ease-in-out infinite;
}

@keyframes pulse-dot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.5; transform: scale(0.72); }
}

.nav__menu-btn {
  justify-self: start;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink);
  font-size: 21px;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}
.nav__menu-btn:hover,
.nav__menu-btn[aria-expanded="true"] {
  background: var(--glass);
  border-color: var(--glass-border);
}

/* LIVE配信中バッジ -- js/main.js が live_now.php をポーリングして表示を
   切り替える。3回点滅→3秒停止→また3回点滅、を繰り返す(ずっと点滅し続け
   ると煩いが、止まったままだと動いてる感が出ないので、この中間)。 */
.nav__live-badge {
  flex: none;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 3px 7px;
  border-radius: 4px;
  background: #e11d3c;
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  white-space: nowrap;
  text-decoration: none;
  animation: nav-live-blink 11s ease-in-out infinite;
}
.nav__live-badge i {
  font-size: 9.5px;
}
/* Desktop has plenty of header room -- the phone-tuned size just reads as
   tiny/lost up there, so scale it back up past tablet width. */
@media (min-width: 768px) {
  .nav__live-badge {
    gap: 7px;
    padding: 8px 16px;
    border-radius: 8px;
    font-size: 13px;
  }
  .nav__live-badge i {
    font-size: 15px;
  }
}
@keyframes nav-live-blink {
  0%, 9%, 100% { opacity: 1; }
  4.5% { opacity: 0.3; }
}

.nav__right {
  justify-self: end;
  display: flex;
  align-items: center;
  min-width: 38px;
}

/* Dropdown panel, toggled by js/main.js. Anchored under the nav bar,
   collapsed to height:0 (not display:none) so the height transition can
   actually animate instead of snapping open/closed. */
/* Transform+opacity only (no height/max-height) -- compositor-only, so the
   snap-open reads as fast and "気持ちよく" rather than fighting a layout
   property. Same overshoot curve as the fixed CTAs elsewhere on the site. */
.nav__menu {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 39;
  display: flex;
  flex-direction: column;
  border-bottom: 1px solid transparent;
  opacity: 0;
  transform: translateY(-16px);
  pointer-events: none;
  transition: transform 0.32s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.22s ease, border-color 0.3s;
}
/* background + backdrop-filter live ONLY on .is-open: this element sits in
   the DOM (fixed, full viewport width) on every single page whether the
   menu is open or not, and backdrop-filter forces the browser to keep
   sampling/blurring whatever scrolls behind it every frame it's present --
   even at opacity:0 -- not just while actually visible. That was a
   permanent, silent scroll-jank cost paid on every page for a panel that's
   closed the overwhelming majority of the time. */
.nav__menu.is-open {
  background: rgba(5, 5, 8, 0.92);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
  border-bottom-color: var(--glass-border);
  will-change: transform, opacity;
}
.nav__menu a {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px clamp(20px, 5vw, 56px);
  font-size: 15px;
  color: #fff;
  border-top: 1px solid var(--glass-border);
  transition: background 0.2s;
}
.nav__menu a i {
  font-size: 18px;
  color: var(--accent-3);
}
.nav__menu a:first-child {
  /* Clears the fixed nav bar's own height so the first link doesn't sit
     underneath it. */
  margin-top: 74px;
}
.nav__menu a:hover {
  background: var(--glass);
}

.nav__menu-backdrop {
  position: fixed;
  inset: 0;
  z-index: 38;
  background: rgba(0, 0, 0, 0.4);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s;
}
.nav__menu-backdrop.is-open {
  opacity: 1;
  pointer-events: auto;
}

/* ---------- hero ---------- */

.hero {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* The particle figure occupies the upper part of the frame; the headline
     starts just beneath it with a small gap, instead of being pinned to the
     bottom of the viewport. */
  justify-content: flex-start;
  text-align: center;
  padding: clamp(300px, 52svh, 560px) 24px 80px;
  position: relative;
}

/* No scrim behind the hero. A dark ellipse here reads as a shadow painted
   around the logo rather than as depth, and it also hides the 3D scene the
   hero is supposed to be sitting inside. Legibility is handled by the gas
   keeping a clearing around the origin instead. */

.hero__title {
  line-height: 0.98;
}

.hero__logo {
  display: block;
  width: clamp(120px, 22vw, 190px);
  height: auto;
  margin: 0 auto;
}

.hero__sub {
  margin-top: 26px;
  max-width: 620px;
  text-align: center;
  color: var(--ink-dim);
  font-weight: 300;
}

/* Hierarchy inside the block: a quiet, small set-up line, then the payoff at
   roughly twice the size and full brightness. Previously both lines sat at the
   same size, so the pair read as one flat paragraph with no emphasis. */
.hero__lead {
  display: block;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: clamp(38px, 11.5vw, 66px);
  font-weight: 100;
  letter-spacing: 0.06em;
  color: #ffffff;
  opacity: 1;
}

.hero__lead-particle {
  font-size: 0.55em;
}


.hero__punch {
  display: block;
  margin-top: -6px;
  color: var(--ink);
  font-family: 'Noto Sans JP', sans-serif;
  font-weight: 100;
  font-size: clamp(40px, 12vw, 64px);
  line-height: 1.3;
}

/* Same trick as the title: the maru is taken out of flow so the phrase itself
   is what gets centred, and the mark hangs off its right shoulder. */
.hero__punch-main {
  position: relative;
  display: inline-block;
  color: #ffffff;
}

.hero__punch-main .dot-tail {
  position: absolute;
  left: 100%;
  top: 0;
}

.hero__reveal-char {
  display: inline-block;
  clip-path: inset(0 100% 0 0);
  transition: clip-path 0.4s ease-out;
}
.hero__reveal-char.is-in {
  clip-path: inset(0 0 0 0);
}

.hero__socials {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  margin-top: 34px;
  margin-bottom: 60px;
}
/* Full 6-icon set on the about page -- reuses .hero__social's own button
   look, just in a left-aligned wrapping row instead of the hero's centred
   one. */
.about-socials {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
}
/* "SNSはindex.phpで使用してるような、ページに入ったらAnimationでひょこ
   ひょこイージングで上がってくるように" -- footer__socialのバウンド
   ポップイン(css/main.css内 .footer .footer__social.is-in)と同じ
   質感を、スクロール検知(IntersectionObserver)なしでページ読み込み
   直後に発火させる版。 */
.about-socials--center {
  justify-content: center;
  margin: 0 0 18px;
}
/* "赤ボタンの下に少しスペースを入れてSNS各種(YouTubeはボタンで既に
   大きくあるので5個)" */
.about-socials--spaced {
  margin: 32px 0 32px;
}
/* "SNSボタンの上に薄いボーダーで仕切る" -- margin-topは重複するので
   その分を相殺し、paddingで境界からの余白を作る。 */
.about-socials--divider {
  margin-top: 0;
  padding-top: 32px;
  border-top: 1px solid var(--glass-border);
}
.about-socials--center .hero__social {
  opacity: 0;
  transform: translateY(32px) scale(0.6);
  transition: transform 0.6s cubic-bezier(0.34, 1.7, 0.55, 1), opacity 0.35s ease;
}
.about-socials--center .hero__social.is-in {
  opacity: 1;
  transform: translateY(0) scale(1);
}
.hero__social {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.35);
  color: #ffffff;
  text-decoration: none;
  transition: border-color 0.2s ease, background 0.2s ease;
}
.hero__social:hover {
  border-color: rgba(255, 255, 255, 0.7);
  background: rgba(255, 255, 255, 0.08);
}
.hero__social i {
  font-size: 20px;
  line-height: 1;
}

.hero__scroll {
  position: absolute;
  bottom: 40px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  color: var(--ink-faint);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.hero__scroll-line {
  width: 1px;
  height: 40px;
  background: linear-gradient(var(--ink-faint), transparent);
  animation: scroll-hint 2.2s ease-in-out infinite;
}

@keyframes scroll-hint {
  0% { transform: scaleY(0); transform-origin: top; opacity: 0; }
  40% { transform: scaleY(1); transform-origin: top; opacity: 1; }
  60% { transform: scaleY(1); transform-origin: bottom; opacity: 1; }
  100% { transform: scaleY(0); transform-origin: bottom; opacity: 0; }
}

/* ---------- prose (narrative copy) ---------- */

.prose {
  max-width: 620px;
  margin: 0 auto;
  text-align: center;
}

.prose p {
  font-size: clamp(15px, 2vw, 18px);
  font-weight: 300;
  color: var(--ink-dim);
  line-height: 1.9;
  margin: 0 0 2px;
}

.prose p.gap { margin-bottom: 30px; }

.prose .lead {
  font-size: clamp(20px, 2.8vw, 27px);
  color: var(--ink);
  font-weight: 500;
  font-family: 'Space Grotesk', 'Noto Sans JP', sans-serif;
  line-height: 1.5;
  margin: 42px 0;
}

.prose strong {
  color: var(--ink);
  font-weight: 500;
}

.prose-divider {
  width: 1px;
  height: 64px;
  background: linear-gradient(var(--glass-border), transparent);
  margin: 30px auto;
}

.prose-divider--wide {
  width: 44px;
  height: 1px;
  background: var(--glass-border);
  margin: 72px auto;
}

/* ---------- explorer cards (今日は、どこから覗きますか？) ---------- */

.exploregrid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
}

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

.explorecard {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 34px 30px;
  border-radius: var(--radius);
  background: var(--glass);
  border: 1px solid var(--glass-border);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  transition: transform 0.5s var(--ease), border-color 0.5s var(--ease), background 0.5s var(--ease);
  overflow: hidden;
}

.explorecard::after {
  content: '';
  position: absolute;
  width: 260px;
  height: 260px;
  border-radius: 50%;
  right: -100px;
  top: -100px;
  background: radial-gradient(circle, var(--card-c1, var(--accent)) 0%, transparent 70%);
  opacity: 0.14;
  filter: blur(10px);
  transition: opacity 0.5s var(--ease), transform 0.6s var(--ease);
  pointer-events: none;
}

.explorecard:hover {
  transform: translateY(-6px);
  border-color: rgba(255, 255, 255, 0.16);
}
.explorecard:hover::after { opacity: 0.3; transform: scale(1.12); }

.explorecard__emoji {
  font-size: 26px;
  line-height: 1;
}

.explorecard__title {
  font-family: 'Space Grotesk', 'Noto Sans JP', sans-serif;
  font-size: 21px;
  font-weight: 600;
}

.explorecard__body {
  font-size: 14px;
  color: var(--ink-dim);
  font-weight: 300;
  line-height: 1.85;
  flex-grow: 1;
}

.explorecard__cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--card-c1, var(--accent-3));
  margin-top: 4px;
}
.explorecard__cta svg { width: 13px; height: 13px; transition: transform 0.35s var(--ease); }
.explorecard:hover .explorecard__cta svg { transform: translate(3px, -3px); }

.explorecard__sublinks {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}

.explorecard__sublinks a {
  font-size: 12.5px;
  padding: 6px 13px;
  border-radius: 999px;
  border: 1px solid var(--glass-border);
  color: var(--ink-dim);
  transition: border-color 0.25s, color 0.25s, background 0.25s;
}

.explorecard__sublinks a:hover {
  color: var(--ink);
  border-color: var(--card-c1, var(--accent));
  background: rgba(255, 255, 255, 0.05);
}

/* ---------- section shell ---------- */

.section {
  position: relative;
  max-width: 1180px;
  margin: 0 auto;
  padding: clamp(80px, 12vw, 160px) 24px;
}

.section__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 56px;
  flex-wrap: wrap;
}

.section__kicker {
  display: block;
  font-size: 12.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent-3);
  margin-bottom: 14px;
  font-weight: 500;
}

.section__title {
  font-size: clamp(28px, 4.4vw, 44px);
  font-weight: 600;
}

.section__note {
  color: var(--ink-faint);
  font-size: 14px;
  max-width: 320px;
}

.reveal {
  opacity: 0;
  transform: translateY(36px);
  transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* ---------- link hub ---------- */

.linkgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 18px;
}

.linkcard {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 28px;
  padding: 28px;
  min-height: 168px;
  border-radius: var(--radius);
  background: var(--glass);
  border: 1px solid var(--glass-border);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  overflow: hidden;
  transition: transform 0.5s var(--ease), border-color 0.5s var(--ease), background 0.5s var(--ease);
  cursor: pointer;
}

.linkcard::before {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(135deg, var(--card-c1, var(--accent)), transparent 45%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  transition: opacity 0.5s var(--ease);
}

.linkcard::after {
  content: '';
  position: absolute;
  width: 220px;
  height: 220px;
  border-radius: 50%;
  right: -80px;
  top: -80px;
  background: radial-gradient(circle, var(--card-c1, var(--accent)) 0%, transparent 70%);
  opacity: 0.16;
  filter: blur(10px);
  transition: opacity 0.5s var(--ease), transform 0.6s var(--ease);
}

.linkcard:hover {
  transform: translateY(-6px);
  background: var(--glass-hover);
}
.linkcard:hover::before { opacity: 1; }
.linkcard:hover::after { opacity: 0.32; transform: scale(1.15); }

.linkcard__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.linkcard__icon {
  width: 42px;
  height: 42px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.06);
  color: var(--card-c1, var(--accent));
}
.linkcard__icon svg { width: 20px; height: 20px; }

.linkcard__arrow {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--glass-border);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.4s var(--ease), border-color 0.4s var(--ease);
}
.linkcard:hover .linkcard__arrow {
  transform: rotate(45deg);
  border-color: var(--card-c1, var(--accent));
}
.linkcard__arrow svg { width: 13px; height: 13px; }

.linkcard__title {
  font-family: 'Space Grotesk', sans-serif;
  font-size: 19px;
  font-weight: 600;
}

.linkcard__desc {
  font-size: 13px;
  color: var(--ink-faint);
  margin-top: 4px;
}

.linkcard--pending {
  cursor: default;
  opacity: 0.55;
}
.linkcard--pending:hover { transform: none; }
.linkcard--pending::after { display: none; }

.badge-soon {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 999px;
  border: 1px solid var(--glass-border);
  color: var(--ink-faint);
}

/* ---------- reports ---------- */

.reportgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
}

.reportcard {
  position: relative;
  padding: 30px 28px 28px;
  border-radius: var(--radius);
  background: linear-gradient(160deg, rgba(255,255,255,0.05), rgba(255,255,255,0.015));
  border: 1px solid var(--glass-border);
  transition: transform 0.5s var(--ease), border-color 0.4s;
  isolation: isolate;
  overflow: hidden;
}

.reportcard:hover { transform: translateY(-4px); border-color: rgba(255,255,255,0.18); }

.reportcard__spark {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0.5;
  background: radial-gradient(420px circle at var(--mx, 50%) var(--my, 0%), rgba(61, 139, 255,0.14), transparent 60%);
}

.reportcard__meta {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 11.5px;
  color: var(--ink-faint);
  letter-spacing: 0.04em;
  margin-bottom: 16px;
}

.reportcard__tag {
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(61, 139, 255, 0.14);
  color: #c9c0ff;
  font-weight: 500;
}

.reportcard__title {
  font-family: 'Space Grotesk', sans-serif;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.3;
  margin-bottom: 12px;
}

.reportcard__excerpt {
  font-size: 13.5px;
  color: var(--ink-dim);
  font-weight: 300;
}

.reportcard__stat {
  margin-top: 22px;
  display: flex;
  gap: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--glass-border);
}

.reportcard__stat div b {
  display: block;
  font-family: 'Space Grotesk', sans-serif;
  font-size: 17px;
  font-weight: 600;
}
.reportcard__stat div span {
  font-size: 10.5px;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* ---------- about strip ---------- */

.about {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: 60px;
  align-items: center;
}

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

.about__text {
  font-size: clamp(20px, 2.6vw, 27px);
  font-weight: 300;
  line-height: 1.55;
  color: var(--ink-dim);
}

.about__text strong {
  color: var(--ink);
  font-weight: 500;
  background: linear-gradient(90deg, var(--accent-3), var(--accent));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.stat-col {
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.stat-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--glass-border);
}

.stat-row b {
  font-family: 'Space Grotesk', sans-serif;
  font-size: 30px;
  font-weight: 600;
}

.stat-row span {
  font-size: 12.5px;
  color: var(--ink-faint);
  text-align: right;
  max-width: 160px;
}

/* ---------- footer ---------- */

.footer {
  position: relative;
  padding: 60px 24px 40px;
  text-align: center;
  color: var(--ink-faint);
  font-size: 12.5px;
}

.footer__brand {
  font-family: 'Space Grotesk', sans-serif;
  font-size: 20px;
  font-weight: 600;
  color: #fff;
  margin-bottom: 16px;
}

.footer__copy {
  color: var(--accent);
  margin-top: 30px;
}

/* Kept in the DOM (not display:none) -- just pushed below the visible
   footer so it's effectively invisible without deleting the content.
   height:0 + overflow:hidden keeps this from adding to the page's
   scrollable area -- an absolutely positioned box still counts toward
   document.documentElement.scrollHeight even at opacity:0, which was
   throwing off blocks.js's atBottom() bottom-of-page detection. */
.footer__copy--hidden {
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;
  height: 0;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}

.footer a { border-bottom: 1px solid transparent; transition: border-color 0.2s; }
.footer a:hover { border-color: var(--ink-faint); }

.to-top {
  display: inline-flex;
  margin-top: 24px;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1px solid var(--glass-border);
  align-items: center;
  justify-content: center;
  transition: transform 0.3s var(--ease), border-color 0.3s;
}
.to-top:hover { transform: translateY(-4px); border-color: var(--accent-3); }
.to-top i { font-size: 16px; line-height: 1; }

.footer__socials {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-top: 30px;
}
/* ".footer .footer__social", not just ".footer__social" -- ".footer a"
   above sets border-bottom on every footer link, and its extra tag
   selector gives it higher specificity than a bare ".footer__social"
   class, which was silently punching a transparent gap in the bottom of
   the circle border. */
.footer .footer__social {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.3);
  color: #fff;
  text-decoration: none;
  opacity: 0;
  /* --dx/--dy: each icon's own random scatter offset, set inline by
     blocks.js so every icon bounces in from a different direction. */
  transform: translate(var(--dx, 0), var(--dy, 46px)) scale(0.5);
  transition: transform 0.65s cubic-bezier(0.34, 1.7, 0.55, 1), opacity 0.4s ease, border-color 0.2s ease, background 0.2s ease;
}
.footer .footer__social.is-in {
  opacity: 1;
  transform: translate(0, 0) scale(1);
}
.footer .footer__social:hover {
  border-color: rgba(255, 255, 255, 0.7);
  background: rgba(255, 255, 255, 0.08);
}
.footer .footer__social i {
  font-size: 18px;
  line-height: 1;
}
.footer__social-text {
  font-family: 'Space Grotesk', sans-serif;
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
}

/* ---------- misc ---------- */

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

::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.14); border-radius: 8px; }

.noscript-note {
  position: fixed;
  bottom: 16px;
  left: 16px;
  z-index: 50;
  font-size: 11px;
  color: var(--ink-faint);
  background: rgba(0,0,0,0.4);
  padding: 6px 10px;
  border-radius: 8px;
  border: 1px solid var(--glass-border);
}

/* ---------- mobile-first reading column ----------
   The reading column keeps phone proportions on every screen size. Two reasons:
   the layout was designed mobile-first, and the 3D morph stages are driven by
   how much of the viewport each section covers -- so holding the column width
   constant keeps the total scroll length, and therefore the timing of those
   stages, roughly the same on a desktop as on a phone.
   The 3D canvas is fixed and full-bleed, so the background is unaffected. */
.site {
  max-width: 560px;
  margin-inline: auto;
}
.section {
  max-width: 100%;
}
.exploregrid,
.reportgrid {
  grid-template-columns: 1fr;
}
.section__head {
  flex-direction: column;
  align-items: flex-start;
}

/* ---------- modal CTAs + shared modal (see js/modal.js) -------------------
   .modal-cta is the small pill button reused for the poem and hub/spiral
   triggers -- same visual family as .reports-cta but standalone (those two
   spots have no scroll-linked entrance animation of their own). */
.modal-cta {
  display: inline-block;
  margin-top: 28px;
  padding: 11px 22px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.10);
  border: 1px solid rgba(255, 255, 255, 0.28);
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-decoration: none;
  transition: background 0.2s, border-color 0.2s;
}
.modal-cta:hover {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(255, 255, 255, 0.45);
}
.modal-cta--below-spiral {
  margin-top: 32px;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  background: rgba(4, 6, 12, 0.72);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.1s ease;
}
.modal-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}
/* Full-screen, not a narrow centred card -- deliberately doesn't feel like
   a cramped popup. Scales up with a springy overshoot (cubic-bezier alone
   -- no JS, no rAF loop -- pushes it past 1 then settles back, which reads
   as a rebound) rather than a plain fade. Closing runs the exact same
   transition in reverse, no separate close-only rule needed. Only
   transform + opacity are touched, both compositor-only, so this never
   forces layout/paint on the (very tall) page underneath -- will-change is
   scoped to while the overlay is actually open so it isn't holding a
   layer in memory the rest of the time. */
/* Plain sizing/positioning shell only -- deliberately carries NO transform.
   .modal-panel (below) is what actually scales in on open and scrolls its
   own content; keeping this element transform-free means .modal-stars,
   positioned fixed as its child, is a real spec-standard "fixed to the
   viewport" element -- not fixed-relative-to-a-transformed-ancestor, which
   real browsers (mobile Safari especially) don't reliably honor, and which
   a JS scroll-position listener can only approximate with a frame or two
   of lag (the "kicks up" stutter that was visible before this). */
.modal-box {
  position: relative;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
}
@media (min-width: 900px) {
  .modal-overlay {
    justify-content: center;
  }
  .modal-box {
    width: 880px;
    max-width: 880px;
  }
  /* Phone keeps the body copy left-aligned (easier to read line to line on
     a narrow column); desktop's much wider column reads better centred,
     matching the already-centred title above it. */
  .modal-body,
  .modal-body p {
    text-align: center;
  }
  .modal-activity {
    text-align: center;
  }
  .modal-activity__name {
    justify-content: center;
  }
}
/* The actual visible card: scroll container, background, and the open/
   close scale animation all live here now. */
.modal-panel {
  position: relative;
  width: 100%;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  /* bg.png sits under the gradient tint (an atmosphere/colour-grade layer,
     not opaque), so the stack reads back-to-front as: bg image -> gradient
     tint -> (the truly-fixed, always-on-top-of-this) twinkling stars/
     meteors -> title/body. Tiled vertically at full width rather than
     stretched with cover, since this can be many screens tall. */
  background-image: radial-gradient(ellipse at 50% 0%, rgba(18, 22, 42, 0.55) 0%, rgba(10, 12, 20, 0.7) 46%, rgba(5, 6, 10, 0.85) 100%), url('../images/bg.png');
  background-repeat: no-repeat, repeat-y;
  background-size: 100% 100%, 100% auto;
  background-position: center, center top;
  padding: max(28px, env(safe-area-inset-top, 0px)) 26px calc(48px + env(safe-area-inset-bottom, 0px));
  transform: scale(0.82) translateY(4%);
  transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.modal-overlay.is-open .modal-panel {
  transform: scale(1) translateY(0);
  will-change: transform;
}
/* True position:fixed now that nothing between this and the viewport
   carries a transform -- stays visually still as .modal-panel scrolls
   underneath it, natively, with zero JS involved.
   .modal-panel has its own `transform`, which (independent of z-index)
   always creates its own stacking context -- so panel's title/body can
   never be individually interleaved between this and panel's own
   background from outside; z-index can only stack this above or below the
   panel as one atomic unit. Above + screen blending is the shape that
   works: stars/meteors only ever ADD brightness, so passing in front of
   bold white text or the dark background alike stays legible, without the
   scroll-lag a "truly behind the text" arrangement would need JS for. */
.modal-stars {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  mix-blend-mode: screen;
  pointer-events: none;
}
/* Activity modal only: the quantum swarm's electron dots are small and
   similarly-hued to the stars, so relying on mix-blend-mode to keep them
   visible underneath is too fragile (some mobile browsers don't composite
   a WebGL canvas's blend-mode reliably, effectively painting the starfield
   over it). Drop the stars below the panel here instead -- one modal loses
   "stars glide in front of the text" and gains "the quantum swarm is
   actually visible", which is the right trade for this specific modal. */
.modal-overlay[data-modal="contact"] .modal-stars {
  z-index: 0;
}
.modal-close,
.modal-mark,
.modal-title,
.modal-body {
  position: relative;
  z-index: 1;
}
/* Below .modal-body now (after the closing line, before "もどる") -- see
   index.php. */
.modal-mark {
  display: block;
  margin: 36px auto 4px;
  width: clamp(100px, 16vw, 150px);
  height: auto;
}
/* The blue "hitodama" wisp (js/modal-wisp.js) -- poem modal only, shown/
   mounted by js/modal.js. [hidden] wins the display fight so the canvas
   genuinely isn't laid out (and isn't mounted/rendering -- see modal.js)
   for every other modal. First element in the panel, so it still needs to
   clear the close button (top:12px + 34px tall = 46px), just with less
   breathing room above it than before. */
.modal-wisp {
  display: flex;
  justify-content: center;
  margin: 20px 0 10px;
}
.modal-wisp[hidden] {
  display: none;
}
/* Two separate <canvas> elements now (poem's single hitodama, contact's
   sphere cluster) rather than one shared canvas toggled between them --
   a canvas's WebGL context, once created, can never change size class or
   be un-created; reusing one element for two independently-mounted scenes
   meant a leftover/failed context from one could permanently break the
   other for the rest of that page's life. Only the active one is ever
   actually mounted (js/modal.js), so there's no double GPU cost -- this
   split is purely about isolating their canvases from each other. */
.modal-wisp__cv--poem {
  width: 108px;
  height: 144px;
  display: block;
}
.modal-wisp__cv--quantum {
  width: 220px;
  height: 140px;
  display: none;
  /* Genuinely transparent now (see js/modal-wisp.js's selective-bloom
     composite) -- no mix-blend-mode trick needed. */
}
/* Went through a full-bleed background-layer version (100vw, absolute,
   height:400px, spanning behind the title) and a smaller 190x190 mobile
   override -- both got reverted per feedback: same box/margin as the
   poem wisp ("人魂と同じ余白"/"人魂がいた位置くらい"), at the plain 220x140
   size, no special mobile case. Some of the orbs' bloom halo spilling past
   the box edge is fine/expected (bloom always bleeds a bit) -- that's the
   "少々動いてもいいや" the box doesn't need to contain perfectly. */
.modal-wisp--quantum .modal-wisp__cv--poem {
  display: none;
}
.modal-wisp--quantum .modal-wisp__cv--quantum {
  display: block;
}
/* Field first (same DOM order as the poem wisp -- no more reordering,
   that flex `order` version broke real Safari's rendering badly, see
   commit history), positioned as a background layer so its own bottom
   edge lines up with the divider line under the title ("タイトル下の
   ボーダーをあわせる" -- cut off at a real design line instead of an
   arbitrary point, so it reads as intentional). Title gets pushed down to
   open the same amount of space above it as the poem modal has above its
   own title. Both numbers came from live-measuring both modals' DOM. */
.modal-wisp--quantum {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  margin: 0;
  height: 278px;
  z-index: 0;
}
.modal-wisp--quantum + .modal-title {
  margin-top: 172.5px;
}
.modal-wisp--quantum .modal-wisp__cv--quantum {
  width: 100%;
  height: 100%;
}
.modal-overlay.is-open {
  will-change: opacity;
}
.modal-overlay.is-open .modal-box {
  transform: scale(1) translateY(0);
  will-change: transform;
}
.modal-close {
  position: absolute;
  top: 12px;
  left: 12px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.08);
  color: var(--ink);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.modal-close:hover {
  background: rgba(255, 255, 255, 0.18);
}
/* Starts clear below the close button (top:12px + 34px tall), centered,
   with a thin rule underneath to separate it from the body copy. */
.modal-title {
  margin: 0;
  padding-bottom: 20px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
  font-family: 'M PLUS 1', 'Noto Sans JP', sans-serif;
  font-size: clamp(36px, 9vw, 48px);
  font-weight: 100;
  text-align: center;
  color: var(--ink);
}
/* Trailing "。": taken out of flow so it doesn't count toward the centred
   text's own width, same trick as .dot-tail/.debug-block__dot elsewhere. */
.modal-title__text {
  position: relative;
  display: inline-block;
}
.modal-title__dot {
  position: absolute;
  left: 100%;
  top: 0;
}
.modal-body {
  margin-top: 28px;
  font-family: 'M PLUS 1', 'Noto Sans JP', sans-serif;
  font-size: clamp(17px, 3.6vw, 19px);
  font-weight: 700;
  line-height: 1.9;
  /* Noticeably whiter than --ink-dim (0.62 alpha) -- that variable read as
     too dim for a full paragraph of body copy in the modal specifically. */
  color: rgba(244, 242, 255, 0.88);
}
.modal-body p {
  margin: 0 0 22px;
}
.modal-body p:last-child {
  margin-bottom: 0;
}
/* Bottom "もどる" -- a second, always-reachable way to close the modal once
   you've scrolled to the end of it, matching data-modal-close so it's
   wired up automatically alongside the top-corner close button. Generous
   margin on both sides so it reads as its own closing beat, not crowded
   against the last paragraph or the bottom edge. */
.modal-back {
  display: block;
  margin: 72px auto 56px;
  padding: 13px 32px;
  border-radius: 999px;
  border: 1px solid var(--glass-border);
  background: var(--glass);
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s, transform 0.2s var(--ease);
}
.modal-back:hover {
  border-color: var(--accent-3);
  background: var(--glass-hover);
  transform: translateY(-2px);
}
/* The closing line ("僕たちはそれを、「スピエンス」と呼んでいます。") reads as
   the piece's punchline -- given a beat of extra space above and a slightly
   larger size so it lands instead of blending into the rest of the copy. */
.modal-body__punch {
  margin-top: 8px;
  font-size: 1.15em;
}
.modal-body strong {
  color: var(--accent);
  font-weight: 700;
}
/* Activity list (contact modal): each item is its own name + 2-line blurb,
   spacing controlled at the item level rather than per-<p>, since a <p>
   here is always the last thing in its own .modal-activity wrapper. */
.modal-activity {
  margin-bottom: 32px;
}
.modal-activity:last-child {
  margin-bottom: 0;
}
.modal-activity__name {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 10px;
  font-size: 1.25em;
  font-weight: 700;
  color: #fff;
}
.modal-activity__name i {
  font-size: 1.1em;
  color: var(--accent-3);
}
.modal-activity p {
  margin: 0;
  font-size: 0.86em;
  font-weight: 500;
  line-height: 1.75;
  color: #fff;
}
/* The <br> mid-sentence in each activity description was placed for
   desktop's wider modal width; on a narrow phone screen it's just an odd
   short first line, so it's suppressed below the desktop breakpoint and
   left to wrap naturally instead. */
@media (max-width: 899px) {
  .modal-activity p br {
    display: none;
  }
}

/* ---------- content pages (reports/youtube/community/about) ---------------
   Lightweight page shell for the standalone content pages -- no WebGL
   scene, no scroll-driven choreography, just static dark layout reusing
   the same design tokens/nav/footer as the homepage. */
.page {
  min-height: 100vh;
  /* パンくずをヘッダーの後ろに隠した分、詰めた高さ(96px→76px)。 */
  padding-top: 76px;
}
/* about.php test: a soft blue U-shaped HDR-bloom glow (real
   UnrealBloomPass, see js/story-glow.js) -- up the left edge, across the
   bottom, up the right edge -- sitting behind the 生い立ち text. A pure-
   CSS radial-gradient version was tried first (three separate ellipses)
   and got the position/shape right, but the seams between the three
   gradients weren't smooth and it couldn't produce genuine HDR falloff --
   only WebGL pushing colour past 1.0 into a real blur/composite pass
   does that. This version reuses the CSS pass's now-confirmed-correct
   positioning (same U-curve) with real bloom instead.
   z-index sits above the page's normal-flow text (which has no stacking
   context of its own, so it always renders below ANY positioned element
   regardless of z-index value) -- mix-blend-mode:screen makes it add
   light onto whatever's beneath instead of covering it, so it reads as
   glow behind the text rather than a layer blocking it.
   height uses vh directly (fine here -- nothing in JS resizes this
   element or observes its size via ResizeObserver, so there's no
   feedback-loop risk the earlier canvas version had before that was
   found and fixed). bottom offset is still set from JS to clear iOS
   Safari's own bottom toolbar chrome. */
.story-glow {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 50vh;
  z-index: 100000;
  pointer-events: none;
  opacity: 0;
  transition: opacity 2.5s ease;
  mix-blend-mode: screen;
}
.story-glow.is-active {
  opacity: 1;
}

.luminelabo-stage {
  position: relative;
  min-height: calc(100vh - 96px);
  background: var(--bg);
  overflow: hidden;
}
.luminelabo-stage canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* ルミネラボAI 御神殿 (luminelabo.php + js/lumine-shrine.js) -- AIの存在を
   象徴する見るだけの演出ページ。レイヤーは奥から: データHUD(タイプライ
   ター) → WebGLキャンバス(コア/軌道粒子/イコライザー+HDRブルーム) →
   中央ロゴ → 下部キャプション。 */
/* このページはスクロールしない1画面完結("サイトはスライドしない") --
   フッターも出さない(luminelabo.php側で$no_footer)。 */
body:has(.lumine-shrine) {
  overflow: hidden;
  height: 100dvh;
}
.lumine-shrine {
  position: relative;
  height: calc(100vh - 96px);
  height: calc(100dvh - 96px);
  background: #030308;
  overflow: hidden;
  /* スクロールが存在しないので、キャンバス上のあらゆるドラッグを回転に
     使ってよい */
  touch-action: none;
  /* HUDラベルの上でドラッグ回転すると、pointer-events:noneだけでは
     Safariがネイティブのテキスト選択(白いハイライト)を起こすことがある
     -- "右下のスライドボタン。白すぎて文章の目を奪う"はこれだった。
     ページ全体がドラッグ操作専用の演出面なので選択自体を禁止する。 */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
.lumine-shrine__cv {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
}
/* リーダー表紙にルミネラボAI風のフルWebGL演出を試験導入したが、実機で
   fps 0.1(ほぼ停止)・進行タイマー不発火という2つの実測結果が出たため
   撤退(js/lumine-shrine-cover.jsごと削除済み)。表紙自体はシンプルな
   黒背景+タイトル+ロゴの構成に戻している。 */
/* "大きなタイトルが1番上のレイヤーで画面に大きく出てない" -- 通常の
   文書フロー(margin-topでの押し下げ)だとtextElのスクロール可能領域に
   埋もれて画面外になることがあった。position:fixedでビューポート中央に
   直接固定し、常に画面いっぱいに大きく見えるようにする。 */
/* "画面のまだ上の方に表示、下に3Dが入る" -- 中央固定ではなく上寄りに、
   その下に陰陽星のモデル用のスペースを空ける。 */
.story-reader__cover-title {
  position: fixed;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding-top: 9vh;
  pointer-events: none;
}
.story-reader__cover-title-main {
  display: block;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: clamp(36px, 12vw, 56px);
  font-weight: 700;
  color: #fff;
}
/* "ルミネラボ誕生秘話は大きくして下の文字と横幅を合わせる" -- 通常の
   .history-eyebrow(小さい表記、about.php等で共用)より、表紙だけ大きく
   する上書き。 */
.story-reader__cover-eyebrow {
  font-size: clamp(20px, 6.5vw, 28px);
  margin-bottom: 6px;
}
.lumine-hud {
  position: absolute;
  top: 0;
  left: 0;
  /* 画面全面ではなく左上の小さな観測ログブロックに留める(全面に流すと
     シンボル本体と重なってゴチャつく -- 実機で確認済み) */
  max-width: min(74%, 420px);
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  /* 2026-08-22: 1行目(SESSION ...)が右上の「SIMULATION LIVE」と同じ高さから
     始まっていて、狭い画面では文字が重なっていた（実機で発覚）。
     ステータスは top:20px・9px・1行なので、その下端(約34px)より下から
     始める。横幅を詰める案もあるが、それだと1行目が折り返して
     観測ログらしさが崩れるので、開始位置を下げるほうを採る。 */
  padding: max(44px, calc(env(safe-area-inset-top) + 26px)) 18px 18px;
  overflow: hidden;
  pointer-events: none;
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 10px;
  line-height: 1.9;
  letter-spacing: 0.05em;
  color: rgba(120, 180, 235, 0.32);
  /* 下端に向けて薄れて消える(中央のシンボルの邪魔をしない) */
  -webkit-mask-image: linear-gradient(to bottom, #000 0, #000 55%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0, #000 55%, transparent 100%);
}
/* 四隅のHUDブラケット */
.lumine-corner {
  position: absolute;
  z-index: 4;
  width: 22px;
  height: 22px;
  pointer-events: none;
  border: 0 solid rgba(120, 200, 255, 0.35);
}
.lumine-corner--tl { top: 14px; left: 14px; border-top-width: 1px; border-left-width: 1px; }
.lumine-corner--tr { top: 14px; right: 14px; border-top-width: 1px; border-right-width: 1px; }
.lumine-corner--bl { bottom: 14px; left: 14px; border-bottom-width: 1px; border-left-width: 1px; }
.lumine-corner--br { bottom: 14px; right: 14px; border-bottom-width: 1px; border-right-width: 1px; }
/* 右上ライブステータス */
.lumine-status {
  position: absolute;
  top: max(20px, env(safe-area-inset-top));
  right: 22px;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 8px;
  pointer-events: none;
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 9px;
  letter-spacing: 0.22em;
  color: rgba(159, 208, 255, 0.6);
  text-align: right;
}
.lumine-status__dot {
  width: 5px;
  height: 5px;
  border-radius: 999px;
  background: #8fd7ff;
  box-shadow: 0 0 6px rgba(143, 215, 255, 0.9);
  animation: lumine-live-blink 2.2s ease-in-out infinite;
}
.lumine-status__clock {
  color: rgba(159, 208, 255, 0.4);
  letter-spacing: 0.1em;
}
@keyframes lumine-live-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.25; }
}
/* ニューラル網ハブに3D追従するHUDラベル(位置はjs/lumine-shrine.jsが
   毎フレームtransformで更新) */
.lumine-labels {
  position: absolute;
  inset: 0;
  z-index: 3;
  overflow: hidden;
  pointer-events: none;
}
.lumine-node-label {
  position: absolute;
  top: 0;
  left: 0;
  padding: 3px 9px 3px 13px;
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  /* "小さくして発色を少し強く" -- サイズは9pxに戻し気味、そのぶん
     色を白熱シアン寄りに、グローを強めて視認性はカバー。 */
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.16em;
  white-space: nowrap;
  color: #d9f4ff;
  text-shadow:
    0 0 6px rgba(120, 220, 255, 0.95),
    0 0 14px rgba(80, 180, 255, 0.55);
  border-left: 1px solid rgba(140, 225, 255, 0.85);
  background: linear-gradient(to right, rgba(24, 70, 115, 0.55), transparent);
  will-change: transform, opacity;
}
.lumine-node-label::before {
  content: '';
  position: absolute;
  left: -2.5px;
  top: 50%;
  width: 4px;
  height: 4px;
  margin-top: -2px;
  border-radius: 999px;
  background: #8fd7ff;
  box-shadow: 0 0 5px rgba(143, 215, 255, 0.9);
}
.lumine-hud__row {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: clip;
}
.lumine-shrine__center {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.lumine-shrine__logo {
  width: clamp(120px, 30vw, 210px);
  height: auto;
  /* --pulse は js/lumine-shrine.js がコアの鼓動と同期して毎フレーム更新。
     ロゴ自身の発光がWebGL側の後光と一体で呼吸する。 */
  opacity: calc(0.65 + var(--pulse, 0.6) * 0.35);
  filter:
    drop-shadow(0 0 calc(6px + var(--pulse, 0.6) * 10px) rgba(159, 208, 255, 0.9))
    drop-shadow(0 0 calc(20px + var(--pulse, 0.6) * 30px) rgba(140, 120, 255, 0.5));
}
.lumine-shrine__caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: max(26px, env(safe-area-inset-bottom));
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  pointer-events: none;
}
.lumine-shrine__caption-en {
  font-family: 'Space Grotesk', sans-serif;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.42em;
  text-indent: 0.42em; /* letter-spacingの右端余りを見た目センターに補正 */
  color: rgba(159, 208, 255, 0.75);
}
.lumine-shrine__caption-ja {
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  color: rgba(255, 255, 255, 0.45);
}

/* Elemental bloom-particle switcher test (js/element-bloom.js) -- pick a
   preset, see it live. Fixed to the bottom so it stays reachable over a
   full-bleed stage regardless of scroll. */
.element-switch {
  position: fixed;
  left: 50%;
  bottom: 22px;
  z-index: 5;
  display: flex;
  gap: 8px;
  transform: translateX(-50%);
  padding: 8px;
  border-radius: 999px;
  background: rgba(10, 10, 14, 0.55);
  border: 1px solid var(--glass-border);
  backdrop-filter: none; /* see main.css's nav.is-scrolled note -- static blur still costs a composite pass every frame it's on screen */
}
.element-switch__btn {
  padding: 10px 18px;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--ink-dim);
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.25s ease, color 0.25s ease;
}
.element-switch__btn.is-active {
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
}
.page-hero {
  max-width: 880px;
  margin: 0 auto;
  padding: 0 24px 56px;
  text-align: center;
}
.page-eyebrow {
  display: block;
  font-family: 'Space Grotesk', sans-serif;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: var(--accent-3);
  text-transform: uppercase;
  margin-bottom: 14px;
}
.page-title {
  margin: 0 0 18px;
  font-family: 'Space Grotesk', 'Noto Sans JP', sans-serif;
  font-size: clamp(32px, 6vw, 52px);
  font-weight: 700;
  line-height: 1.3;
  color: var(--ink);
}
/* 句読点の字面はグリフの左半分に寄っているので、右半分の空白ぶん
   マージンを詰めて中央揃えの幅計算から除外する（ぶら下げ組の考え方）。 */
.page-title__punct {
  display: inline-block;
  margin-right: -0.5em;
}
.page-title__big {
  display: inline-block;
  font-size: 1.5em;
}
.page-lead {
  margin: 0 auto;
  max-width: 640px;
  font-size: clamp(15px, 2.4vw, 17px);
  font-weight: 500;
  line-height: 1.9;
  color: var(--ink-dim);
}
/* Animated いまぽん。 model (js/about-model.js), about.php only. Explicit
   size/display -- an unstyled <canvas> falls back to the browser default
   300x150 inline box, which was left in normal flow with no touch-action
   set while unfinished; giving it a real block box here closes that off.
   touch-action:none (not pan-y) now that the canvas itself is a
   deliberate drag-to-rotate widget -- js/about-model.js owns all touch
   handling inside it, the same trade any embedded 3D viewer makes. */
.about-model {
  display: block;
  width: 100%;
  max-width: 320px;
  height: 320px;
  margin: 24px auto 0;
  /* manipulation (not none, not plain pan-y) -- a vertical swipe starting
     on the model must still scroll the page (touch-action:none blocked
     ALL touch here, reading as "the page won't scroll past this point"),
     AND rapid tapping (the tap-to-change-action interaction) must not
     trigger the browser's own double-tap-to-zoom gesture, which pan-y
     alone didn't reliably suppress on every browser. manipulation permits
     panning + pinch-zoom-would-be-allowed-if-not-for-viewport-meta while
     explicitly disabling double-tap-zoom; js/about-model.js also adds its
     own JS-level double-tap guard as a second layer. Horizontal
     drag-to-rotate still works since nothing here is actually
     horizontally scrollable for the browser to claim. */
  touch-action: manipulation;
  cursor: grab;
}
.page-breadcrumb {
  /* "SEO向けではあるけど、ヘッダーの後ろ側にいくように隠しといて" --
     テキスト自体はDOMに残す(SEO/構造化データとしては有効)が、目には
     見えないようにする。当初.navの後ろにz-indexで重ねる方式を試したが、
     .navはスクロール前は背景が透明("スクロールの合成コストを抑えるため
     背景をスクロール時だけ付ける"設計)なので、実機では逆に文字が透けて
     ヘッダーと重なって見える新しいバグになった。visibility:hiddenで
     確実に見えなくする(検索エンジンはCSSで非表示でもテキストを読める)。 */
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  visibility: hidden;
  max-width: 880px;
  margin: 0 auto;
  padding: 0 24px;
  font-size: 12.5px;
  color: var(--ink-faint);
}
.page-breadcrumb a {
  color: var(--ink-faint);
  transition: color 0.2s;
}
.page-breadcrumb a:hover {
  color: var(--ink);
}
.page-breadcrumb__sep {
  margin: 0 6px;
  opacity: 0.5;
}

/* report list */
.report-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 20px;
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 24px 80px;
}
.report-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 24px;
  border-radius: var(--radius);
  background: var(--glass);
  border: 1px solid var(--glass-border);
  text-decoration: none;
  transition: border-color 0.25s, background 0.25s, transform 0.25s var(--ease);
}
.report-card:hover {
  border-color: var(--accent);
  background: var(--glass-hover);
  transform: translateY(-3px);
}
.report-card__category {
  font-family: 'Space Grotesk', sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-3);
}
.report-card__title {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  line-height: 1.5;
  color: var(--ink);
}
.report-card__excerpt {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.75;
  color: var(--ink-dim);
}
.report-card__date {
  margin-top: auto;
  padding-top: 6px;
  font-size: 12px;
  color: var(--ink-faint);
}
.page-empty {
  max-width: 880px;
  margin: 0 auto;
  padding: 40px 24px 80px;
  text-align: center;
  color: var(--ink-faint);
  font-size: 14px;
}

/* report detail */
.report-article {
  max-width: 720px;
  margin: 0 auto;
  padding: 0 24px 100px;
}
.report-article__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  margin-bottom: 28px;
  font-size: 13px;
  color: var(--ink-faint);
}
.report-article__body {
  font-size: 16px;
  line-height: 2;
  color: rgba(244, 242, 255, 0.88);
}
.report-article__body p {
  margin: 0 0 24px;
}
.report-article__body h2 {
  margin: 40px 0 16px;
  font-size: 22px;
  font-weight: 700;
  color: var(--ink);
}
.report-article__body img {
  max-width: 100%;
  border-radius: 12px;
  margin: 24px 0;
}
.report-article__body ul {
  margin: 0 0 24px;
  padding-left: 1.3em;
}
.report-article__body li {
  margin-bottom: 8px;
}
.report-article__body blockquote {
  margin: 0 0 24px;
  padding: 4px 0 4px 18px;
  border-left: 3px solid var(--accent);
  color: var(--ink);
  font-weight: 600;
}
.report-article__body blockquote p {
  margin: 0;
}

/* [num:...] stat box from the source paper markup -- a big highlighted
   figure with its unit and a one-line caption. */
.report-stat {
  display: inline-flex;
  flex-direction: column;
  gap: 4px;
  margin: 8px 16px 24px 0;
  padding: 16px 22px;
  border-radius: var(--radius);
  background: var(--glass);
  border: 1px solid var(--glass-border);
}
.report-stat__value {
  font-family: 'Space Grotesk', sans-serif;
  font-size: 32px;
  font-weight: 700;
  color: var(--accent-3);
}
.report-stat__value small {
  margin-left: 4px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-dim);
}
.report-stat__caption {
  font-size: 12.5px;
  color: var(--ink-faint);
}

/* link to the interactive source system a report is based on */
.report-system-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 20px auto 0;
  padding: 13px 24px;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-weight: 600;
  font-size: 14px;
  text-decoration: none;
  transition: background 0.2s, transform 0.2s var(--ease);
}
.report-system-link:hover {
  background: var(--accent-2);
  transform: translateY(-2px);
}

.report-video {
  position: relative;
  width: 100%;
  max-width: 720px;
  aspect-ratio: 16 / 9;
  margin: 28px auto 0;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-2);
}
.report-video iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: none;
}

.report-table {
  width: 100%;
  margin: 0 0 24px;
  border-collapse: collapse;
  font-size: 14px;
  overflow-x: auto;
  display: block;
}
.report-table th,
.report-table td {
  padding: 10px 14px;
  text-align: left;
  border-bottom: 1px solid var(--glass-border);
  white-space: nowrap;
}
.report-table th {
  color: var(--accent-3);
  font-weight: 700;
}
.report-table td {
  color: rgba(244, 242, 255, 0.8);
}

/* generic content section, used by youtube/community/about pages */
.page-section {
  max-width: 880px;
  margin: 0 auto;
  padding: 0 24px 80px;
}
.page-section + .page-section {
  border-top: 1px solid var(--glass-border);
  padding-top: 56px;
}
.page-section h2 {
  margin: 0 0 16px;
  font-size: clamp(20px, 3vw, 26px);
  font-weight: 700;
  color: var(--ink);
}
.page-section__heading--center {
  text-align: center;
}
.page-section .page-section__heading--big {
  margin: 0 0 20px;
  font-size: clamp(26px, 4.5vw, 34px);
  font-weight: 700;
  color: #fff;
}
/* "ヒストリーのタイトルの上に小さく薄めに、タイトルと下の余白を詰めて" */
.history-eyebrow {
  display: block;
  margin: 0 0 2px;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: rgba(255, 255, 255, 0.4);
  text-align: center;
}
h3.page-section__heading--big {
  margin-top: 40px;
}
.page-section p {
  margin: 0 0 18px;
  font-size: 15px;
  line-height: 1.9;
  color: var(--ink-dim);
}
/* "この長いストーリーは一旦削除して、リーダーのみにする" -- about.php を
   SNS/リンクのハブページ化するにあたり、縦スクロールでの全文表示は
   非表示に。js/story-reader.js がこのDOMから章を集めて読み上げる
   仕組みなので、要素自体(と中の.story-model/.story-orbs)は残す。 */
.profile-story-source {
  display: none;
}
/* Matches the poem modal's .modal-body typography exactly (same
   font-family/size/weight/line-height). Originally literal white, dialed
   to a slight gray per instruction ("この文書のみ少しグレーにする"). */
.profile-story p {
  margin: 0 0 22px;
  font-family: 'M PLUS 1', 'Noto Sans JP', sans-serif;
  font-size: clamp(17px, 3.6vw, 19px);
  font-weight: 700;
  line-height: 1.9;
  color: #d4d4d4;
}
/* Bigger than the shared .profile-story size (特技 etc. keep the base
   size above) -- used by 生い立ち and the other narrative blocks
   (商売の話/7億円の地獄/見えないものを信じる/そしてルミネラボへ). */
.profile-story--lg p {
  font-size: clamp(20px, 4.6vw, 24px);
}
.profile-story p:last-child {
  margin-bottom: 0;
}
/* Visual break inside a narrative block -- "いまぽんとなる。の下に大きく
   余白を入れて薄いボーダーを". */
.profile-story__divider {
  /* A border here read as invisible in practice (--glass-border is a very
     faint rgba(255,255,255,0.09), fine for a bordered card but too subtle
     against this section's near-black background) -- "ボーダーは見えない、
     入れれないのなら大きく余白か改行に" -- just a big gap instead. */
  height: 80px;
}
/* HDR bloom orbs (js/story-orbs.js), transition strip between 生い立ち and
   the next chapter -- "HDRの発光球体を2、3個ふわふわと浮かせ". Reuses the
   same real-bloom engine as the 活動内容モーダルの「量子のゆらぎ」オーブ
   (js/modal-wisp.js), just a shorter standalone strip instead of a modal
   centerpiece. */
.story-orbs {
  position: relative;
  height: 200px;
  margin: 8px 0 0;
}
/* Taller for the V-formation reenactment (js/story-orbs.js) -- the
   two-arm shape + mountain silhouette need more vertical room to read
   clearly than the ambient 3-orb strip. */
.story-orbs--v {
  height: 280px;
}
/* 光る人物 -- a single standing figure reads better a bit taller/narrower
   than the wide ambient strip. */
.story-orbs--still {
  height: 260px;
  max-width: 220px;
  margin-left: auto;
  margin-right: auto;
}
/* 3Dモデルの粒子ホログラム(js/story-model.js) -- 章の中で空中浮遊する。
   .story-orbsと同じ帯レイアウトを流用しつつ、モデルが縦長でも収まる
   高さに。 */
.story-model {
  position: relative;
  height: 440px;
  margin: 8px 0 0;
}
/* "顕微鏡の上下の余白が広すぎる" -- 最初はcanvasの高さそのものを
   詰めたが、それだと透視投影のカメラ枠が縮む分モデル自体も一緒に
   縮んで見えてしまった("先程の大きさは良かった")。モデルの実寸は
   保ったまま、前後の段落との余白(margin)だけを詰める。 */
.story-model--compact {
  margin-top: -60px;
  margin-bottom: -60px;
}
/* さらに余白を狭めたい個別ケース用(標準のcompact比率-60pxはそのまま
   使い回すルールとして維持、これは特に指定された時だけの上乗せ)。 */
.story-model--tight {
  margin-top: -100px;
  margin-bottom: -100px;
}
/* 章の一番上(挿絵の下)に置く場合用、tightよりさらに詰める。 */
.story-model--tighter {
  margin-top: -160px;
  margin-bottom: -100px;
}
.story-model canvas {
  display: block;
  width: 100%;
  height: 100%;
}
/* リーダー表紙の陰陽星モデル -- .story-modelの標準サイズ(440px固定)より
   後にこのルールを書いて上書きし、画面下部に固定表示する。 */
/* "表示フィールドは画面全体" -- .story-modelの明示的なheight:440pxが
   inset:0より優先されてしまう(top/bottom両方指定でも、heightが明示
   されていればそちらが勝つ)ため、heightも明示的に上書きする。 */
.story-reader__cover-model {
  position: fixed;
  inset: 0;
  height: 100dvh;
  margin: 0;
  z-index: 0;
}
.story-orbs canvas {
  display: block;
  width: 100%;
  height: 100%;
}
/* Placeholder swapped for the real .story-orbs node the instant
   js/story-reader.js's render() runs (see there) -- same height so
   nothing visibly jumps in the moment before the swap. */
.story-orbs-slot {
  height: 200px;
}

/* Stories-style tap-through reader test (js/story-reader.js). Launch
   button sits right above 生い立ち; the reader itself is a full-screen
   fixed overlay built entirely in JS. */
.story-reader-launch {
  /* "このボタンをレイヤー的に天体望遠鏡の足の部分くらいまで被せる" --
     以前はmargin-topのマイナスでボタンを引き上げて重ねていたが、それだと
     文書フロー上の高さ計算にも影響し、望遠鏡の上下の余白を揃えようと
     しても揃わなかった("上下の余白を均等に")。position:absoluteで
     #hero-telescope-link(position:relative)の中に直接重ねる方式に変更、
     文書フローの高さ計算から完全に切り離す -- こうすれば親要素の
     margin-top/bottomを同じ値にするだけで純粋に上下対称になる。 */
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 12px 22px;
  border: 1px solid var(--glass-border);
  border-radius: 999px;
  /* backdrop-filterは使わない -- スクロール中の再合成コストがコンポジタ
     スレッドを専有し、スクロールが固まる原因になることが実機で確認
     済み(CLAUDE.md参照、nav.is-scrolledで同じ理由により既に排除済み)。
     不透明に近い単色背景で代用する。"透明度をもう少し上げて"(=もっと
     透けさせて)なので0.88→0.6に。 */
  background: rgba(8, 8, 10, 0.6);
  color: #fff;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 15px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.2s ease;
  /* "ゆっくりイージングをかけて呼吸してるように拡大縮小し、うっすら
     青白い外光が入る" -- transform/box-shadowだけのcompositor完結
     アニメーションなので常時ループでも軽い。 */
  animation: story-reader-breathe 3.6s ease-in-out infinite;
}
.story-reader-launch:active {
  animation-play-state: paused;
  transform: translateX(-50%) scale(0.97);
}
@keyframes story-reader-breathe {
  0%, 100% {
    transform: translateX(-50%) scale(1);
    box-shadow: 0 0 0 rgba(188, 216, 255, 0);
  }
  50% {
    transform: translateX(-50%) scale(1.045);
    box-shadow: 0 0 18px 4px rgba(188, 216, 255, 0.28);
  }
}
/* "ストーリーで読むの下に赤い四角のYOUTUBE特別ボタン" -- ヘッダーの
   nav__live-badgeと同系色(#e11d3c)だが、こちらは押しやすい大きさの
   角丸スクエアボタン。 */
.about-youtube-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 20px auto 14px;
  padding: 14px 22px;
  border-radius: 10px;
  background: #e11d3c;
  color: #fff;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 20px;
  font-weight: 700;
  text-decoration: none;
  transition: background 0.2s ease, transform 0.2s ease;
}
.about-youtube-btn i {
  font-size: 24px;
}
.about-youtube-btn:hover {
  background: #c9152f;
}
.about-youtube-btn:active {
  transform: scale(0.97);
}
/* "AI目線・ビズコミュ・ルミネラボの3サイト共通バナーをホームへのリンク
   の上に、余白を入れ薄いボーダーを上下に" */
.about-sister-banners__heading {
  margin: 0 0 -4px;
  padding-top: 24px;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--text-dim, rgba(255, 255, 255, 0.55));
  border-top: 1px solid var(--glass-border);
}
.about-sister-banners {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin: 0 0 28px;
  padding: 12px 0 20px;
  border-bottom: 1px solid var(--glass-border);
}
.about-sister-banners a:last-child {
  grid-column: 1 / -1;
}
.about-sister-banners img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
  opacity: 0.85;
  transition: opacity 0.2s ease;
}
.about-sister-banners a:not(:last-child) img {
  border-radius: 5px;
}
.about-sister-banners a:hover img {
  opacity: 1;
}
/* "←ホームへ、戻るボタン系の小型のグレイなアイコンボタン" -- TOPへの
   控えめな小型ピル(YouTubeボタンほど強調しない、あくまで戻る導線)。 */
.about-index-link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  width: fit-content;
  margin: 0 auto 28px;
  padding: 7px 14px;
  border: 1px solid var(--glass-border);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  color: rgba(255, 255, 255, 0.55);
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 12px;
  font-weight: 700;
  text-decoration: none;
  transition: background 0.2s ease, color 0.2s ease;
}
.about-index-link i {
  font-size: 13px;
}
.about-index-link:hover {
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.85);
}

/* One slide = one whole chapter now, not one paragraph -- "一回に出す
   文字が少ないので、もう出して" -- so this is a scrollable reading pane
   per chapter (basically a normal read, just chunked and swipeable
   between chapters) rather than a centered short-phrase card. */
.story-reader {
  position: fixed;
  inset: 0;
  z-index: 200000;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  padding: max(18px, env(safe-area-inset-top)) 0 max(18px, env(safe-area-inset-bottom));
  background: #050507;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
  /* No overflow-x was set anywhere in here -- if content ever overflows
     horizontally by even a stray pixel right as the pane opens/re-renders,
     iOS rubber-bands the whole thing side to side ("開いた直後に左右に
     ガタつく"). Closing that off entirely rather than chasing the exact
     pixel is the safe fix. */
  overflow-x: hidden;
}
.story-reader.is-open {
  opacity: 1;
  pointer-events: auto;
}
/* Per-chapter background illustration test ("セクションとテストで何か
   挿絵いれてみて") -- sits behind everything else in the overlay, dimmed
   so text stays readable on top, fades in/out between chapters via
   render() toggling .is-visible rather than popping. */
.story-reader__illustration {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transition: opacity 0.6s ease;
}
.story-reader__illustration::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(5, 5, 7, 0.72);
}
.story-reader__illustration.is-visible {
  opacity: 1;
}
.story-reader__label {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  margin: 22px 28px 4px;
  text-align: center;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.5);
  /* Each new chapter fades in rather than popping to full opacity
     instantly ("章のページセクションの表示、フェードインから表示が
     出るように") -- js/story-reader.js's render() clears .is-entered on
     swap, then adds it back a frame later so this transition has
     something to animate from. */
  opacity: 0;
  transition: opacity 0.5s ease;
}
.story-reader__label.is-entered {
  opacity: 1;
}
/* Title-card interstitial -- chapter switch shows ONLY the chapter title,
   full-screen, before the content fades in ("一枚のセッションタイトルしか
   出ない表示のページが出てから中身にいく"). Sits above everything else in
   the overlay while .is-visible, tap or the TITLECARD_MS timer in
   js/story-reader.js dismisses it into revealContent(). */
.story-reader__titlecard {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 0 32px;
  background: #050507;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease;
  text-align: center;
}
.story-reader__titlecard.is-visible {
  opacity: 1;
  pointer-events: auto;
}
/* "第一章"のような章番号。空(エンディングページ)の時はレイアウトに
   隙間を残さない。 */
.story-reader__titlecard-num {
  display: block;
  font-family: 'Noto Sans JP', sans-serif;
  font-size: clamp(13px, 3.4vw, 16px);
  font-weight: 700;
  letter-spacing: 0.2em;
  color: rgba(255, 255, 255, 0.5);
}
.story-reader__titlecard-num:empty {
  display: none;
}
.story-reader__titlecard-text {
  font-family: 'Noto Sans JP', sans-serif;
  /* "タイトルはもう少し文字を大きく" -- 24-34px→30-44px */
  font-size: clamp(30px, 8.6vw, 44px);
  font-weight: 700;
  line-height: 1.5;
  color: #fff;
}
/* Bottom footer nav -- close pinned at the far left, chapter names scroll
   horizontally to its right ("リーダーの下にフッターをいれ、スクロールで
   各セクション名が出る。1番左は閉じるで固定"). Replaces the old top
   segmented progress bar entirely. */
.story-reader__footer {
  position: relative;
  z-index: 2;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  /* Close button and the tab row sat right up against each other, easy to
     fat-finger the wrong one ("左の閉じると右のメニュー群が近すぎて誤操作
     しそう") -- a real gap between them, not just the row's own internal
     tab-to-tab spacing. */
  gap: 18px;
  margin-top: 10px;
  padding: 0 12px;
}
/* "メニューもなんも出ないのでスクロールもどこにもいけないエラー" --
   閉じるボタンごとスクロール発見待ちにしていたら、スクロール不要な
   短い章やページで本当に抜け出せない詰みが起きた。閉じるボタンは常時
   表示に戻し(退避口として必須)、章タブの一覧だけをスクロール待ちに
   する。 */
.story-reader__footer-scroll {
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
  transition: opacity 0.4s ease, transform 0.4s ease;
}
.story-reader__footer.is-revealed .story-reader__footer-scroll {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
/* 表紙では章タブの一覧を出さない -- "下のメニューが見えない方がいいね"
   (まだ読んでない章名が見えてしまうのも避けたい)。閉じるボタンだけは
   表紙でも押せるよう残す。 */
.story-reader__footer-scroll--hidden {
  visibility: hidden;
  pointer-events: none;
}
/* "ここは閉じるも奪う" -- 表紙では閉じるボタンも出さない */
.story-reader__footer-close--hidden {
  visibility: hidden;
  pointer-events: none;
}
.story-reader__footer-close {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: none;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: 18px;
  -webkit-tap-highlight-color: transparent;
}
.story-reader__footer-close:active {
  transform: scale(0.94);
}
.story-reader__footer-scroll {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  gap: 6px;
  overflow-x: auto;
  overflow-y: hidden;
  touch-action: pan-x;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: 0 4px;
}
.story-reader__footer-scroll::-webkit-scrollbar {
  display: none;
}
/* "パソコン版だけリーダー下のメニューが左寄せになってる" -- 横スクロール
   前提のモバイルはflex-startのままでよいが、PC幅は行に収まりきって
   スクロールしないぶん、左に寄って見えてしまっていた。 */
@media (min-width: 768px) {
  .story-reader__footer-scroll {
    justify-content: center;
  }
}
.story-reader__footer-item {
  flex: 0 0 auto;
  border: none;
  border-radius: 999px;
  padding: 9px 16px;
  background: rgba(255, 255, 255, 0.06);
  color: rgba(255, 255, 255, 0.55);
  font-family: 'Noto Sans JP', sans-serif;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  /* Active tab eases into the highlighted state and scrollIntoView's
     itself into place on chapter change (js/story-reader.js's render()) --
     "セクションにいくとそのスライダーメニューはその位置までイージングで
     移動しアクティブを強調". */
  transition: background-color 0.3s ease, color 0.3s ease;
}
.story-reader__footer-item.is-active {
  /* 純白だと発色が強すぎて目を奪う("右下のスライドメニューの色が白す
     ぎる") -- ギリギリ判別できる暗さまでグレーを寄せる。 */
  background: #6e6e6e;
  color: #fff;
}
.story-reader__text {
  position: relative;
  z-index: 1;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  touch-action: pan-y; /* vertical-mode's horizontal reading scroll is currently disabled (縦書きボタン削除), so only vertical panning is ever intended here */
  -webkit-overflow-scrolling: touch;
  margin: 0 auto;
  padding-top: 12px;
  padding-bottom: 40px;
  /* Explicit left/right instead of the shorthand's horizontal value --
     reported visibly stuck to the left edge on a real device
     ("がっつりひっついてる") despite measuring correctly symmetric
     (28px/28px) in sandbox testing; spelling both sides out individually
     removes any doubt about which physical side each value lands on. */
  padding-left: 28px;
  padding-right: 28px;
  width: 100%;
  max-width: 560px;
  box-sizing: border-box;
  font-family: 'M PLUS 1', 'Noto Sans JP', sans-serif;
  font-size: clamp(25px, 6.2vw, 31px);
  font-weight: 700;
  line-height: 1.85;
  color: #fff;
  text-align: left;
  /* Fades in with the label above on each chapter swap -- see
     .story-reader__label's comment. */
  opacity: 0;
  transition: opacity 0.5s ease;
  /* Tried native scroll-snap (proximity) here for a soft per-paragraph
     "catch" -- with these short, tightly-packed lines the snap points sat
     too close together, so it fought every small scroll attempt instead
     of just gently catching ("ちと固いな。またスクロールしなくなる").
     Reverted to plain free scroll; the spotlight highlight is doing the
     "which line is current" job on its own already, and the browser's
     own momentum deceleration already gives the "ゆっくりからそっと停止"
     feel without any artificial snap fighting it.
     Fade mask top/bottom: a still-bright/active paragraph scrolling out
     past the edge used to just vanish off-screen still lit up
     ("アクティブなのにスライドで上に消えていく") -- masking it to
     transparent as it nears the edge dims it out gracefully first,
     independent of the discrete is-current state.
     REMOVED as a live experiment (2026-08-21) -- every other suspect for
     "reader won't scroll" (background canvases, __scenePaused, the edge
     tap zones) has now been ruled out by hard data in the jank log
     (scenePaused:true, nearBlocks empty, yet still stuck at scattered
     positions with dozens of touchmoves). The chapters got much longer in
     the latest content rewrite (~70 paragraphs, 5000px+ scrollHeight) --
     `mask-image` over a tall, constantly-recomposited scroll surface with
     several paragraphs transitioning at once is GPU-compositor work that
     scales with content length, unlike anything else still active here.
     This was never actually confirmed cheap on a real device at this
     length ("costs nothing extra" above was asserted, not measured) --
     removing it is a genuine test, not a confirmed fix. If scrolling is
     still stuck after this, it's not the mask and this should go back. */
}
/* エンディング(ありがとう)だけ中央寄せ -- "と中央寄せで" */
.story-reader__text--centered {
  text-align: center;
  /* 文面を長めの言い回しに変更した際「改行にならない程度の文字の
     大きさで」と指定されたため、このスライドだけ基本サイズより
     一段小さくして折り返しを避ける。 */
  font-size: clamp(20px, 5vw, 25px);
}
/* 縦書きテスト ("これさ縦書きバージョンとかもできるかな？") -- columns
   run top-to-bottom, stacking right-to-left, so the scroll axis flips
   from vertical to horizontal; the fade mask flips to left/right to
   match. js/story-reader.js's updateSpotlight() branches on this same
   `vertical` flag to read left/width instead of top/height. */
.story-reader__text.is-vertical {
  writing-mode: vertical-rl;
  overflow-x: auto;
  overflow-y: hidden;
  height: 100%;
  max-height: 100%;
  max-width: none;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 60px, #000 calc(100% - 70px), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 60px, #000 calc(100% - 70px), transparent 100%);
}
/* Fills the blank lead-in spacer every chapter starts with (js/story-
   reader.js's render() -- the spacer itself exists just to push the first
   paragraph down to the spotlight focus line, but that left it dead empty
   space every single time -- "全てのスタート位置が大きく空白になってる")
   with the same いまぽんmark used as index.php's .modal-mark. */
.story-reader__lead {
  /* % of textEl's own height, not a JS-computed px snapshot -- iOS
     Safari's address bar collapsing/expanding resizes the viewport WHILE
     reading, and a px value frozen at the moment a chapter opened went
     stale the instant that happened (confirmed via two screenshots of the
     same chapter/scroll position with visibly different spacing depending
     on the toolbar's state). % recomputes for free on every reflow.
     38% matches js/story-reader.js's FOCUS_FRAC -- keep in sync if that
     ever changes. */
  height: 38%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.story-reader__trail {
  height: 62%; /* 1 - FOCUS_FRAC, see .story-reader__lead above */
}
.story-reader__lead-mark {
  display: block;
  width: clamp(64px, 22vw, 110px);
  height: auto;
  opacity: 1;
  /* iOS Safari can hijack a touch that lands on an <img> (long-press
     callout / native drag) instead of passing it through to the scroll
     pane underneath -- this sits right at the top of every chapter, so a
     swipe starting on it was exactly reproducing the reader-stuck jank-log
     pattern (pos:0, only a couple touchmoves before it reads as frozen).
     pointer-events:none removes it from hit-testing entirely; the other
     two are belt-and-suspenders for the same class of hijack. */
  pointer-events: none;
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
  -webkit-user-select: none;
  user-select: none;
}
.story-reader__text p {
  margin: 0 0 24px;
  /* Apple Music lyrics style: dimmed/smaller until it's one of the ~3
     paragraphs around the focus line (js/story-reader.js's
     updateSpotlight), then brightens up -- transition on transform+
     opacity+color is what makes it read as sliding smoothly into focus
     rather than a hard cut. transform-origin left so it grows in place
     rather than visually drifting rightward (this text is left-aligned).
     A gentle symmetric S-curve (slow-fast-slow, no overshoot) rather than
     a sharp ease -- "動きはイージングいれて、いけるならガウス" -- reads
     softer than the site's usual snap-into-place expo-out curve.
     No filter:blur() here (an earlier version had one) -- with a 3-line
     active window instead of 1, several paragraphs can be transitioning
     filter at once WHILE the pane is actively being scrolled, not just
     once on scroll-into-view. CLAUDE.md documents that exact combination
     (filter transitions animating during live scroll) as a real confirmed
     jank source elsewhere on this site ("スライドしなくなる"), so it's
     left out here rather than re-risking it. */
  transform-origin: left center;
  transform: scale(0.9);
  opacity: 0.32;
  color: #9a9a9a;
  transition:
    transform 0.55s cubic-bezier(0.45, 0.05, 0.15, 0.95),
    opacity 0.55s cubic-bezier(0.45, 0.05, 0.15, 0.95),
    color 0.55s cubic-bezier(0.45, 0.05, 0.15, 0.95);
}
.story-reader__text p.is-current {
  transform: scale(1);
  opacity: 1;
  /* 純白だと発色が強すぎて読みにくい -- ごく僅かにグレーを混ぜて和らげる。 */
  color: #f0f0f0;
}
.story-reader__text p:last-child {
  margin-bottom: 0;
}
.story-reader__text.is-entered {
  opacity: 1;
}
/* Edge-tap prev/next used to be a pair of divs (.story-reader__zone)
   layered on top of .story-reader__text -- removed. Being a SIBLING of
   the text pane, not an ancestor, a touch starting on one of those divs
   could never scroll the pane underneath it (a browser resolves a
   touch's scroll target by walking UP the tree from whatever it hit),
   making the left/right ~13% of every chapter a scroll dead zone no
   matter what touch-action was set to. js/story-reader.js now handles
   edge taps by position directly in its touch handler instead, so
   nothing ever sits on top of the pane to begin with.
*/

/* 生い立ち's background particle drift (js/story-particles.js) -- canvas
   sits behind the heading/text, text stays readable on top. */
.story-wrap {
  position: relative;
}
.story-particles-cv {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.story-wrap > h3,
.story-wrap > .profile-story {
  position: relative;
  z-index: 1;
}

/* about.php profile block -- simple label/value rows, no table semantics
   needed (it's a short fixed list, not tabular data). */
.profile-info {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0 0 28px;
  padding: 20px 22px;
  border: 1px solid var(--glass-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
  transition: background 0.2s ease, transform 0.2s ease;
}
/* "半ば強引なんだけど、このBOX全体もリンクにして、このリーダーにつなげて"
   -- プロフィール情報ボックス全体をタップでもストーリーリーダーが開く。 */
.profile-info[role="button"] {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.profile-info[role="button"]:hover {
  background: rgba(255, 255, 255, 0.06);
}
.profile-info[role="button"]:active {
  transform: scale(0.99);
}
/* "押したくなるようなアクションやパーティクル表現でクリック率をUP" --
   輪郭のソフトなグローリングを数回だけパルスさせてタップを誘う。
   パフォーマンス方針(永久ループ禁止)を守り、animation-iteration-countを
   有限回にして着地後は完全に静止させる(CSSのtransform/opacityのみ、
   compositorだけで完結する軽量アニメーションなのでcanvas/rAFループとは
   別物として扱ってよい)。 */
#hero-telescope-link {
  position: relative;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* "ヒストリーのタイトルと赤ボタンの中の天体望遠鏡の上下に余白" --
     中のcanvas(.story-model--compact)が-60pxマージンを持つため、
     display:flow-rootでこの要素をブロック整形コンテキストにし、
     子のマージンがここの外まで浸み出す(collapse)のを防いだ上で
     余白を指定する。 */
  display: flow-root;
  margin: 24px 0 68px;
}
#hero-telescope-link:active {
  transform: scale(0.98);
}
/* "天体望遠鏡の後ろにルミネラボのイコライザーを入れてボタンを促す" --
   中心から放射状に伸びるバーが明滅する軽量CSS版。canvasより先に書いて
   あるのでz-indexなしで自然に背後に来る。 */
.telescope-eq {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 1px;
  height: 1px;
  pointer-events: none;
}
.telescope-eq__bar {
  /* "イコライザーの位置が中心じゃない" -- 以前はtransform-originを
     "center 90px"のようにズラしてtranslateYで半径を出そうとしていたが、
     originのズレとtranslateYの組み合わせが正しい円にならず歪んでいた。
     bar自体をtop:50%/left:50%+負のマージンでアンカー点(親の中心)に
     正確に重ね、originを既定の50% 50%(=bar自身の中心)にしてから
     rotate→translateYする、という素直な円配置に直す。 */
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1px;
  height: 16px;
  margin: -8px 0 0 -0.5px;
  border-radius: 0;
  /* "もっと1pxの滲んでないラインで鮮明に水色" -- グラデーションでの
     フェードやdrop-shadowのぼかしをやめ、はっきりした水色の単色線に。
     "虹色に色がランダムで混ざり変わる" -- 彩度の高い基準色を
     filter:hue-rotate()で回し続ける(色そのものを毎フレーム書き換える
     よりcompositorだけで完結して軽い)。バーごとに開始角度・周期が
     違うので、隣同士の色がズレ続けて混ざって見える。 */
  background: hsl(190, 95%, 65%);
  filter: hue-rotate(var(--hue0));
  transform-origin: 50% 50%;
  transform: rotate(calc(360deg / var(--n) * var(--i))) translateY(-80px) scaleY(var(--h));
  opacity: 0.3;
  animation: telescope-eq-pulse var(--dur) ease-in-out infinite,
    telescope-eq-hue var(--hueDur) linear infinite;
  animation-delay: calc(var(--i) * -0.09s), 0s;
}
@keyframes telescope-eq-pulse {
  0%, 100% { transform: rotate(calc(360deg / var(--n) * var(--i))) translateY(-80px) scaleY(var(--h)); opacity: 0.22; }
  50% { transform: rotate(calc(360deg / var(--n) * var(--i))) translateY(-80px) scaleY(calc(var(--h) * 2.1)); opacity: var(--peak); }
}
@keyframes telescope-eq-hue {
  0% { filter: hue-rotate(var(--hue0)); }
  100% { filter: hue-rotate(calc(var(--hue0) + 360deg)); }
}
#hero-telescope-link::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 140px;
  height: 140px;
  margin: -70px 0 0 -70px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(188, 216, 255, 0.35) 0%, rgba(188, 216, 255, 0) 70%);
  pointer-events: none;
  opacity: 0;
  transform: scale(0.6);
  animation: telescope-invite-pulse 1.8s ease-out 1.2s 3;
}
@keyframes telescope-invite-pulse {
  0% { opacity: 0; transform: scale(0.6); }
  30% { opacity: 0.9; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.5); }
}
.profile-info__row {
  display: flex;
  gap: 18px;
  font-size: 14px;
  line-height: 1.7;
}
.profile-info__row dt {
  flex: 0 0 84px;
  color: var(--ink-dim);
  font-weight: 500;
}
.profile-info__row dd {
  margin: 0;
  color: var(--ink);
}

/* Scroll-triggered reveal for the profile/生い立ち blocks (#profile-section
   only -- 特技 etc. keep the plain fade the rest of the page uses). Each row
   is hidden until js/profile-reveal.js's IntersectionObserver adds .is-in.
   A soft upward drift rather than a hard left/right slide -- the slide read
   as too mechanical for text ("ある程度固まった文字が動くので固い感じする"),
   so this leans on scale/opacity together with a slow, no-overshoot ease so
   each line settles gently instead of snapping in.
   Originally also had a filter: blur() cross-fade, but a live jank-log
   check (2026-08-20) showed this exact section correlating with real
   frame-gap stalls while scrolling (nearProfile:true on nearly every
   entry) -- CSS filter/backdrop-filter blur is a known jank source on this
   site (see the nav.is-scrolled fix elsewhere in this file), and animating
   it on up to ~20 elements at once during scroll was the same class of
   problem. Dropped the blur, kept the rest.
   One-shot per element (observer unobserves after firing) -- no idle
   animation once landed, per the site's usual perf rule. */
#profile-section .profile-info__row,
.profile-story--lg p {
  opacity: 0;
  transform: translateY(22px) scale(0.98);
  transition:
    opacity 1.1s cubic-bezier(0.22, 1, 0.36, 1),
    transform 1.1s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform, opacity;
}
#profile-section .profile-info__row.is-in,
.profile-story--lg p.is-in {
  opacity: 1;
  transform: translateY(0) scale(1);
}
.profile-info__row--special {
  margin-top: 4px;
  padding-top: 14px;
  border-top: 1px solid var(--glass-border);
}
.profile-info__row--special dd {
  line-height: 1.8;
}
/* "スピエンスとは" -- LLM/検索が「スピエンス」という語を単独で引用・
   要約しやすいよう、プロフィールの下に短い定義文を独立して置く。 */
.about-spience {
  max-width: 560px;
  margin: 24px auto 0;
  text-align: center;
}
.about-spience__heading {
  margin: 0 0 8px;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ink);
}
.about-spience__body {
  margin: 0;
  font-size: 14px;
  line-height: 1.9;
  color: var(--text-dim, rgba(255, 255, 255, 0.7));
  /* "す。だけ落ちてる" -- 最終行に1〜2文字だけ残る孤立行(text
     orphan)を、対応ブラウザでは自動で回避する。 */
  text-wrap: pretty;
}

/* live banner (youtube.php) */
.live-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  width: calc(100% - 48px);
  max-width: 832px;
  margin: 0 auto 32px;
  padding: 14px 20px;
  border-radius: 14px;
  background: rgba(255, 60, 60, 0.08);
  border: 1px solid rgba(255, 60, 60, 0.3);
}
.live-banner__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #ff4d4d;
  box-shadow: 0 0 10px 2px rgba(255, 77, 77, 0.7);
  animation: pulse-dot 1.4s ease-in-out infinite;
  flex: none;
}
.live-banner__label {
  font-weight: 700;
  color: #ff8080;
  flex: none;
  font-size: 13.5px;
}
.live-banner__title {
  flex: 1 1 100%;
  order: 3;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-dim);
  font-size: 13px;
}
.live-banner__link {
  flex: none;
  margin-left: auto;
  padding: 6px 14px;
  border-radius: 999px;
  background: rgba(255, 77, 77, 0.18);
  border: 1px solid rgba(255, 77, 77, 0.4);
  color: var(--ink);
  font-weight: 600;
  font-size: 13px;
  text-decoration: none;
  transition: background 0.2s ease;
}
.live-banner__link:hover {
  background: rgba(255, 77, 77, 0.3);
}

/* YouTube list */
.video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 18px;
}
.video-card {
  display: block;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--glass);
  border: 1px solid var(--glass-border);
  text-decoration: none;
  transition: border-color 0.25s, transform 0.25s var(--ease);
}
.video-card:hover {
  border-color: var(--accent);
  transform: translateY(-3px);
}
.video-card__thumb-wrap {
  position: relative;
}
.video-card__thumb {
  aspect-ratio: 16 / 9;
  width: 100%;
  object-fit: cover;
  display: block;
  background: var(--bg-2);
}
.video-card__play {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 40px;
  height: 40px;
  font-size: 40px;
  line-height: 1;
  color: rgba(255, 255, 255, 0.9);
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 0.2s;
  pointer-events: none;
}
.video-card:hover .video-card__play {
  opacity: 1;
}
.video-card__title {
  padding: 14px 16px 18px;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.6;
  color: var(--ink);
}

/* js/from-site.js -- 3サイト(AI目線・ビズコミュ・ルミネラボ)から
   ?from=経由で着地した人が迷子にならないよう、常に戻れる場所を示す
   常設バー。ナビ(z-index:40)より前、モーダル/リーダー(60〜)より
   後ろになる帯。 */
.from-site-bar {
  /* index.php下部中央のfixed-cta(z-index:60)、リーダーのフッターと
     衝突しない位置として、下ではなくナビ直下(上部)に置く。z-indexは
     ハンバーガーメニュー(.nav__menu:39 / .nav__menu-backdrop:38)より
     確実に低くして、メニューを開いた時にバーが前面に出てしまわない
     ようにする(通常コンテンツのz-indexは概ね5以下なので20で十分前面)。 */
  position: fixed;
  left: 12px;
  top: calc(64px + env(safe-area-inset-top, 0px));
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 6px 4px 4px;
  border-radius: 999px;
  background: rgba(10, 10, 14, 0.82);
  border: 1px solid var(--from-site-color, var(--glass-border));
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(6px);
}
.from-site-bar__link {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px 12px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--from-site-color, #888) 22%, transparent);
  color: var(--ink);
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.from-site-bar__close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-faint);
  font-size: 14px;
  cursor: pointer;
  flex: none;
}
.from-site-bar__close:hover {
  color: var(--ink-dim);
}
.video-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 24px;
  margin-top: 32px;
}
.video-pager__link {
  display: flex;
  align-items: center;
  gap: 2px;
  color: var(--ink);
  font-weight: 600;
  font-size: 14px;
  text-decoration: none;
}
.video-pager__link--disabled {
  color: var(--ink-faint);
  pointer-events: none;
}
.video-pager__count {
  font-size: 13px;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

/* contact form */
.contact-form {
  max-width: 560px;
  margin: 0 auto;
  padding: 0 24px 80px;
  display: flex;
  flex-direction: column;
  gap: 22px;
}
.contact-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.contact-field label {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-dim);
}
.contact-field--required label::after {
  content: ' *';
  color: var(--accent-3);
}
.contact-field input,
.contact-field select,
.contact-field textarea {
  width: 100%;
  padding: 13px 16px;
  border-radius: 12px;
  border: 1px solid var(--glass-border);
  background: var(--glass);
  color: var(--ink);
  font-family: inherit;
  font-size: 15px;
  transition: border-color 0.2s;
}
.contact-field input:focus,
.contact-field select:focus,
.contact-field textarea:focus {
  outline: none;
  border-color: var(--accent);
}
.contact-field textarea {
  resize: vertical;
  min-height: 140px;
  line-height: 1.7;
}
/* Honeypot -- real users never see or fill this in; visually and from
   assistive tech it's just absent, not merely opacity:0 (which some bots
   skip filling anyway, but position:absolute off-screen catches more). */
.contact-field--trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.contact-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 4px;
  padding: 15px 24px;
  border-radius: 999px;
  border: none;
  background: var(--accent);
  color: #fff;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s, transform 0.2s var(--ease);
}
.contact-submit:hover {
  background: var(--accent-2);
  transform: translateY(-2px);
}
.contact-note {
  font-size: 12.5px;
  line-height: 1.8;
  color: var(--ink-faint);
}
.contact-error {
  padding: 14px 18px;
  border-radius: 12px;
  background: rgba(255, 90, 90, 0.1);
  border: 1px solid rgba(255, 90, 90, 0.3);
  color: #ffb3b3;
  font-size: 13.5px;
  line-height: 1.7;
}

/* thanks page */
.thanks-hero {
  max-width: 560px;
  margin: 0 auto;
  padding: 120px 24px 100px;
  text-align: center;
}
.thanks-hero__icon {
  font-size: 40px;
  margin-bottom: 20px;
}
.thanks-hero p {
  margin: 0 0 14px;
  font-size: 15px;
  line-height: 1.9;
  color: var(--ink-dim);
}
.thanks-hero .contact-note {
  margin-top: 24px;
}

/* ==========================================================================
   ログイン / ログアウト（ルミネラボAIの入口）
   ========================================================================== */

.auth {
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: 32px 20px;
}

.auth__panel {
  width: min(420px, 100%);
  padding: 36px 28px 30px;
  background: var(--glass);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  text-align: center;
}

.auth__title {
  margin: 0 0 6px;
  font-size: 1.35rem;
  letter-spacing: 0.06em;
  color: var(--ink);
}

.auth__lead {
  margin: 0 0 24px;
  font-size: 0.82rem;
  color: var(--ink-dim);
}

.auth__error {
  margin: 0 0 18px;
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(255, 90, 90, 0.1);
  border: 1px solid rgba(255, 90, 90, 0.28);
  color: #ffb4b4;
  font-size: 0.8rem;
  text-align: left;
}

.auth__form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  text-align: left;
}

.auth__label {
  font-size: 0.72rem;
  color: var(--ink-faint);
  letter-spacing: 0.04em;
  margin-top: 10px;
}

.auth__input {
  width: 100%;
  padding: 11px 13px;
  font-size: 0.92rem;
  font-family: inherit;
  color: var(--ink);
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid var(--glass-border);
  border-radius: 11px;
  transition: border-color 0.25s var(--ease), box-shadow 0.25s var(--ease);
}

.auth__input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(61, 139, 255, 0.16);
}

.auth__submit {
  margin-top: 22px;
  padding: 12px 16px;
  font-size: 0.9rem;
  font-family: inherit;
  font-weight: 600;
  letter-spacing: 0.05em;
  color: var(--ink);
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  border: none;
  border-radius: 11px;
  cursor: pointer;
  transition: transform 0.25s var(--ease), filter 0.25s var(--ease);
}

.auth__submit:hover { filter: brightness(1.12); }
.auth__submit:active { transform: scale(0.985); }

.auth__note {
  margin: 20px 0 0;
  font-size: 0.72rem;
  color: var(--ink-faint);
}

.auth__note a { color: var(--accent-3); }

/* ==========================================================================
   ルミネラボAI 音声インターフェース（luminelabo.php・ログイン時のみ）
   状態色: 聞いている=青 / 考えている=青紫 / 話している=紫
   ========================================================================== */

.voice {
  --v-level: 0;                       /* js/lumine-voice.js が毎フレーム入れる音量 */
  --v-hue: 205deg;                    /* 既定は青。話すときは紫へ寄る */
  --v-glow: rgba(61, 139, 255, 0.55);
  position: fixed;
  left: 50%;
  bottom: max(24px, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  width: min(680px, calc(100% - 32px));
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  transition: --v-glow 0.6s var(--ease);
}

body[data-voice-mode="speaking"] .voice { --v-hue: 272deg; --v-glow: rgba(150, 88, 255, 0.6); }
body[data-voice-mode="thinking"] .voice { --v-hue: 240deg; --v-glow: rgba(104, 116, 255, 0.5); }

.voice__log {
  width: 100%;
  max-height: 34vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0 2px;
  -webkit-overflow-scrolling: touch;
}

.voice__log:empty { display: none; }

.voice__log__row,
.voice-log__row {
  max-width: 86%;
  padding: 9px 13px;
  border-radius: 14px;
  font-size: 0.86rem;
  line-height: 1.65;
  white-space: pre-wrap;
  word-break: break-word;
  animation: voiceRowIn 0.4s var(--ease) both;
}

@keyframes voiceRowIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

.voice-log__row--you {
  align-self: flex-end;
  background: rgba(61, 139, 255, 0.16);
  border: 1px solid rgba(61, 139, 255, 0.3);
  color: var(--ink);
}

.voice-log__row--ai {
  align-self: flex-start;
  background: rgba(150, 88, 255, 0.13);
  border: 1px solid rgba(150, 88, 255, 0.26);
  color: var(--ink);
}

.voice__bar {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
}

/* --- マイクボタン。音量に合わせてリングが広がる --- */
.voice__mic {
  position: relative;
  flex: none;
  width: 54px;
  height: 54px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--glass-border);
  background: var(--glass);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: var(--ink);
  cursor: pointer;
  transition: border-color 0.3s var(--ease), background 0.3s var(--ease);
}

.voice__mic:hover { background: var(--glass-hover); }

.voice__mic.is-active {
  border-color: hsl(var(--v-hue) 100% 70% / 0.6);
  background: hsl(var(--v-hue) 100% 60% / 0.14);
}

/* 声の大きさで広がる同心円。--v-level が 0 のときは出ない */
.voice__mic-ring {
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 1px solid hsl(var(--v-hue) 100% 72% / calc(0.15 + var(--v-level) * 0.55));
  transform: scale(calc(1 + var(--v-level) * 0.5));
  box-shadow: 0 0 calc(10px + var(--v-level) * 40px) var(--v-glow);
  opacity: calc(0.25 + var(--v-level) * 0.75);
  pointer-events: none;
}

.voice__mic-icon {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- テキスト入力（音声が繋がるまでの併用手段） --- */
.voice__field {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 4px 4px 14px;
  border-radius: 999px;
  border: 1px solid var(--glass-border);
  background: var(--glass);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.voice__input {
  flex: 1;
  min-width: 0;
  padding: 9px 0;
  border: none;
  background: none;
  color: var(--ink);
  font-family: inherit;
  font-size: 0.88rem;
}

.voice__input:focus { outline: none; }
.voice__input::placeholder { color: var(--ink-faint); }

.voice__send {
  flex: none;
  padding: 8px 16px;
  border: none;
  border-radius: 999px;
  background: hsl(var(--v-hue) 90% 58% / 0.85);
  color: #fff;
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  transition: filter 0.25s var(--ease);
}

.voice__send:hover { filter: brightness(1.12); }

.voice__status {
  margin: 0;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: hsl(var(--v-hue) 90% 78% / 0.85);
}

.voice__note {
  margin: 0;
  font-size: 0.72rem;
  color: #ffb4b4;
}

.voice__note:empty { display: none; }

.voice__account {
  position: fixed;
  top: max(14px, env(safe-area-inset-top));
  right: 16px;
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.7rem;
  color: var(--ink-faint);
}

.voice__account a { color: var(--ink-dim); }

@media (max-width: 520px) {
  .voice__field { padding-left: 12px; }
  .voice__send { padding: 8px 12px; }
  .voice__log { max-height: 28vh; }
}

@media (prefers-reduced-motion: reduce) {
  .voice-log__row { animation: none; }
  .voice__mic-ring { transform: none; }
}

/* ==========================================================================
   観測ダッシュボード（dashboard.php・ログイン時のみ）
   ========================================================================== */

.dash {
  max-width: 1080px;
  margin: 0 auto;
  padding: 28px 18px 60px;
}

.dash__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 22px;
}

.dash__title {
  margin: 0 0 4px;
  font-size: 1.2rem;
  letter-spacing: 0.05em;
}

.dash__sub {
  margin: 0;
  font-size: 0.76rem;
  color: var(--ink-dim);
}

.dash__pill {
  display: inline-block;
  padding: 1px 9px;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 600;
}

.dash__pill.is-ok   { background: rgba(52, 211, 153, 0.14); color: #6ee7b7; }
.dash__pill.is-warn { background: rgba(251, 191, 36, 0.14); color: #fcd34d; }

.dash__nav {
  display: flex;
  gap: 14px;
  font-size: 0.76rem;
}

.dash__nav a { color: var(--ink-dim); }
.dash__nav a:hover { color: var(--ink); }

.dash__error {
  padding: 12px 14px;
  border-radius: 12px;
  background: rgba(255, 90, 90, 0.1);
  border: 1px solid rgba(255, 90, 90, 0.25);
  color: #ffb4b4;
  font-size: 0.8rem;
}

.dash__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(248px, 1fr));
  gap: 14px;
}

.card {
  padding: 18px 18px 16px;
  background: var(--glass);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.card--wide { grid-column: 1 / -1; }

.card__title {
  margin: 0 0 12px;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
}

.card__big {
  margin: 0 0 12px;
  font-size: 1.7rem;
  font-weight: 700;
  line-height: 1.2;
  color: var(--ink);
}

.card__unit {
  margin-left: 6px;
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--ink-faint);
}

.card__rows { margin: 0; font-size: 0.78rem; }

.card__rows > div {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 5px 0;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
}

.card__rows dt { color: var(--ink-faint); flex: none; }
.card__rows dd { margin: 0; text-align: right; color: var(--ink); }
.card__rows dd.is-alert { color: #fca5a5; font-weight: 600; }

.card__foot {
  margin: 10px 0 0;
  font-size: 0.7rem;
  color: var(--ink-faint);
}

.card__empty {
  margin: 0;
  font-size: 0.78rem;
  color: var(--ink-faint);
}

/* --- 世の中の空気 --- */
.mood {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 10px;
  font-size: 0.72rem;
}

.mood__label { flex: none; width: 2.4em; color: var(--ink-faint); }

.mood__bar {
  flex: 1;
  display: flex;
  height: 7px;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.07);
}

.mood__pos { background: linear-gradient(90deg, #34d399, #6ee7b7); }
.mood__neg { background: linear-gradient(90deg, #f87171, #fca5a5); }
.mood__val { flex: none; color: var(--ink-dim); font-variant-numeric: tabular-nums; }

/* --- AI各社の料金 --- */
.ptable {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.78rem;
}

.ptable th,
.ptable td {
  padding: 7px 6px;
  text-align: left;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}

.ptable th {
  color: var(--ink-faint);
  font-weight: 600;
  font-size: 0.7rem;
  border-top: none;
}

.ptable .num { text-align: right; font-variant-numeric: tabular-nums; }
.ptable tr.is-cheapest td { color: #6ee7b7; font-weight: 600; }

/* --- 収集状況 --- */
.collect {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 7px;
}

.collect__item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.03);
  font-size: 0.74rem;
}

.collect__dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
}

.collect__item.is-ok    .collect__dot { background: #34d399; }
.collect__item.is-stale .collect__dot { background: #fbbf24; }
.collect__item.is-error .collect__dot { background: #f87171; }

.collect__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-dim);
}

.collect__age { flex: none; color: var(--ink-faint); font-size: 0.68rem; }

@media (max-width: 560px) {
  .dash { padding: 20px 14px 48px; }
  .card__big { font-size: 1.45rem; }
}
