.chapter--osaka { background: #0a0a0a; padding: 2rem 0; width: 100%; }

/* One band holding both the title and the ticker. Its height is driven by
   the ticker's own type size so the strip stays proportional as the viewport
   changes. */
.osaka-head {
  position: relative;
  /* Above the full-bleed stills below, so the title and ticker stay readable
     once the photos have taken over the frame. */
  z-index: 5;
  display: flex;
  align-items: center;
  min-height: clamp(3.5rem, 9vw, 6rem);
}

.osaka-title {
  /* Sits above the ticker rather than after it — `position: relative` is what
     lets z-index apply, and the ticker below is taken out of flow so the two
     occupy the same band. */
  position: relative;
  z-index: 1;
  font-family: 'Noto Serif JP', serif;
  font-size: clamp(1.5rem, 4vw, 3rem);
  color: #f0ebe3;
  margin: 0;
  padding: 0 2rem;
}

.osaka-ticker-wrap {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  overflow: hidden;
  z-index: 0;
}

.osaka-ticker {
  display: flex;
  width: max-content;
  font-family: 'Noto Serif JP', serif;
  font-size: clamp(2rem, 8vw, 5rem);
  color: rgba(240,235,227,0.15);
  white-space: nowrap;
}

/* Spacing lives on each span as a trailing margin, not as a flex `gap` on the
   container. Flex `gap` only inserts space *between* children, not after the
   last one — so with the doubled markup (4 spans, then the same 4 again for
   a seamless loop), the true content period is `4·(item+gap)`, one gap
   longer than `totalWidth / 2`. `xPercent: -50` in osaka.js translates by
   exactly half the container's own width, so it needs that half to equal
   the true period. Giving every span (including the 4th, right before the
   loop point) its own trailing margin makes the two halves — and therefore
   `totalWidth / 2` — exactly match the period, with no snap. */
.osaka-ticker span {
  margin-right: 3rem;
}

.osaka-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* Tighter gutters and a shorter gap to the band above than before: with the
     ticker no longer occupying a row of its own, the grid moves up and each
     column gets more width to itself. */
  gap: 0.75rem;
  margin-top: 1.5rem;
  padding: 0 1.5rem;
}

.osaka-grid img {
  width: 100%;
  /* 4/5 rather than 3/4 — taller frames read as bigger at the same column
     width, while still leaving room for the scroll-driven scale-up to 1.1
     without the section clipping it. */
  aspect-ratio: 4/5;
  object-fit: cover;
}

/* Each still covers the whole pinned frame. No explicit z-index: positioned
   elements that all share `z-index: auto` paint in DOM order, so each still
   already sits above the one before it, and the unpositioned grid paints
   below all of them.
   (An earlier `:nth-of-type(1..3)` here silently broke this — nth-of-type
   counts every div sibling, so it was matching the head band and the grid,
   and only the first still ever received a stacking value. That left stills
   two and three sliding in *behind* the first, invisible.) */
.osaka-still {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

/* Two halves of one picture. Each band clips to its half and the image inside
   is offset so the two, once both are home, read as a single uncut photo —
   the split only shows while they're travelling. */
.osaka-still-band {
  position: absolute;
  left: 0;
  width: 100%;
  height: 50%;
  overflow: hidden;
  will-change: transform;
}

.osaka-still-band--top { top: 0; }
.osaka-still-band--bottom { top: 50%; }

/* Kept out of sight until the chapter module has run, so the stills don't
   appear un-cut in the gap while it loads. This deliberately does NOT
   pre-set a transform: GSAP treats an existing CSS transform as the
   baseline it animates from, so a `translateX(-100%)` here would stack with
   the `xPercent: -100` osaka.js sets and push the bands twice as far out,
   where they never return into frame. `visibility` is untouched by GSAP, and
   chapter-loader.js sets `data-chapter-ready` right after `init()`. */
.chapter--osaka:not([data-chapter-ready]) .osaka-still {
  visibility: hidden;
}

.osaka-still-band img {
  position: absolute;
  left: 0;
  width: 100%;
  /* Twice the band's height, so each band shows its own half of a picture
     that spans the full frame. */
  height: 200%;
  object-fit: cover;
}

.osaka-still-band--top img { top: 0; }
.osaka-still-band--bottom img { top: -100%; }

/* Closing frame only: the band holds two photos rather than one, each taking
   half the width. Everything else — the band clipping, the 200% image height,
   the offset that makes the two halves line up — carries over unchanged. */
.osaka-still--double .osaka-still-band img { width: 50%; }
.osaka-still--double .osaka-still-band img:last-child { left: 50%; }

/* Held to the left half, mirroring Hakone and countering Kyoto's and Nara's
   right-hand columns. Same construction as Kyoto's: no box on the container,
   just a centre line — the panel is drawn per paragraph so it fits the text it
   actually holds, and nothing is painted at all while every paragraph is still
   transparent, which is the case throughout the opening grid. */
.osaka-story {
  position: absolute;
  top: 50%;
  left: clamp(1.25rem, 5vw, 4rem);
  transform: translateY(-50%);
  z-index: 6;
  width: min(calc(100vw - 2.5rem), 440px);
  height: 0;
}

.osaka-story p {
  /* Transparent until JS reveals the active one. The chapter module loads
     lazily, so without this all paragraphs would briefly paint stacked on
     top of each other in the gap before `init()` runs. */
  opacity: 0;
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  transform: translateY(-50%);
  padding: 1.75rem;
  background: linear-gradient(rgba(10,10,10,0.78), rgba(10,10,10,0.6) 70%, rgba(10,10,10,0.78));
  backdrop-filter: blur(2px);
  border-radius: 4px;
  font-family: 'Inter', sans-serif;
  font-size: clamp(0.9rem, 2vw, 1.05rem);
  line-height: 1.7;
  color: rgba(240,235,227,0.92);
  letter-spacing: 0.01em;
}

/* The off-frame parking position is deliberately NOT set here as a CSS
   `translateX(-100%)`. GSAP reads an existing transform and resolves it into
   its own model as a pixel `x`, so a CSS-set percentage translate becomes a
   fixed x of -1280px; a tween that then moves `xPercent` from -100 to 0
   leaves that -1280px sitting alongside it and the band never arrives. The
   parking is applied from js/chapters/osaka.js instead, where it stays in the
   same unit the tween animates. Nothing flashes before that runs, because the
   images inside carry `data-src` and have no source to paint yet. */
