/* ==========================================================================
   An individual world.
   One island in its own sky. The other worlds are never present here — that
   is the point of the level.
   ========================================================================== */

@font-face {
  font-family: "OrticaLinear-Light";
  src: url("/assets/fonts/OrticaLinear-Light.otf") format("opentype");
  font-weight: 300;
}

.world {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  padding: calc(var(--header-h) + .5rem) var(--gutter) clamp(1rem, 3vh, 2rem);
  background: #F2EFEA;                 /* under the sky, and the reduced case */
}

/* A photographed sky rather than a gradient: layered cloud, a warm horizon,
   peaks far below. The islands float above cloud in the artwork, so this puts
   them somewhere instead of on a colour ramp. One image serves all four
   worlds; the tint below is what makes each of them its own place. */
.world__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 62% / cover no-repeat;
}
.world__sky::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(162deg,
    var(--sky-a) 0%, rgba(255,255,255,0) 46%, var(--sky-b) 100%);
  opacity: .46;
}
.world > *:not(.world__sky) { position: relative; z-index: 1; }

.world__grid {
  flex: 1; min-height: 0;
  display: grid;
  grid-template-columns: minmax(15rem, 1fr) minmax(0, 1.85fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: center;
}

.world__intro {
  margin-top: calc(-1 * clamp(2.5rem, 10vh, 4.5rem));
}

.world__no { color: #000000; margin: 0 0 1rem; }

.world__logo {
  height: auto; max-height: 50px; width: auto;
  margin: 0 0 1.5rem;
  display: block;
}

.world__title {
  margin: 0; font-weight: 300; font-family: OrticaLinear-Light;
  font-size: clamp(1.6rem, 2.5vw, 2.6rem);
  line-height: 1.1; letter-spacing: var(--track-tight);
  max-width: 14em;
}
.world__line {
  margin: 1rem 0 0; max-width: 24rem;
  font-size: var(--t-lede); color: var(--ink-soft);
}

.world__intro-btn {
  margin-top: clamp(1.25rem, 3vh, 1.75rem);
}

/* --- the case study card ------------------------------------------------ */
.world__case {
  margin-top: clamp(1.75rem, 5vh, 3rem);
  padding-top: clamp(1.25rem, 3vh, 1.75rem);
  border-top: 1px solid var(--rule-soft);
  max-width: 24rem;
}
.world__case-label { color: var(--ink-muted); margin: 0 0 .6rem; }
.world__case-title {
  margin: 0; font-size: 1.375rem; font-weight: 400; letter-spacing: -.015em;
}
.world__case-sub { margin: .3rem 0 1.25rem; color: var(--ink-muted); font-size: .8125rem; }

.world__case-note {
  display: inline-block; padding: .7rem 1rem;
  border: 1px dashed var(--rule); color: var(--ink-muted);
}

/* Sentence case at a readable size, with no tracking at all. Letterspaced
   uppercase reads as "spread apart" whether or not you are hovering, and
   nothing here animates spacing — the hover moves the arrow and lifts the
   surface instead. */
.btn {
  display: inline-flex; align-items: center; gap: .65rem;
  padding: .8rem 1.35rem;
  border: 1px solid var(--rule);
  font-size: .9375rem; font-weight: 500;
  letter-spacing: 0; text-transform: none;
  transition: background var(--dur-s) var(--ease-out),
              border-color var(--dur-s) var(--ease-out),
              transform var(--dur-s) var(--ease-out);
}
.btn:hover {
  background: rgba(22, 25, 30, .05);
  border-color: var(--ink-soft);
  transform: translateY(-1px);
}
.btn span { transition: transform var(--dur-s) var(--ease-out); }
.btn:hover span { transform: translateX(.3rem); }

/* --- the island --------------------------------------------------------- */
.world__scene {
  display: flex; align-items: center; justify-content: center; min-height: 0;
  perspective: 1400px;                 /* the tilt lives on .scene now */
}
/* The plates are ~400px native, so left alone they render postage-stamp sized
   in this column. The container is sized and the image contained inside it:
   that scales up without the aspect distortion you get from setting width and
   max-height together on an <img>. */
/* The pointer tilt is applied here, one level above the picture, so the
   image is free to carry its own hover transform without the two
   overwriting each other on the same property. */
.scene {
  width: 100%; height: min(58svh, 32rem);
  display: flex; justify-content: center;
  transform: rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
  transition: transform 1.1s var(--ease-out);
  will-change: transform;
}
/* The artwork is the second door into the project. It carries a quiet cue on
   interest rather than a panel over the picture — the image itself is the
   affordance. */
.scene__hit {
  display: block; position: relative;
  width: 100%; height: 100%;
  cursor: pointer;
}
.scene__cue {
  position: absolute; left: 50%; bottom: 4%;
  transform: translate(-50%, .4rem);
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .55rem 1rem;
  font-size: .875rem; font-weight: 500; letter-spacing: 0;
  color: var(--ink);
  background: rgba(252, 251, 249, .9);
  box-shadow: 0 10px 30px -14px rgba(12, 16, 22, .5);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-s) var(--ease-out), transform var(--dur-m) var(--ease-out);
}
.scene__cue span { transition: transform var(--dur-s) var(--ease-out); }
.scene__hit:is(:hover, :focus-visible) .scene__cue { opacity: 1; transform: translate(-50%, 0); }
.scene__hit:is(:hover, :focus-visible) .scene__cue span { transform: translateX(.3rem); }
.scene__hit:is(:hover, :focus-visible) .scene__img { transform: scale(1.02) translateY(-2px); }

.scene__img {
  width: 100%; height: 100%; object-fit: contain;
  transition: transform var(--dur-m) var(--ease-out);
}
@media (prefers-reduced-motion: no-preference) {
  /* `translate` is its own property, so the float composes with the hover
     transform instead of replacing it. */
  .scene__img { animation: island-float 12s var(--ease-mid) infinite alternate; }
}

/* --- the five beats ----------------------------------------------------- */
.world__journey {
  flex: none;
  padding-top: clamp(1rem, 2.5vh, 1.5rem);
  border-top: 1px solid var(--rule-soft);
}
.world__journey h3 { margin: 0 0 1rem; color: var(--ink-muted); font-weight: 500; }

.beats {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: clamp(.75rem, 2vw, 1.75rem);
}
.beat { display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto auto;
        column-gap: .75rem; align-items: center; }
.beat__disc {
  grid-row: 1 / 4; width: clamp(2.5rem, 4vw, 3.5rem); aspect-ratio: 1;
  border-radius: 50%; overflow: hidden; border: 1px solid var(--rule-soft);
}
.beat__disc img { width: 100%; height: 100%; object-fit: cover; }
.beat__no { color: var(--ink-muted); }
.beat__title { color: var(--ink); }
.beat__q { font-size: var(--t-micro); color: var(--ink-muted); }

/* --- arrival ------------------------------------------------------------ */
.world .scene__img, .world__intro, .world__journey { opacity: 0; }
.world[data-ready="true"] .scene__img { opacity: 1; transition: opacity var(--dur-l) var(--ease-out); }
.world[data-ready="true"] .world__intro { opacity: 1; transition: opacity var(--dur-l) var(--ease-out) 120ms; }
.world[data-ready="true"] .world__journey { opacity: 1; transition: opacity var(--dur-l) var(--ease-out) 260ms; }

/* Departure into the case study: the island recedes, the words stay longest. */
.world[data-leaving] .scene__img {
  opacity: 0; transform: scale(1.18);
  transition: opacity 420ms var(--ease-in), transform 520ms var(--ease-in);
}
.world[data-leaving] .world__journey,
.world[data-leaving] .worlds__crumb { opacity: 0; transition: opacity 240ms var(--ease-in); }
.world[data-leaving] .world__intro { opacity: 0; transition: opacity 320ms var(--ease-in) 60ms; }

/* ==========================================================================
   Stacked
   ========================================================================== */
@media not all and (min-width: 1180px) and (min-aspect-ratio: 3/2) {
  .world { position: static; min-height: 100svh; padding-bottom: clamp(2rem, 6vh, 3.5rem); }
  .world__grid { grid-template-columns: 1fr; gap: clamp(1.5rem, 5vh, 2.5rem); align-items: start; }
  .world__scene { order: -1; }
  .scene { height: min(42svh, 20rem); }

  /* No hover here, so the artwork needs a permanent affordance — but the
     button directly beneath already says "Open the case study", and repeating
     it verbatim over the picture is noise. A small arrow marks the image as
     tappable without saying the same thing twice. */
  .scene__cue {
    opacity: 1;
    left: auto; right: 4%; bottom: 6%;
    transform: none;
    width: 2.75rem; height: 2.75rem; padding: 0;
    border-radius: 50%;
    justify-content: center;
    font-size: 0;                       /* hides the words, keeps the arrow */
  }
  .scene__cue span { font-size: 1rem; }

  /* The beats were laying their title into a third column, which right-aligned
     it away from its own number. Disc on the left, everything else stacked. */
  .beat { grid-template-columns: auto 1fr; grid-template-rows: auto auto auto; }
  .beat__disc { grid-row: 1 / 4; }
  .beat__no, .beat__title, .beat__q { grid-column: 2; }
  .world__case { max-width: none; }
  .beats { grid-template-columns: 1fr; gap: .9rem; }
  .beat { grid-template-columns: auto 1fr auto; grid-template-rows: auto auto; }
  .beat__disc { grid-row: 1 / 3; }
}

/* Under the desktop width the world spans the screen, so its sky reaches the
   bottom of the page instead of stopping at the first screenful (which left a
   bare cream box under the button). The words keep their measure. */
/* Phones and tablets: the colour of each sky where it meets the top of the
   screen, for the browser's own bars (read by stage.js). */
@media (max-width: 1179px), (hover: none) and (pointer: coarse) {
  .world[data-world="healthcare"] { --edge: #D0DBE3; }
  .world[data-world="ai"]         { --edge: #CCD8E2; }
  .world[data-world="product"]    { --edge: #D5D8D9; }
  .world[data-world="creative"]   { --edge: #D5D2D1; }
}

@media (max-width: 1179px) {
  .world { position: relative; width: 100%; justify-self: stretch; }
  /* Healthcare's island trails vines low enough to brush "01 — Healthcare":
     its words sit a little lower. The island itself does not move. */
  .world[data-world="healthcare"] .world__intro { margin-top: calc(-1 * clamp(2.5rem, 10vh, 4.5rem) + 1.75rem); }
  .world__grid { width: 100%; max-width: 34rem; margin-inline: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .scene { transition-duration: 1ms; }
  .world[data-leaving] .scene__img,
  .world[data-leaving] .world__journey,
  .world[data-leaving] .worlds__crumb,
  .world[data-leaving] .world__intro { transition-duration: 1ms; }
  .scene__img { transition-duration: 1ms; }
}
