/* ==========================================================================
   The sky.

   The island is a cut-out with no sky of its own, so the environment is built
   here — which keeps it sharp at any resolution, costs no bytes, and can be
   tuned without re-exporting artwork. It is deliberately simple: a high
   atmosphere, a warmer band near the horizon, and three very soft cloud forms.
   Nothing detailed enough to compete with the island or to change what the
   artwork means.

   The columns of text sit over the palest part of it, and each carries its own
   light wash, so expanding the sky never costs readability. That was the
   constraint: text first, sky second.
   ========================================================================== */
.worlds__sky {
  position: absolute; inset: 0; z-index: 0;
  overflow: hidden;
  background:
    image-set(url("/assets/img/world-sky-1280.webp") 1x,
              url("/assets/img/world-sky-1920.webp") 1.5x,
              url("/assets/img/world-sky-2560.webp") 2x) center 58% / cover no-repeat;
  transition: opacity var(--dur-l) var(--ease-out);
}
/* Lifted a little further here than on the world pages: this screen carries two
   columns of type and four cards, and the sky's job is to hold them, not to be
   looked at. */
.worlds__sky::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(170deg,
    rgba(246,245,242,.34) 0%, rgba(246,245,242,.20) 45%, rgba(246,245,242,.52) 100%);
}
/* The hand-drawn cloud shapes that used to live here are gone: the sky is a
   photograph now and has its own. */

/* ==========================================================================
   The Worlds scene — one island, four districts.

   Composition follows reference 02: a narrow intro column, the map, and a
   journey column. All four cards are visible at rest, as they are in the
   reference; interest promotes one and recedes the others. Nothing important
   is hidden behind a hover.
   ========================================================================== */

.worlds {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  padding: calc(var(--header-h) + .5rem) var(--gutter) clamp(1rem, 3vh, 2rem);
}
/* everything above the sky */
.worlds > *:not(.worlds__sky) { position: relative; z-index: 1; }

.worlds__crumb {
  display: flex; align-items: center; gap: .55rem;
  color: var(--ink-muted); flex: none;
}
.crumb__link { color: inherit; }
.crumb__link:hover { color: var(--ink); text-decoration: underline; text-underline-offset: .25em; }
.worlds__crumb [aria-current] { color: var(--ink); }

.worlds__grid {
  flex: 1; min-height: 0;
  display: grid;
  grid-template-columns: minmax(11rem, 1fr) minmax(0, 3.5fr) minmax(9.5rem, .9fr);
  gap: clamp(1rem, 2.6vw, 3rem);
  align-items: center;
}

/* No scrim behind the columns. `node tools/contrast.js` checks every text ink
   against the sky's top, middle and lower bands, and the worst case — muted
   ink on the deepest blue — is 4.71:1. So the sky can be completely open,
   which is the whole point of it. If the sky is ever darkened, run that check
   again before assuming the text still holds. */

.worlds__eyebrow { color: var(--ink-muted); margin: 0 0 1.1rem; }

.worlds__title {
  margin: 0; font-weight: 300;
  font-size: clamp(1.5rem, 2.1vw, 2.2rem);
  line-height: 1.12; letter-spacing: var(--track-tight);
}
.worlds__body {
  margin: 1.2rem 0 0; max-width: 19rem;
  font-size: .8125rem; line-height: 1.68; color: var(--ink-soft);
}

.worlds__intro {
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: -40px;
}

/* --- the world list: canonical navigation, and a legend for the map ------ */
.wlist { list-style: none; margin: 0; padding: 0; }
.wlist__item { position: relative; }
/* the connector rail between the dots */
.wlist__item:not(:last-child)::after {
  content: ""; position: absolute; left: 4px; top: 1.65rem; bottom: -.35rem;
  width: 1px; background: var(--rule-soft);
}
.wlist__btn {
  display: flex; align-items: center; gap: .85rem;
  width: 100%; padding: .5rem 0; text-align: left;
  color: var(--ink-soft);
  transition: color var(--dur-s) var(--ease-out);
}
.wlist__dot {
  flex: none; width: 9px; height: 9px; border-radius: 50%;
  border: 1px solid var(--ink-quiet); background: transparent;
  transition: background var(--dur-s) var(--ease-out),
              border-color var(--dur-s) var(--ease-out),
              transform var(--dur-s) var(--ease-out);
}
.wlist__name { font-size: .8125rem; }

/* --- the map ------------------------------------------------------------ */
.worlds__map {
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(.5rem, 2vh, 1.25rem);
  min-height: 0;
}

.island {
  position: relative;
  container-type: size; container-name: island;
  width: min(100%, calc(var(--island-h, 72svh) * 1.1718));
  aspect-ratio: 1303 / 1112;
  perspective: 1400px;
  transform-style: preserve-3d;
}

.island__img {
  width: 100%; height: 100%; object-fit: contain;
  transform: rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
  transition: transform 1.1s var(--ease-out);
  will-change: transform;
}
/* A very slow float. The island is airborne; holding perfectly still reads as
   a flat picture. Amplitude is small enough to notice only if you look. */
@media (prefers-reduced-motion: no-preference) {
  .island__img { animation: island-float 11s var(--ease-mid) infinite alternate; }
}
@keyframes island-float {
  from { translate: 0 -5px; }
  to   { translate: 0 5px; }
}

/* --- districts: redundant pointer targets, hidden from AT --------------- */
.district {
  position: absolute;
  left: calc(var(--x) * 100%); top: calc(var(--y) * 100%);
  width: 16%; aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  cursor: pointer;
}
.district::before {
  content: ""; position: absolute; inset: 14%;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .9);
  box-shadow: 0 0 0 1px rgba(22, 25, 30, .1), 0 10px 34px -12px rgba(12, 16, 22, .5);
  background: radial-gradient(circle, rgba(255,255,255,.28), rgba(255,255,255,0) 70%);
  opacity: 0; transform: scale(.7);
  transition: opacity var(--dur-s) var(--ease-out), transform var(--dur-m) var(--ease-out);
}

/* --- district cards ----------------------------------------------------- */
/* Sized in container units so the cards keep the reference's proportion to the
   island at any viewport — a fixed rem width makes the labels wrap as soon as
   the map is smaller than the mockup was. */
.dcard {
  position: absolute;
  left: calc(var(--x) * 100%); top: calc(var(--y) * 100%);
  width: 23cqw; min-width: 7.5rem;
  display: block; padding: 2cqw 2.3cqw 2.2cqw;
  background: rgba(253, 252, 251, .93);
  box-shadow: 0 1px 2px rgba(12,16,22,.06), 0 14px 40px -20px rgba(12, 16, 22, .45);
  /* The card is a target in its own right. Pointing at the words and pointing
     at the district should not behave differently. */
  pointer-events: auto;
  cursor: pointer;
  border: 1px solid transparent;
  transition: opacity var(--dur-m) var(--ease-out),
              transform var(--dur-m) var(--ease-out),
              box-shadow var(--dur-m) var(--ease-out);
}
.dcard__head { display: flex; align-items: baseline; justify-content: space-between; gap: .6em; }
.dcard__name { font-size: clamp(.6875rem, 2cqw, .8125rem); letter-spacing: -.005em; white-space: nowrap; }
.dcard__go { color: var(--ink-muted); transition: transform var(--dur-s) var(--ease-out); }
.dcard__line {
  display: block; margin-top: .35em;
  font-size: clamp(.5625rem, 1.6cqw, .6875rem); line-height: 1.5; color: var(--ink-muted);
}

/* --- interest: promote one world, recede the rest -----------------------
   JS stamps every element carrying a world with data-state: "rest" when
   nothing is selected, "on" for the chosen world, "off" for the others. That
   keeps this readable — the alternative is sixteen attribute selectors that
   all have to stay in sync. Elements have no data-state until the scene is
   ready, which is what holds the cards back during the arrival. */

.dcard[data-state="rest"] { opacity: .92; }
.dcard[data-state="off"]  { opacity: .3; }
.dcard[data-state="on"] {
  opacity: 1;
  transform: translateY(-4px);
  border-color: rgba(22, 25, 30, .12);
  box-shadow: 0 2px 4px rgba(12,16,22,.07), 0 22px 54px -22px rgba(12, 16, 22, .5);
}
.dcard[data-state="on"] .dcard__go { transform: translateX(.3rem); }

.district[data-state="on"]::before { opacity: 1; transform: none; }

.wlist__btn[data-state="on"]  { color: var(--ink); }
.wlist__btn[data-state="off"] { color: var(--ink-muted); }   /* dimmed, still AA */
.wlist__btn[data-state="on"] .wlist__dot {
  background: var(--ink); border-color: var(--ink); transform: scale(1.15);
}

.journey__item[data-state="off"] { opacity: .42; }

/* --- the cue under the map ---------------------------------------------- */
.worlds__cue {
  display: flex; align-items: center; gap: .7rem;
  align-self: flex-start;
  color: var(--ink-muted); margin: 0; flex: none;
}
.worlds__cue-mark {
  width: 13px; height: 19px; border: 1px solid var(--rule); border-radius: 7px;
  position: relative;
}
.worlds__cue-mark::after {
  content: ""; position: absolute; left: 50%; top: 4px;
  width: 1px; height: 4px; background: var(--ink-quiet);
  transform: translateX(-50%);
}

/* --- the journey column ------------------------------------------------- */
.journey { list-style: none; margin: 0; padding: 0; }
.journey__item {
  display: grid; grid-template-columns: 1.6rem 1fr; gap: .5rem;
  padding: .55rem 0;
  transition: opacity var(--dur-s) var(--ease-out);
}
.journey__no { color: var(--ink-muted); }
.journey__line { display: block; font-size: .8125rem; line-height: 1.32; }
.journey__kicker {
  display: block; margin-top: .3rem;
  font-size: var(--t-micro); color: var(--ink-muted);
}
.worlds__quote {
  margin: clamp(1.25rem, 4vh, 2.25rem) 0 0;
  padding-top: clamp(1rem, 3vh, 1.75rem);
  border-top: 1px solid var(--rule-soft);
  font-size: .8125rem; line-height: 1.6; font-style: italic; color: var(--ink-soft);
}

/* --- arrival + departure ------------------------------------------------ */
.worlds .island__img,
.worlds__intro,
.worlds__journey { opacity: 0; }
.dcard { opacity: 0; }        /* until JS stamps a data-state */

.worlds[data-ready="true"] .island__img { opacity: 1; transition: opacity var(--dur-l) var(--ease-out); }
.worlds[data-ready="true"] .worlds__intro,
.worlds[data-ready="true"] .worlds__journey {
  opacity: 1; transition: opacity var(--dur-l) var(--ease-out) 140ms;
}
.worlds[data-ready="true"] .dcard { transition-delay: 300ms; }

/* Departure: the camera keeps travelling, aimed at the district you chose.
   Deliberately a long, deep push — this is the moment you enter a world, and it
   should read as being pulled in rather than as a page changing. The opacity
   holds most of the way so the scale is what you notice. */
.worlds[data-leaving] .island {
  transform: scale(3.6);
  transform-origin: var(--focus-x, 50%) var(--focus-y, 50%);
  opacity: 0;
  transition: transform 900ms cubic-bezier(.55, 0, .78, .3),
              opacity 420ms var(--ease-in) 470ms;
}
/* Everything else clears out early so the island has the screen to itself. */
.worlds[data-leaving] .worlds__sky { opacity: 0; transition: opacity 640ms var(--ease-in); }
.worlds[data-leaving] .worlds__intro,
.worlds[data-leaving] .worlds__journey,
.worlds[data-leaving] .worlds__crumb,
.worlds[data-leaving] .worlds__cue {
  opacity: 0; transition: opacity 260ms var(--ease-in);
}
.worlds[data-leaving] .dcard { opacity: 0; transition: opacity 200ms var(--ease-in); }

/* ==========================================================================
   Stacked composition — the map becomes an illustration, the list the control
   ========================================================================== */
.worlds__index { display: none; }

@media not all and (min-width: 1180px) and (min-aspect-ratio: 3/2) {
  .worlds { position: static; padding-bottom: clamp(2rem, 6vh, 3.5rem); }

  .worlds__grid {
    grid-template-columns: 1fr;
    gap: clamp(1.75rem, 5vh, 2.75rem);
    align-items: start;
  }
  .worlds__body { max-width: 34rem; font-size: var(--t-body); }
  .worlds__title { font-size: clamp(1.6rem, 6vw, 2.2rem); }

  /* The list moves to the index below; the rail and dots would duplicate it. */
  .wlist { display: none; }

  /* Read order: the map, then what it is, then how to enter, then the
     narrative. The index is the primary control, so it comes before it. */
  .worlds__map     { order: 1; }
  .worlds__intro   { order: 2; }
  .worlds__index   { order: 3; }
  .worlds__journey { order: 4; }
  .island { width: 100%; }
  .district { pointer-events: none; }
  /* Cards are anchored to a desktop-sized map; at this width they collide. */
  .dcard { display: none; }
  .worlds__cue { display: none; }

  .journey__item[data-state] { opacity: 1; }

  .worlds__index { display: block; border-top: 1px solid var(--rule-soft); }
}

/* Phones and tablets: the island with its four district cards, as on the
   desktop, instead of a list of rows. The cards are compact and sit on the
   island's corners; tapping one (or its district) enters that world. The
   list stays for screen readers and keyboards, and appears when focused. */
@media not all and (min-width: 1180px) and (min-aspect-ratio: 3/2) {
  @media (max-width: 1179px), (hover: none) and (pointer: coarse) {
    .worlds { min-height: 100svh; }
    .worlds__grid { flex: 1; align-content: center; }
    .district { pointer-events: auto; }
    .dcard {
      display: block;
      width: auto; min-width: 0;
      padding: .5rem .7rem;
    }
    .dcard[data-side="left"]  { left: 0; }
    .dcard[data-side="right"] { left: auto; right: 0; }
    .dcard__name { font-size: .8125rem; }
    .dcard__head { gap: .5rem; }
    .dcard::after { content: ""; position: absolute; inset: -.35rem; }   /* finger-sized */
    .worlds__cue { display: flex; justify-content: center; margin-top: 1.5rem; }
    /* the district cards drift very slightly, out of step, like the island */
    @media (prefers-reduced-motion: no-preference) {
      .dcard { animation: worlds-card-drift 10s ease-in-out infinite alternate; }
      .dcard[data-world="ai"]       { animation-duration: 12s; animation-delay: -5s; }
      .dcard[data-world="product"]  { animation-duration: 11s; animation-delay: -8s; }
      .dcard[data-world="creative"] { animation-duration: 13s; animation-delay: -3s; }
    }
    .worlds__index:not(:focus-within) {
      position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
      overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
    }
  }
}

/* Under the desktop width the page spans the screen and the sky covers all of
   it, down to the last row of the index. */
@media (max-width: 1179px) {
  .worlds { position: relative; width: 100%; justify-self: stretch; }
  .worlds__grid { width: 100%; max-width: 40rem; margin-inline: auto; }
}
/* Phones and tablets: the sky's colour at the top of the screen, for the
   browser's own bars (read by stage.js). */
@media (max-width: 1179px), (hover: none) and (pointer: coarse) {
  .worlds { --edge: #DDE2E5; }
}

@media (prefers-reduced-motion: reduce) {
  /* The departure timings are hand-written in ms, so the duration tokens do
     not reach them. Cut them here rather than leaving one stray animation. */
  .worlds[data-leaving] .island,
  .worlds[data-leaving] .worlds__intro,
  .worlds[data-leaving] .worlds__journey,
  .worlds[data-leaving] .worlds__crumb,
  .worlds[data-leaving] .worlds__cue,
  .worlds[data-leaving] .dcard { transition-duration: 1ms; }
  .island__img { transition-duration: 1ms; }
}

@keyframes worlds-card-drift {
  0%   { translate: 0 0; }
  50%  { translate: 2px -3px; }
  100% { translate: -1.5px 2px; }
}
