/* ---- The hero scene ----------------------------------------------------
   The savanna the widget draws, in CSS. Geometry is the app's, not invented, and every
   constant below is pinned against the Swift source by check.sh's check_geometry — if they
   drift, the site stops being the widget and becomes an artist's impression of it.

   Lives in its own file rather than in styles.css because index.html is the only page with
   a scene, and index.html no longer links styles.css — the redesigned landing page carries
   its own inlined design system. Keeping these rules in a stylesheet the scene's own page
   does not load is how you get a check_geometry that validates a copy nobody serves, so
   there is exactly one copy and check_geometry greps this file.

   **This file now reproduces LayeredSkinScene, not ProceduralSkinScene.** That is the whole
   difference from the version before it. SkinAppearance.savanna is `.layered`, and
   SkinAppearance.all is `[savanna]` — so the layered path is what every user sees and the
   procedural one is unreachable. The page used to draw the procedural scene: a CSS sky
   gradient, a ground rectangle, an ellipse for the water, and three acacia SVGs positioned
   at 0.25 / 0.5 / 0.75. All of it is now inside the background SVG, which is the entire scene
   as one drawing, so the page composites two images instead of placing eight boxes.

   **Everything here is a fraction; nothing is a widget point.** That is the other difference,
   and it is why the .scene-stage / --scale machinery is gone from the scene (the phone
   mockup's .app-stage still uses it, for its own 390x844 screen). The procedural scene had
   to scale a fixed 338x158 stage because the app does NOT scale its constants with tile size
   — a 24pt inset is 24pt on any tile. LayeredSkinScene has no such constants: every value in
   it is a fraction of the surface or of the artwork's own canvas, so the same rules are
   correct at any size with no stage and no scale factor.

   The three insets the old file warned about (--inset, --finish-inset, --marker-inset) are
   gone with the code that used them. The layered run has exactly two ends, --start and
   --finish, and they are not the procedural ones. */

.scene {
  /* The surface this scene is drawn on, in the app's own units — used ONLY to work out how
     the background's overflow is cropped (see --canvas-h). The widget's medium tile is
     338x158; the app's Today screen draws SkinScene 200 tall inside TodayView's 16pt
     padding, so 358x200 on a 390pt phone. */
  --tile-w: 338;
  --tile-h: 158;

  /* LayeredSkinScene.backgroundSizes[.mediumWidget] — the background's own viewBox.

     **Per surface since the V2 artwork, which is why this is a default and not a constant.**
     Each surface gets a composition drawn for its own aspect ratio rather than one drawing
     cropped three ways: .smallWidget is 530x530, .mediumWidget 1112x530, .banner 1160x620.
     The values here are the medium widget's, because the widget tile is what .scene draws by
     default; the phone's Today card is a .banner and overrides both, beside its own
     --tile-w/--tile-h. A scene that sets one pair without the other is the bug this pairing
     exists to make obvious.

     Re-export a background and both its size here and --ground-line have to be re-measured. */
  --bg-w: 1112;
  --bg-h: 530;

  /* No foreground. The V2 backgrounds contain the complete scene including any foreground
     detail, so LayeredSkinScene composites two layers where it used to composite three, and
     savanna-foreground.svg is no longer drawn or shipped. */

  /* LayeredSkinScene.walkingSize / .drinkingSize. Both poses came off one canvas at one
     scale, so a single points-per-unit factor sizes either; storing the sizes rather than
     the aspect ratios is what makes that true. Framing both to a common HEIGHT was the
     shipped bug in the app — a drinking gazelle is 204 units tall against a walking one's
     309 because its head is down, so pinning them to one height rendered it 1.51x oversized
     the moment the goal was met. */
  --walk-w: 302;
  --walk-h: 309;
  --drink-w: 298;
  --drink-h: 204;

  /* LayeredSkinScene.animalHeightFraction — the walking pose's height as a fraction of the
     scene's. The reference pose: every other pose is drawn at the scale this one sets.
     0.22 until the V2 artwork; the animal was enlarged against the new backgrounds. */
  --animal-h: 0.28;

  /* LayeredSkinScene.groundLineFractions[.mediumWidget]. A fraction of the BACKGROUND
     CANVAS's height, not of the scene's — the distinction that matters, and the one the app
     got wrong first. The background is scaled to fill and the overflow cropped, so how much
     of the drawing's height survives depends on the surface's aspect ratio.

     Per surface since V2, and for a second reason on top of the crop: each background is its
     own composition, so the watering hole is drawn at a different height in each one. This
     is the medium widget's; the banner's 0.776 is set beside it in index.html. Measured off
     the rendered artwork by scanning for the top edge of the water, which is the near shore
     a drinking animal stands on. */
  --ground-line: 0.826;

  /* LayeredSkinScene.startFraction and .finishFractions[.mediumWidget] — where the animal
     stands at progress 0 and 1, as fractions of the scene's width. The start is one value for
     every surface; the finish is not, because each background puts the water's left edge
     somewhere different. This is the medium widget's; the banner's 0.81 is in index.html. */
  --start: 0.08;
  --finish: 0.82;

  --progress: 0.42;

  position: relative;
  width: 100%;
  aspect-ratio: var(--tile-w) / var(--tile-h);
  /* No bottom margin. The scene sits inside .visual-panel, which has padding: 0 and a white
     background, so any margin here renders as a white band under the ground. */
  margin: 0;
  /* Matches .visual-panel's radius: the panel clips with overflow: hidden, so a smaller
     radius here leaves white slivers in the corners. */
  border-radius: 24px;
  overflow: hidden;

  /* ---- The fill crop, derived rather than measured -----------------------
     `Image(background).scaledToFill()` scales the canvas by max(w/bgW, h/bgH) and centre-
     crops the overflow. Reproduced here as object-fit: cover on the image itself; the two
     properties below recover WHERE the ground line ended up after that crop, which is the
     one thing cover does not tell you.

       --canvas-h  the scaled canvas's height, as a multiple of the scene's height.
                   max(1, …) IS the max() in the Swift: above 1 the surface is wider than the
                   artwork and the fill is width-driven, with the canvas overflowing top and
                   bottom; at 1 it is height-driven and the canvas exactly fills the height.
                   Both branches are still live on this page, and only just: the widget tile
                   is 2.139:1 against savanna-bg-medium's 2.098:1 (width-driven, 1.020), and
                   the phone's Today card is 1.790:1 against savanna-bg-banner's 1.871:1
                   (height-driven, so 1.0). Hard-coding either branch would put the gazelle's
                   hooves off the ground in the other one. The margins are far narrower than
                   they were before the V2 artwork, when one drawing served every surface at
                   1.81:1 — each background is now drawn close to the shape it is shown in.
       --ground-y  the ground line as a fraction of the SCENE's height, which is what CSS can
                   position against. Exactly Swift's
                   (height - canvasHeight) / 2 + canvasHeight * groundLineFraction,
                   divided through by the scene height. */
  --canvas-h: max(1, calc((var(--tile-w) / var(--tile-h)) / (var(--bg-w) / var(--bg-h))));
  --ground-y: calc(
    (1 - var(--canvas-h)) / 2 + var(--canvas-h) * var(--ground-line)
  );
}

/* The phone mockup's screen, drawn at the app's own 390x844 and scaled to the mockup, so its
   type sizes and spacings can be the app's literal points rather than guesses that happen to
   look right at one size. scene.js sets --scale from the rendered width.

   This is the ONLY remaining user of the stage-and-scale approach. The scene itself no longer
   needs it: LayeredSkinScene has no fixed-point constants to preserve, so .scene positions
   everything as a fraction and is correct at any size with no scale factor. The class name is
   shared with .app-stage's markup for that history; nothing else uses it. */
.scene-stage {
  position: absolute;
  top: 0;
  left: 0;
  width: calc(var(--tile-w) * 1px);
  height: calc(var(--tile-h) * 1px);
  transform: scale(var(--scale, 1));
  transform-origin: 0 0;
}

/* Layer 1 of 2: the whole scene as one drawing — sky, mountains, acacias, watering
   hole, and the foreground detail that used to be a third layer.
   object-fit: cover is scaledToFill; the .scene's overflow: hidden is .clipped(). */
.scene-background {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Layer 2 of 2: the gazelle.

   Sized from its height and its own viewBox's aspect ratio rather than from a width and a
   height, so the frame cannot disagree with the drawing and squash it — the CSS equivalent
   of the app deriving both dimensions from one points-per-unit factor.

   The hooves land on --ground-y, so the centre sits half a pose above it. Stating it that
   way rather than as a top edge is what keeps the feet on one line when the pose changes
   height. */
.scene-animal {
  position: absolute;
  height: calc(var(--animal-h) * 100%);
  aspect-ratio: var(--walk-w) / var(--walk-h);
  width: auto;
  left: calc(
    (var(--start) + (var(--finish) - var(--start)) * var(--progress)) * 100%
  );
  top: calc((var(--ground-y) - var(--animal-h) / 2) * 100%);
  transform: translate(-50%, -50%);
  cursor: grab;
  touch-action: none;
}
.scene-animal:active {
  cursor: grabbing;
}

/* The drinking pose, at progress 1.0 — the app's `isAtFinish`. Height comes from the SHARED
   unit (animal-h is the walking pose's), so the animal keeps its size and only changes
   posture: drinkingHeight = animalHeight * (drinkH / walkH). Writing `--animal-h` here
   instead is the 1.51x bug, in CSS.

   **`top` has to move with `height`, and that is the whole reason --drink-frac exists.**
   Both rules place a CENTRE, so the centre is half a pose above the ground line — and this
   pose is shorter. Overriding the height alone leaves the centre where the taller walking
   frame's was, which lifts this frame's bottom (0.22 - 0.1452) / 2 = 0.0374 of the scene's
   height clear of the ground: on the 236px-tall hero tile, a gazelle floating 9px above its
   own shadow. The app never had this bug — `animalY = groundY - animalSize.height / 2`
   reads the CURRENT pose's size — and it is invisible until you actually reach the water. */
.scene {
  --drink-frac: calc(var(--animal-h) * (var(--drink-h) / var(--walk-h)));
}
.scene[data-pose='drinking'] .scene-animal {
  height: calc(var(--drink-frac) * 100%);
  aspect-ratio: var(--drink-w) / var(--drink-h);
  top: calc((var(--ground-y) - var(--drink-frac) / 2) * 100%);
}

/* ---- Widget HUD overlay -------------------------------------------------
   The step count, goal, and streak bubble that MediumWidgetLayout floats in
   the top-left corner. Positioned as the Swift does it: a VStack(alignment:
   .leading, spacing: 6) with .padding(12), white text with a drop shadow for
   legibility against the sky. Sizes are the Swift layout's: 22pt bold count,
   caption2 (~11px) for "steps" and "of N", a 30px streak bubble. */
.widget-hud {
  position: absolute;
  top: 7.6%;
  left: 3.6%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  pointer-events: none;
  z-index: 2;
}
.widget-hud-steps {
  display: flex;
  align-items: baseline;
  gap: 3px;
}
.widget-hud-count {
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif;
  font-size: clamp(14px, 2.4vw, 22px);
  font-weight: 700;
  color: #fff;
  line-height: 1;
}
.widget-hud-unit {
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif;
  font-size: clamp(8px, 1.2vw, 11px);
  color: rgba(255, 255, 255, 0.85);
  line-height: 1;
}
.widget-hud-goal {
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif;
  font-size: clamp(8px, 1.2vw, 11px);
  color: rgba(255, 255, 255, 0.85);
  line-height: 1;
  margin-top: -4px;
}
.widget-hud-steps,
.widget-hud-goal {
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.55));
}
.widget-hud-streak {
  position: relative;
  width: clamp(22px, 3.2vw, 30px);
  height: clamp(22px, 3.2vw, 30px);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.35);
}
.widget-hud-flame {
  position: absolute;
  font-size: clamp(14px, 2.2vw, 20px);
  opacity: 0.28;
  line-height: 1;
}
.widget-hud-streak-num {
  position: relative;
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif;
  font-size: clamp(8px, 1.3vw, 12px);
  font-weight: 700;
  color: #fff;
  line-height: 1;
}

.scene-hint {
  color: var(--ink-soft, var(--stone-500));
  font-size: 0.85rem;
  margin: 0.75rem 0 0;
  text-align: center;
}

/* The gazelle glides to its new position while being dragged. Under reduced motion it still
   moves — dragging must keep working — but without the easing. */
.scene-animal {
  transition: left 0.08s linear;
}
.scene-animal:focus-visible {
  outline: 2px solid var(--link, var(--amber-600));
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .scene-animal {
    transition: none;
  }
}
