/* =========================================================================
   Islands — the map tiles.
   Shared water/shore, a per-scene landmark. Colour is set here so the nine
   scenes read as one archipelago rather than nine separate drawings.
   ========================================================================= */

.isle { inline-size: 100%; block-size: auto; overflow: visible; direction: ltr; }

.isle-sea   { fill: url(#isle-sea-g); }
.isle-waves path { stroke: #7fd0f7; }
.isle-rock  { fill: url(#isle-rock-g); }
.isle-sand  { fill: url(#isle-sand-g); }
.isle-trunk { stroke: #a9713f; }
.isle-leaf path,
.isle-bush  { fill: url(#isle-leaf-g); }
.isle-fruit { fill: url(#isle-fruit-g); }
.isle-peak  { fill: url(#isle-peak-g); }
.isle-lava  { fill: url(#isle-lava-g); }
.isle-smoke circle { fill: #d9d9e4; }
.isle-mouth { fill: #2f2a52; }
.isle-spark { fill: #ffc800; }
.isle-snow  { fill: #ffffff; }
.isle-pool  { fill: #4fc3f7; }
.isle-stone { fill: #cbb99a; }
.isle-ice   { fill: #cfeeff; }
.isle-cap   { fill: #ff4b4b; }
.isle-dot   { fill: #fff; }

/* The island art holds still.
   It used to breathe: waves lapping, smoke puffing, a leaf swaying, a spark
   glinting. None of it meant anything, and sixteen icons all moving at once
   on the map is motion for its own sake. The bob on the island you are
   currently on stays, because that one says something: you are here. */
.isle-waves path { opacity: 0.7; }
.isle-spark { opacity: 0.8; }

/* =========================================================================
   The island tile on the map
   ========================================================================= */
.isle-card {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 0.35rem;
  padding: 1rem 0.9rem 1.2rem;
  background: var(--paper);
  border: 2px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: 0 var(--lift) 0 var(--line);
  text-decoration: none;
  color: var(--ink);
  transition: transform var(--t-fast) var(--ease-out),
              box-shadow var(--t-fast) var(--ease-out),
              border-color var(--t-fast);
}
.isle-card:hover { color: var(--ink); transform: translateY(-3px); box-shadow: 0 7px 0 var(--line); }
.isle-card:active { transform: translateY(var(--lift)); box-shadow: 0 0 0 var(--line); }

.isle-card.is-current {
  border-color: var(--brand);
  box-shadow: 0 var(--lift) 0 var(--brand-deep);
}
.isle-card.is-complete { border-color: var(--grass); box-shadow: 0 var(--lift) 0 var(--grass-deep); }
.isle-card.is-locked {
  filter: grayscale(0.85);
  opacity: 0.6;
  cursor: not-allowed;
  pointer-events: none;
}

/* Not yet released, as opposed to not yet reached. Lighter than the locked
   card because this one is a promise rather than a refusal — the island is
   still meant to look inviting. Deliberately keeps pointer-events, so the
   "opens on release day" tooltip can actually be read; the locked card's
   tooltip has never been reachable for exactly that reason. */
.isle-card.is-soon {
  filter: grayscale(0.5);
  opacity: 0.72;
  cursor: default;
}

.isle-num {
  position: absolute;
  inset-block-start: 8px;
  inset-inline-start: 10px;
  min-inline-size: 26px;
  padding: 0.1rem 0.4rem;
  border-radius: var(--r-pill);
  background: var(--canvas-deep);
  color: var(--ink-soft);
  font-weight: 900;
  font-size: 0.8rem;
  text-align: center;
}
/* White on --brand/--grass (#58cc02) measures 2.09:1 — a number nobody with
   ordinary eyesight reads comfortably. --ink-grass on the soft tint is 6.51:1
   and the ring keeps "this is the live one" unmistakable. */
.isle-card.is-complete .isle-num { background: var(--brand-soft); color: var(--ink-grass); }
.isle-card.is-current .isle-num  { background: var(--brand-soft); color: var(--ink-grass);
                                   box-shadow: 0 0 0 2px var(--brand); }

.isle-art { inline-size: 128px; max-inline-size: 100%; }
.isle-title { font-size: 0.98rem; font-weight: 800; line-height: 1.3; text-align: center; }
.isle-meta { font-size: 0.8rem; color: var(--ink-soft); }

.isle-bar {
  inline-size: 100%;
  block-size: 9px;
  border-radius: var(--r-pill);
  background: var(--canvas-deep);
  overflow: hidden;
}
.isle-bar i {
  display: block;
  block-size: 100%;
  border-radius: inherit;
  background: var(--grass);
  transition: inline-size var(--t-mid) var(--ease-out);
}

.isle-flag {
  position: absolute;
  inset-block-start: 6px;
  inset-inline-end: 8px;
  font-size: 1.2rem;
}

/* The island icons hold still.
   Gears span, suns pulsed, coins glinted, bars breathed and the current card
   floated. None of it meant anything: sixteen icons moving at once is motion
   for its own sake, and it drew the eye to whichever island happened to be
   current rather than to whatever the child was reading. */


@media (prefers-reduced-motion: reduce) {
  .isle *, .isle-card * { animation: none !important; }
}

/* =========================================================================
   The voyage — islands strung along a winding sea route.
   ========================================================================= */

/* --- semantic landmark colours ------------------------------------------- */
.isle-board  { fill: #ffb020; }
.isle-sun    { fill: url(#isle-sun-g); }
.isle-ray path { stroke: #ffc800; }
.isle-frame  { fill: none; stroke: #a9713f; stroke-width: 3; }
.isle-wire   { stroke: #cbb99a; }
.isle-bead   { fill: #ff4b4b; }
.isle-bar1   { fill: #1cb0f6; }
.isle-coin   { fill: #ffc800; stroke: #e5a400; stroke-width: 1.5; }
.isle-marble1 { fill: #ff4b4b; } .isle-marble2 { fill: #1cb0f6; } .isle-marble3 { fill: #a560ff; }
.isle-edge   { stroke: #8b8f9e; }
.isle-node   { fill: #a560ff; }
.isle-pack   { fill: #ff4b4b; }
.isle-strap  { stroke: #c62828; }
.isle-pocket { fill: #ffb3b3; }
.isle-step   { fill: #8b8f9e; }
.isle-box    { fill: #d8a05a; stroke: #a9713f; stroke-width: 2.5; }
.isle-tape   { stroke: #a9713f; }
.isle-bubble { fill: #fff; stroke: #1cb0f6; stroke-width: 3; }
.isle-dot3   { fill: #1cb0f6; }
.isle-sym    { stroke: #a560ff; }
.isle-symdot { fill: #a560ff; }
.isle-block  { fill: #ff4b4b; } .isle-block2 { fill: #1cb0f6; }
.isle-glyph  { fill: #fff; font: 900 15px/1 "Estedad", system-ui, sans-serif; }
.isle-road   { stroke: #cbb99a; }
.isle-lamp   { fill: #58cc02; } .isle-lamp2 { fill: #ff4b4b; }
.isle-loop   { stroke: #a560ff; }
.isle-gear   { fill: #8b8f9e; } .isle-gearhole { fill: #fff; }
.isle-teeth rect { fill: #8b8f9e; }
.isle-loop, .isle-teeth { transform-origin: 60px 44px; }



/* Landmarks come alive. */





/* =========================================================================
   The voyage -- the route IS the map.
   -------------------------------------------------------------------------
   Everything here is in real pixels: the route svg is sized to its own
   viewBox (720 x H), so 1 unit = 1 CSS px on BOTH axes and a stroke width is
   a stroke width. No preserveAspectRatio, no vector-effect, nothing stretched.
   ========================================================================= */
.voyage {
  --card-w: 216px;
  position: relative;
  block-size: var(--h);       /* no padding: VOYAGE_LEAD_IN/OUT do that job,
                                 and border-box padding would clip the svg */
  margin-block: 1.5rem 1.25rem;
  border-radius: var(--r-xl);
  overflow: hidden;
  background:
    /* a static crest field. Two repeating radial-gradients on the element's
       own background, painted once. The old sea animated background-position
       on two full-height pseudos; water does not have to move for the page to
       read as water, and the dashes and the boat are the only motion here
       that means anything. */
    radial-gradient(ellipse 58px 7px at 50% 50%, rgb(255 255 255 / .85) 0 42%, transparent 72%) 0 0 / 226px 154px,
    radial-gradient(ellipse 38px 5px at 50% 50%, rgb(31 123 168 / .20) 0 42%, transparent 72%) 74px 61px / 173px 211px,
    linear-gradient(180deg,
      color-mix(in srgb, var(--sea) 55%, #fff) 0%,
      var(--sea) 40%,
      color-mix(in srgb, var(--sea) 84%, var(--sea-line)) 100%);
}

.voyage-route {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 50%;
  inline-size: var(--lane);
  block-size: var(--h);
  translate: -50% 0;
  z-index: 1;
}
/* Cards mirror themselves (inset-inline-start); an svg path does not. Mirror
   the whole drawing so the route still meets the islands it threads. The
   element is centred on the meridian and transform-origin is its own centre,
   so flipping it about itself is the same as flipping it about the page. */
[dir="rtl"] .voyage-route { translate: 50% 0; transform: scaleX(-1); }

/* NB: do NOT write `.voyage-route path { fill: none }` -- (0,1,1) beats the
   (0,1,0) of .rt-buoy and the buoys would vanish. Per-class, deliberately. */
.rt-channel, .rt-ahead, .rt-wake, .rt-jetty, .rt-goal-x { fill: none; }

/* --- the water the route runs through ---------------------------------- */
.rt-channel {
  stroke: var(--sea-pale);
  stroke-width: 30;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- the route ahead ----------------------------------------------------
   Thin and dashed, but 3.48:1 against the water. "Faint" is done with weight
   and pattern, never by making the next island hard to find. */
.rt-ahead {
  stroke: var(--sea-line);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-dasharray: 5 15;
  opacity: .55;
  animation: rt-drift 3.4s linear infinite;
}
@keyframes rt-drift { to { stroke-dashoffset: -20; } }   /* exactly one period */

/* --- the route already sailed -------------------------------------------
   Gold with a dark rim: gold on this water is 1.07:1, so the rim carries the
   contrast (5.38:1) and the gold carries the charm.
   The dash is `--sailed --len`: ONE dash exactly as long as the distance
   already sailed, then a gap longer than everything left. Offsetting by
   --sailed hides it, so animating the offset to 0 draws the voyage on load. */
.rt-wake {
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: var(--sailed) var(--len);
  stroke-dashoffset: var(--sailed);
  animation: rt-draw 1.2s var(--ease-out) .15s forwards;
}
@keyframes rt-draw { to { stroke-dashoffset: 0; } }
.rt-wake--edge { stroke: var(--sea-deep); stroke-width: 12; }
.rt-wake--body { stroke: var(--wake);     stroke-width: 7.5; }
.rt-wake--core { stroke: #fffbe8;         stroke-width: 2.2; opacity: .85; }

/* --- ports --------------------------------------------------------------- */
.rt-jetty { stroke: var(--sea-deep); stroke-width: 3; stroke-linecap: round;
            stroke-dasharray: 2 6; opacity: .42; }
.rt-buoy  { fill: #fff; stroke: var(--sea-deep); stroke-width: 3.4; }
.rt-pip   { fill: var(--sea-line); }
.rt-port--complete .rt-buoy { stroke: var(--ink-grass); }
.rt-port--complete .rt-pip  { fill: var(--grass); }
.rt-port--current  .rt-buoy { fill: var(--wake); stroke: var(--sea-deep); }
.rt-port--current  .rt-pip  { fill: var(--sea-deep); }
.rt-port--locked .rt-buoy, .rt-port--soon .rt-buoy { stroke: var(--ink-faint); }
.rt-port--locked .rt-pip,  .rt-port--soon .rt-pip  { fill: var(--ink-faint); }
.rt-port--locked .rt-jetty, .rt-port--soon .rt-jetty { opacity: .2; }

/* Exactly one ring, on exactly one island, on the whole page. */
.rt-ping {
  fill: none; stroke: var(--wake); stroke-width: 3;
  transform-box: fill-box; transform-origin: center;
  animation: rt-ping 2.6s var(--ease-out) infinite;
}
@keyframes rt-ping {
  from { transform: scale(.55); opacity: .95; }
  to   { transform: scale(2.6); opacity: 0; }
}

.rt-goal-ring { fill: none; stroke: var(--sea-deep); stroke-width: 3.4;
                stroke-dasharray: 6 6; opacity: .5; }
.rt-goal-x    { stroke: var(--sea-deep); stroke-width: 4; stroke-linecap: round; opacity: .5; }
.rt-goal.is-won .rt-goal-ring,
.rt-goal.is-won .rt-goal-x { stroke: var(--wake); opacity: 1; }


/* --- the stops, docked either side of the channel -------------------------
   The card's INNER edge is pinned 136px off the meridian, 32px of clear water
   outside the route's 104px crest. The route is therefore NEVER hidden behind
   a card -- the old one ran through the card centres, so ~210px of every 562px
   segment was occluded and the only visible part was the flat middle.

   Cards alternate sides, so adjacent cards cannot overlap horizontally at any
   height, and same-column cards are 440px apart. VOYAGE_STEP is a density
   knob, not a collision margin: a Persian title may wrap to three lines. */
.voyage-stop {
  --haze: 0;
  position: absolute;
  inset-block-start: var(--y);
  inline-size: var(--card-w);
  z-index: 2;
  animation: voyage-arrive .5s var(--ease-bounce) both var(--d, 0s);
}
.voyage-stop--start { inset-inline-start: calc(50% - 136px); translate: -100% -50%; }
.voyage-stop--end   { inset-inline-start: calc(50% + 136px); translate: 0 -50%; }
/* In RTL inset-inline-start resolves to `right`, which anchors the card's
   other edge, so the translate flips with it. Verified: --start spans
   [50%W-352, 50%W-136] in LTR and [50%W+136, 50%W+352] in RTL. Exact mirror,
   one stylesheet. Widest case: at the 760px collapse threshold the container
   is 728px and a --end card reaches container x=716. 12px of slack. */
[dir="rtl"] .voyage-stop--start { translate: 100% -50%; }
@media (min-width: 1120px) { .voyage { --card-w: 232px; } }
@keyframes voyage-arrive { from { opacity: 0; transform: scale(.84); } }

/* Solid paper again -- backdrop-filter is gone, and the water underneath is
   opaque, so there is nothing left for it to do. */
.isle-card { background: var(--paper); }
.isle-art  { inline-size: 104px; position: relative; }

/* A shoal under each island: the shadow it casts into shallow water. Two
   radial-gradients, so the soft edge is real and costs no filter. */
.isle-art::after {
  content: "";
  position: absolute;
  inset-inline: 6%; inset-block-start: 92%;
  block-size: 18px;
  background:
    radial-gradient(closest-side at 56% 42%, rgb(13 92 133 / .22) 0, rgb(13 92 133 / .08) 56%, transparent 80%),
    radial-gradient(closest-side at 44% 38%, rgb(230 246 255 / .8) 0, transparent 82%);
  pointer-events: none;
}

/* --- islands ahead: distance, not damage ---------------------------------
   grayscale(.85) + opacity(.6) said "broken", and composited --ink-soft down
   to 2.25:1 on the title, a straight WCAG failure. Aerial perspective says
   "far": lay the sea's own colour over the ARTWORK in proportion to how many
   islands ahead it is, and leave the text alone. --haze runs 1-6, so the veil
   runs .13 -> .38. No filter: 17 colour-matrix filters is the cost the brief
   warns about, and the veil is one composited layer per card instead. */
.isle-card.is-locked { cursor: default; pointer-events: none; }
.isle-card.is-soon   { cursor: default; }
.isle-card.is-locked { background: var(--canvas-deep); border-color: var(--line); }
.isle-card.is-locked .isle-art::before,
.isle-card.is-soon   .isle-art::before {
  content: "";
  position: absolute; inset: -2%;
  z-index: 1;
  background: var(--sea);
  opacity: calc(.08 + var(--haze) * .05);
  pointer-events: none;
}
/* Coming-soon is a promise, not a refusal -- half the haze of a locked one. */
.isle-card.is-soon .isle-art::before { opacity: calc(.05 + var(--haze) * .028); }

/* --- the boat, at the boundary -------------------------------------------
   Not moored to a card any more: it stands on the route at arc length
   --sailed, exactly where gold becomes dashes. As the current island fills,
   place_voyage shortens the lead from 88px to 44px, so the boat visibly
   closes on it. */
.voyage-boat {
  position: absolute;
  inset-block-start: var(--by);
  inset-inline-start: calc(50% + var(--bx));
  inline-size: 76px;
  z-index: 3;
  translate: -50% -56%;
  rotate: calc(var(--tilt, 0deg) * var(--flip, 1));
  animation: voyage-bob 3.6s ease-in-out infinite;
  pointer-events: none;
}
/* translate, rotate and scale compose in that fixed order before `transform`,
   so `scale: -1 1` alone would mirror the hull but leave the lean on the
   wrong side. --flip negates the tangent angle too: together, a true mirror. */
[dir="rtl"] .voyage-boat { translate: 50% -56%; scale: -1 1; --flip: -1; }
@keyframes voyage-bob {
  0%, 100% { transform: translateY(0) rotate(-2deg); }
  50%      { transform: translateY(-6px) rotate(2deg); }
}

/* --- the legend ---------------------------------------------------------- */
.voyage-key {
  display: flex; flex-wrap: wrap; gap: .4rem 1.3rem;
  justify-content: center;
  margin: 0 0 2.2rem; padding: 0; list-style: none;
  font-size: .82rem; color: var(--ink-soft);
}
.voyage-key li { display: flex; align-items: center; gap: .4rem; }
.voyage-key .key {
  inline-size: 14px; block-size: 14px; border-radius: 50%;
  border: 3px solid var(--sea-deep); background: #fff;
}
/* Scoped to .voyage-key on purpose. The base rule above is (0,2,0); a bare
   .key--done is (0,1,0) and loses to it, so every marker in the legend drew
   as the same blue ring and the key explained nothing. */
.voyage-key .key--done { border-color: var(--ink-grass); background: var(--grass); }
.voyage-key .key--here { border-color: var(--sea-deep); background: var(--wake); }
.voyage-key .key--lock { border-color: var(--ink-faint); background: var(--canvas-deep); }


@media (max-width: 760px) {
  /* Phones get the plain list, deliberately.
     The desktop map earns its route: cards alternate either side of a channel
     and the wake shows how far you have sailed. None of that survives one
     column — the rail it collapsed to was a decoration running down the edge
     of a stack of cards, taking a lane of width away from the titles on the
     narrowest screens for no information the cards do not already carry.
     Each card states its own progress in words, a bar and a colour, so on a
     phone that IS the map. */
  .voyage {
    block-size: auto;
    display: grid;
    grid-template-columns: 1fr;
    padding: 1.1rem .8rem 1.4rem;
    border-radius: var(--r-lg);
    overflow: visible;
  }
  .voyage-route,
  .voyage-boat { display: none; }

  .voyage-stop, [dir="rtl"] .voyage-stop {
    position: relative; inset: auto; translate: none;
    inline-size: auto;
    grid-column: 1;
    grid-row: auto;
    padding-block: .4rem;
  }

  /* The card lies down. The island keeps its shoal and its haze. */
  .isle-card {
    grid-template-columns: 72px 1fr;
    grid-template-areas: "art title" "art meta" "art bar" "art status";
    align-content: center; justify-items: start;
    gap: .12rem .7rem;
    padding: .7rem .8rem;
  }
  .isle-art   { grid-area: art; inline-size: 72px; }
  .isle-title { grid-area: title; text-align: start; }
  .isle-meta  { grid-area: meta; }
  .isle-bar   { grid-area: bar; }
  .isle-status{ grid-area: status; }
  .isle-num   { position: static; grid-area: title; justify-self: end; }
  /* The key describes marks that only the desktop route draws. */
  .voyage-key { display: none; }
}

/* 320px: 288 page - 50 gutter - 13 padding leaves 225 of card, minus 26 of
   card padding and 72+11 of art = 116px for a Persian title. Too tight. */
@media (max-width: 380px) {
  .isle-card  { grid-template-columns: 62px 1fr; }
  .isle-art   { inline-size: 62px; }
  .isle-title { font-size: .9rem; }
}


@media (prefers-reduced-motion: reduce) {
  .isle *, .isle-card *,
  .rt-ahead, .rt-ping, .voyage-stop, .voyage-boat,
  .pboat-rig, .pboat-water path, .pboat-pupil { animation: none !important; }

  /* MUST pin the offset too. .rt-wake is HIDDEN at stroke-dashoffset:
     var(--sailed) and revealed by the animation; stopping the animation
     without this erases the entire progress wake. */
  .rt-wake { animation: none !important; stroke-dashoffset: 0; }
}

/* Frozen, the map still has: the water band and its crest field, the pale
   channel, the gold wake stopping under Pity, the dashed route ahead, a buoy
   per island coloured by state, jetties, the goal marker, the horizon haze on
   everything you have not reached, and the legend. Nothing here depends on
   motion to read as a map. */
