/* The Magical Forest hub.
 *
 * The scene is static/images/forest/hub-forest.jpg — 1376x768, generated by the owner
 * against the episode as reference, and checked against docs/VISUAL_REFERENCE.md: turquoise
 * waterfalls into a luminous pool, a great tree with a round door and a lantern-lit
 * balcony, glowing mushrooms in pink/teal/purple, god-rays, fireflies, warm light against
 * cool shadow. It replaced the episode frame at t=174s, which was only 848x478 and soft
 * when stretched.
 *
 * Each area is a PLACE IN THAT PICTURE — the waterfall, the treehouse, the hollow under
 * the tree, the mushroom bed — rather than a button sitting on top of it, which is what
 * docs/VISUAL_REFERENCE.md asks for.
 *
 * TWO LAYOUTS, because one does not fit both devices:
 *
 *   * SCENE mode (>= 560px wide, the default experience, tablet first). The artwork is a
 *     fixed 848:478 box sized to fit the viewport, and hotspots are positioned in
 *     PERCENTAGES of that box — so a place stays on its place at every screen size.
 *
 *   * STACKED mode (narrower than 560px). Below that width the scene box is under ~315px
 *     tall, and a 15%-wide hotspot lands at ~45px — under the 64px tap floor, and the
 *     labels collide. So a small phone gets the picture whole, with the places listed
 *     under it as big chips. Still the world, still every area reachable; it just stops
 *     pretending a 320px screen can hold a five-place scene.
 *
 * TODO: Mori and Pico are not in this artwork and belong in the hub — needs character
 *       art with transparency. A portrait-composed version would end the letterboxing on
 *       an upright phone or tablet.
 */

.forest {
  position: relative;
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  /* Top padding clears the fixed sound controls. */
  padding: max(1rem, calc(var(--tap-min) + 1.25rem)) 1rem 2rem;
  overflow: hidden;            /* clips the blurred backdrop, nothing else */
  background: var(--night);
}

/* Reaches the edges of any screen so the world does not stop at the artwork. */
.forest-backdrop {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.15);
  filter: blur(30px) brightness(0.45) saturate(1.15);
  pointer-events: none;
  z-index: 0;
}

/* --- Lights on / off -----------------------------------------------------
 * The night artwork is the SAME composition re-lit, which is the whole reason this
 * works: every hotspot percentage keeps landing on the same place, and the two images
 * cross-fade instead of the layout changing underneath the child.
 *
 * Both images are in the page from the start. Lazy-loading the night one would make the
 * very first switch stutter, and 122 KB is a fair price for a switch that feels instant.
 */

.forest-backdrop--night,
.forest-art--night {
  opacity: 0;
  transition: opacity 700ms ease;
}

body.is-night .forest-backdrop--night,
body.is-night .forest-art--night {
  opacity: 1;
}

/* The night image is a layer ON the day one, in both layouts. In the stacked layout the
   day image drives the box height and this sits exactly over it. */
.forest-art--night {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  pointer-events: none;
}

/* Mori and Pico were cut from a warm, sunlit frame. Dropped unchanged into a blue night
   scene they read as pasted on, so they cool very slightly after dark. Deliberately
   subtle — this must not become a recolour of the characters. */
body.is-night .character img {
  filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.6)) brightness(0.88) saturate(0.92);
}

.light-toggle {
  position: fixed;
  top: max(1rem, env(safe-area-inset-top));
  left: max(1rem, env(safe-area-inset-left));
  z-index: 10;
  width: var(--tap-min);
  height: var(--tap-min);
  display: grid;
  place-items: center;
  padding: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.35);
  color: var(--lantern-glow);
}

.light-toggle svg {
  width: 54%;
  height: 54%;
  fill: currentColor;
  grid-area: 1 / 1;              /* both icons stack; only one is shown */
}

/* Show the sun by day and the moon by night — the icon is the current state made
   visible, which is what a child reads. */
.light-toggle-moon { display: none; }
body.is-night .light-toggle-sun { display: none; }
body.is-night .light-toggle-moon { display: block; }

.forest-scene {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 680px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.875rem;
}

.forest-picture {
  position: relative;
  flex: 0 0 100%;
  width: 100%;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 0 40px rgba(255, 217, 138, 0.28);
  line-height: 0;                  /* no descender gap under the image */
}

.forest-art {
  width: 100%;
  height: auto;
  display: block;
}

/* --- Mori and Pico ------------------------------------------------------
 * Cut out of the owner's generated character sheet, so the identity is exactly the
 * episode's. Both were exported with ONE scale factor, which is why their sizes below
 * are 9% and 9.43% rather than a round number — that ratio is how they are drawn.
 *
 * Alive in plain CSS. No Lottie (needs a library plus an After Effects export), no
 * Framer Motion (React, which the brief rules out), no SVG (these are raster cutouts,
 * not vectors). Only `transform`, so the compositor does the work.
 *
 * Nothing here reacts to :hover. The primary device is a tablet, which has none.
 */

/* The world's name, over the scene. It is branding, not an instruction: nothing here
   asks a child to read, and the screen works identically with it removed. */
/* --- The world's name ----------------------------------------------------
   Modelled on the episode's own title card: engraved gold serif, with the subtitle
   under it in wide-spaced small caps between inward chevrons. That card is the series'
   identity, so the hub wears it rather than a font of our own choosing.

   Two things it deliberately does NOT copy:

   - The card's vine-and-leaf flourishes are *artwork*, not type. Approximating them in
     CSS would read as neither, so they are logged as a missing asset instead.
   - The card's display face is a licensed font we do not ship, and CLAUDE.md bars
     pulling assets off the internet. The stack below reaches for the most title-like
     serifs already installed on a Mac, an iPad or a Windows box, then lands on Georgia,
     which is everywhere.

   Still absolutely positioned and still pointer-events:none — it overlays the scene, so
   it cannot move the geometry the hotspot percentages are measured against, and it can
   never swallow a tap meant for a place. */

.world-title {
  position: absolute;
  top: max(0.75rem, env(safe-area-inset-top));
  /* Centred by `inset + margin auto`, NOT by `left: 50%` + a translate. The translate
     only moves the box after layout: with `left: 50%` the available width is whatever is
     left of the container, i.e. half of it, so the box shrink-to-fits into 195px on a
     390px phone and `max-width` never gets a say. That is what wrapped the subtitle into
     the music button. */
  left: 0;
  right: 0;
  z-index: 3;
  margin: 0 auto;
  /* A cap, not a width — it keeps the title clear of the corner controls. */
  max-width: calc(100% - (var(--tap-min) * 2) - 3rem);
  text-align: center;
  font-family: Baskerville, "Palatino Linotype", "Book Antiqua", Palatino,
               Georgia, "Times New Roman", serif;
  font-size: clamp(1.55rem, 5vw, 3.6rem);
  line-height: 1.05;
  pointer-events: none;            /* it is a label, never a target */
}

.world-title-name {
  display: block;
  font-weight: 700;
  /* The card sets the name in small caps — a full-height M and P over smaller ORI/ICO.
     This is presentation only: the text stays "Mori & Pico", so a screen reader still
     says the name rather than spelling it. */
  font-variant-caps: small-caps;
  letter-spacing: 0.035em;
  /* Fallback FIRST, so a browser without background-clip:text still shows a gold title
     rather than the invisible one that `color: transparent` would leave behind. */
  color: var(--lantern-glow);
  /* Depth and glow go through `filter`, NOT `text-shadow`, and this is not a style
     preference — it is the only thing that works. A text-shadow paints AFTER the
     element's background, so under background-clip:text it lands on top of the clipped
     gradient and turns the letters into muddy brown bevels. A filter composites the
     finished glyph instead, so the gold stays gold. It also applies to the flat-colour
     fallback above, which is why it sits out here rather than inside @supports. */
  filter:
    drop-shadow(0 2px 1px rgba(45, 26, 10, 0.9))
    drop-shadow(0 0 16px rgba(255, 206, 120, 0.55))
    drop-shadow(0 0 44px rgba(255, 184, 92, 0.32));
}

@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .world-title-name {
    /* Polished gold: pale highlight along the top, a bright band low down where the
       card catches the light, warm amber at the bottom. The stops start at 22% and stop
       at 92% because the ramp is measured over the whole line box while the glyphs only
       occupy its middle — run it 0-100% and the letters get just the dull centre of it. */
    background-image: linear-gradient(
      180deg,
      #fffbe9 22%,
      #ffeab4 40%,
      #ffd884 56%,
      #f5c463 68%,
      #ffe3a0 80%,
      #e2a948 92%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
  }
}

.world-title-sub {
  display: block;
  margin-top: 0.5em;
  font-size: 0.4em;
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  /* Not the gradient. At this size a gradient turns to mud, and the card keeps the
     subtitle flat gold for the same reason. */
  color: var(--lantern-glow);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85), 0 0 14px rgba(255, 200, 110, 0.45);
}

/* The chevrons the card sets on either side of the subtitle, pointing inward.
   Declared twice on purpose: the second form carries the empty alt text that stops a
   screen reader announcing "right-pointing double angle quotation mark" three times,
   and a browser that cannot parse it simply keeps the first. */
.world-title-sub::before,
.world-title-sub::after {
  opacity: 0.75;
  font-size: 0.85em;
}

.world-title-sub::before {
  content: "\00bb\00bb\00bb";
  content: "\00bb\00bb\00bb" / "";
  margin-right: 0.5em;
}

.world-title-sub::after {
  content: "\00ab\00ab\00ab";
  content: "\00ab\00ab\00ab" / "";
  /* letter-spacing already adds a gap after the last letter, so this side needs less. */
  margin-left: 0.26em;
}

/* On a phone the title cannot sit between the top controls. The light toggle and the two
   sound buttons are all fixed, all 64px, one in each top corner — which leaves a centred
   title about 78px of a 390px screen to live in, narrower than the word "Mori". Squeezing
   it in there is what made the subtitle wrap into the music button.

   So below the layout breakpoint it drops underneath them and takes the full width. It is
   still decorative and still pointer-events:none, so nothing it covers becomes untappable. */
@media (max-width: 559.98px) {
  .world-title {
    top: calc(max(1rem, env(safe-area-inset-top)) + var(--tap-min) + 0.6rem);
    max-width: calc(100% - 1.5rem);
  }

  .world-title-sub {
    /* Slightly tighter tracking so the line survives at 360px without breaking. */
    letter-spacing: 0.18em;
  }
}

.character {
  position: absolute;
  /* The browser must not pan or scroll while a finger is dragging a character. */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  padding: 0;
  background: none;
  box-shadow: none;
  border-radius: 0;
  line-height: 0;
  /* They are a delight, not a route — the 64px floor in main.css exists for actions a
     child must be able to complete, and it would blow their proportions up on a small
     scene. They are hidden entirely below 560px for the same reason (see below). */
  min-width: 0;
  min-height: 0;
  transform-origin: 50% 100%;      /* they stand on the ground, so they grow upward */
  display: none;                   /* scene layout only */
}

.character img {
  width: 100%;
  height: auto;
  display: block;
  pointer-events: none;            /* the button handles the drag, not the image */
  -webkit-user-drag: none;
  filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.45));   /* sits them ON the ground */
}

/* While held: lifted, brighter, and above everything so the child can see what they have.
   Deliberately NOT z-indexed at rest — a character parked on a place must never intercept
   the tap meant for that place. */
.character.is-dragging {
  z-index: 5;
  cursor: grabbing;
  animation: none;                 /* the idle loop would fight the drag */
  filter: drop-shadow(0 14px 22px rgba(0, 0, 0, 0.55)) brightness(1.06);
}

.character.is-selected img {
  filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.45))
          drop-shadow(0 0 14px rgba(255, 217, 138, 0.9));
}

/* --- Bigger / smaller ---------------------------------------------------- */
/* Two big buttons, not a pinch. Multi-touch is barred for this age. */

.character-tools {
  position: absolute;
  z-index: 6;
  transform: translate(-50%, -115%);
  display: flex;
  gap: 0.75rem;
}

.character-tools[hidden] { display: none; }

.character-tools button {
  width: var(--tap-min);
  height: var(--tap-min);
  display: grid;
  place-items: center;
  padding: 0;
  border-radius: 50%;
  background: var(--warm-gold);
  color: var(--wood-dark);
  box-shadow: var(--glow-warm);
}

.character-tools svg {
  width: 46%;
  height: 46%;
  fill: currentColor;
}

/* Idle. Mori breathes; Pico is a ball, so he floats and tips gently. Different periods
   on purpose — matched timing reads as a machine, not as two creatures. */
@keyframes mori-breathe {
  0%, 100% { transform: translateY(0)      scale(1, 1); }
  50%      { transform: translateY(-1.2%)  scale(1.012, 1.018); }
}

@keyframes pico-float {
  0%, 100% { transform: translateY(0)    rotate(-1.5deg); }
  50%      { transform: translateY(-5%)  rotate(1.5deg); }
}

/* Tap: squash, then stretch into a hop, then settle. The overshoot easing is what makes
   it read as alive rather than as a transition. */
@keyframes character-pop {
  0%   { transform: translateY(0)    scale(1, 1); }
  22%  { transform: translateY(0)    scale(1.14, 0.86); }
  55%  { transform: translateY(-11%) scale(0.93, 1.1); }
  78%  { transform: translateY(0)    scale(1.05, 0.95); }
  100% { transform: translateY(0)    scale(1, 1); }
}

/* --- SCENE mode: the places on the artwork ------------------------------- */

/* --- STACKED mode: the places as chips under the picture ------------------ */

.hotspot {
  position: static;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 1rem 1.5rem;
  background: rgba(111, 143, 69, 0.5);
  color: var(--cream);
  text-align: center;
  text-decoration: none;
  box-shadow: var(--glow-warm);
}

/* The one place that is actually open wears the warm gold of the "Enter the Forest"
   button, in BOTH layouts. Without it the playable area looks exactly like the four that
   are not, and a child has no way to tell where to go. */
.hotspot[data-state="open"] {
  background: var(--warm-gold);
  color: var(--wood-dark);
  font-weight: 600;
}

.hotspot[data-state="soon"] {
  background: rgba(107, 68, 41, 0.5);
  box-shadow: none;
  opacity: 0.8;
}

.hotspot-glow { display: none; }        /* scene mode only */

/* --- SCENE mode: the places on the artwork ------------------------------- */

/* The scene, at EVERY width.
 *
 * This used to start at 560px, and below it a phone got the picture with the places
 * listed underneath as chips. The owner was right that it was wrong: Mori and Pico
 * vanished entirely on a phone, and the hub stopped being a world. A child on a phone
 * should see the same forest a child on a tablet sees, only smaller.
 *
 * The phone block at the end of this file makes it survive 360px. */
@media (min-width: 0px) {
  .forest { padding: 1rem; }

  .forest-scene {
    display: block;
    max-width: 100%;
    /* Fit the viewport without cropping: the widest 1376:768 box whose height still fits.
       dvh so it tracks a mobile toolbar sliding in and out. 1376/768 = 1.792. */
    width: min(100%, calc((100dvh - 2rem) * 1.792));
    aspect-ratio: 1376 / 768;
  }

  .forest-picture {
    position: absolute;
    inset: 0;
    border-radius: 18px;
  }

  .forest-art {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* Standing on the bank at the foot of the falls — the one patch of foreground with no
     hotspot over it, so they never sit on top of a place the child needs to tap. */
  .character { display: block; }

  /* `top` rather than `bottom`, so dragging and the stylesheet speak one coordinate
     system. These values put them exactly where `bottom: 3%` did. */
  .character--mori { left: 30%; top: 73.6%; width: 9%; }
  .character--pico { left: 20%; top: 77.1%; width: 9.43%; }

  .character--mori { animation: mori-breathe 4.2s ease-in-out infinite; }
  .character--pico { animation: pico-float  3.4s ease-in-out infinite; }

  /* `.character.is-happy` scores 0,2,0 against `.character--mori`'s 0,1,0, so it wins
     and replaces the idle loop while it runs. This is the same specificity trap that
     previously painted a gold blob over the waterfall — stated here so it stays fixed. */
  .character.is-happy {
    animation: character-pop 640ms cubic-bezier(0.34, 1.56, 0.64, 1);
  }

  .hotspot {
    position: absolute;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    padding: 0;
    background: none;
    box-shadow: none;
    opacity: 1;
    border-radius: 999px;
  }

  /* Both state rules must be reset here BY NAME. `.hotspot { background: none }` above
     does not reach them: `.hotspot[data-state="open"]` scores 0,2,0 against its 0,1,0, so
     the stacked-mode gold chip won inside the scene too and painted a solid gold blob
     over the waterfall. In the scene the place itself lights up, not a filled shape. */
  .hotspot[data-state="open"],
  .hotspot[data-state="soon"] {
    background: none;
    color: var(--cream);
    font-weight: inherit;
    opacity: 1;
  }

  /* The place itself lights up. Always visible and always animating — never a hover
     effect, because the primary device has no hover. */
  .hotspot-glow {
    display: block;
    position: absolute;
    inset: 0;
    border-radius: 50%;
    animation: place-pulse 3.8s ease-in-out infinite;
    pointer-events: none;
  }

  /* Open: warm lantern gold, the colour the world uses for "come here". */
  .hotspot[data-state="open"] .hotspot-glow {
    background: radial-gradient(circle,
                rgba(255, 217, 138, 0.5) 0%, rgba(255, 217, 138, 0.18) 45%, transparent 70%);
  }

  /* Not yet open: the same invitation, cooler and quieter. Not "disabled". */
  .hotspot[data-state="soon"] .hotspot-glow {
    background: radial-gradient(circle,
                rgba(79, 214, 214, 0.34) 0%, rgba(79, 214, 214, 0.12) 45%, transparent 70%);
    animation-duration: 5.2s;
  }

  .hotspot-label {
    position: relative;
    transform: translateY(45%);
    padding: 0.35rem 0.9rem;
    border-radius: 999px;
    background: rgba(11, 18, 15, 0.72);
    white-space: nowrap;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  }

  /* The one place that is actually open wears the same warm gold as the "Enter the
     Forest" button on Home. Without this, the playable area looks exactly like the four
     that are not, and a child has no way to tell where to go. Colour is not the only
     signal — the label text and the aria-label say it too. */
  .hotspot[data-state="open"] .hotspot-label {
    background: var(--warm-gold);
    color: var(--wood-dark);
    font-weight: 600;
    text-shadow: none;
    box-shadow: var(--glow-warm);
  }

  @keyframes place-pulse {
    0%, 100% { transform: scale(1);    opacity: 0.85; }
    50%      { transform: scale(1.08); opacity: 1; }
  }

  /* Coordinates are percentages of the artwork, measured off the frame itself.
     Verified non-overlapping, and every box clears the 64px tap floor at 560px wide. */
  .hotspot[data-area="waterfall"]       { left:  7%; top: 32%; width: 20%; height: 28%; }
  .hotspot[data-area="story-treehouse"] { left: 39%; top: 20%; width: 22%; height: 26%; }
  .hotspot[data-area="puzzle-hollow"]   { left: 44%; top: 62%; width: 16%; height: 22%; }
  .hotspot[data-area="coloring-meadow"] { left: 74%; top: 62%; width: 22%; height: 24%; }
  .hotspot[data-area="memory-tree"]     { left: 79%; top: 14%; width: 18%; height: 26%; }
}

/* The gentle acknowledgement for a "not yet" tap. No failure language, ever. */
.hotspot.is-teasing { animation: tease 420ms ease-out; }

@keyframes tease {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.06); }
  100% { transform: scale(1); }
}


/* --- Phones ---------------------------------------------------------------
 * Three instructions from the owner, looking at a real phone:
 *   "the buttons are big and unnecessary", "only what works should be there",
 *   "and the forest on the whole screen".
 *
 * So on a phone:
 *   1. The label chips go. Five 200px chips cannot share a 360px-wide picture, and a
 *      three-year-old was never reading them. The glow is the affordance, and warm gold
 *      still means open. Every place keeps its aria-label, so assistive tech still names
 *      it.
 *   2. Only the two places that actually DO something are shown — the Waterfall and the
 *      Story Treehouse. The three Coming Soon areas are hidden here rather than disabled:
 *      on a small screen they are five glows competing for a finger, and three of them
 *      lead nowhere. They stay on tablet and desktop, where there is room to discover
 *      them. `display: none` and not `visibility`, so they are out of the tab order and
 *      out of the accessibility tree too — a screen reader should not announce a place a
 *      phone user cannot reach.
 *   3. The picture goes edge to edge and the boxes grow, so the forest is as large as a
 *      16:9 image can be on an upright phone.
 *
 * Every size below is measured against the worst case, 360x201: each box clears the 64px
 * floor, and none of them overlap.
 *
 * The honest limit: a 16:9 picture CANNOT fill an upright phone. Filling 844px of height
 * from a 1376x768 image means showing 26% of its width, which would crop the waterfall
 * off the screen entirely. Truly full-screen on a phone needs portrait artwork — logged
 * on the board as a missing asset.
 */

@media (max-width: 559.98px) {
  .forest { padding: 0; }

  .hotspot-label { display: none; }

  /* Only what works. */
  .hotspot[data-state="soon"] { display: none; }

  /* Both places moved up the picture to leave the lower half to the characters. */
  .hotspot[data-area="waterfall"]       { left:  4%; top: 12%; width: 28%; height: 34%; }
  .hotspot[data-area="story-treehouse"] { left: 38%; top: 12%; width: 30%; height: 34%; }

  /* Much bigger than on a tablet as a share of the picture. On a phone the picture is
     only ~200px tall, so at the tablet's 9% Mori was 32px and read as a speck; here she
     is a little over half the picture's height and is unmistakably a character standing
     in the forest. The 21.2 / 22.2 ratio is the one the two were exported at, so their
     size relative to each other is still exactly as drawn. */
  .character--mori { left:  2%; top: 44%;   width: 21.2%; }
  .character--pico { left: 24%; top: 52.2%; width: 22.2%; }

  /* The title is the biggest thing competing with the picture on a small screen. */
  .world-title { font-size: 1.25rem; }
}

/* --- A phone held sideways ------------------------------------------------
 * Here the forest CAN fill the screen, and upright it cannot. A landscape phone is about
 * 2.16 wide-to-tall and the artwork is 1.79 — close enough that covering the screen
 * crops only ~17% off the top and bottom, and every place and both characters still fit
 * inside what is left. Upright, the same trick would show 26% of the picture's width and
 * throw the waterfall off the screen entirely, which is why this is scoped to landscape.
 *
 * `max()` instead of the `min()` used everywhere else is the whole change: the box is
 * now the SMALLEST 1376:768 rectangle that still covers the viewport, rather than the
 * largest that fits inside it.
 *
 * Height-capped at 560px so it means "a phone on its side" and never a desktop window,
 * where the crop would be judged against a very different screen.
 */

@media (orientation: landscape) and (max-height: 560px) {
  .forest { padding: 0; }

  .forest-scene {
    width: max(100%, calc(100dvh * 1.792));
    max-width: none;
    aspect-ratio: 1376 / 768;
  }

  /* The overflow is clipped from the BOTTOM, not split evenly top and bottom — the box
     is top-aligned once it is taller than its container. Measured at 844x390: the visible
     band is the top 82.8% of the picture, and everything below that is gone. Mori and
     Pico were losing 30px each, about a third of their height, which is exactly their
     feet. These four move up into the band; verified afterwards that nothing is clipped
     at 844x390 or 740x360. */
  .character--mori { top: 56%; }
  .character--pico { top: 61%; }
  .hotspot[data-area="puzzle-hollow"]   { top: 57%; }
  .hotspot[data-area="coloring-meadow"] { top: 56%; }
}
