/* Title font: Fredoka; body text: Nunito. Both are under the SIL Open Font
   License (public/fonts/*-OFL.txt) and kept here rather than loaded from
   Google Fonts, so the page doesn't wait on, or tell, another site. */
@font-face {
  font-family: Fredoka;
  font-weight: 700;
  font-display: swap;
  src: url(./fonts/fredoka-700.woff2) format('woff2');
}

@font-face {
  font-family: Nunito;
  font-weight: 700;
  font-display: swap;
  src: url(./fonts/nunito-700.woff2) format('woff2');
}

/* Colors from the original marker drawing. */
:root {
  --paper: #fffdf7;
  --ink: #1f1a24;
  --froggles-green: #1f8a4c;
  --froggles-dark: #145a33;
  --enchantment: #5b2a9e;
}

* {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  overflow: hidden;
  position: relative;
  background: var(--paper);
  color: var(--ink);
  font-family: Nunito, system-ui, sans-serif;
  font-weight: 700;
  display: flex;
  flex-direction: column;
}

/* ---- Enchantments ---- */

.enchantments {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.enchantments svg {
  width: 100%;
  height: 100%;
}

.enchantments path {
  fill: none;
  stroke: var(--enchantment);
  stroke-width: 6;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke; /* same thickness on any screen shape */
  opacity: 0.85;
}

.enchantments .dot {
  stroke-width: 14;
}

/* ---- Title screen ---- */

.title-screen {
  position: relative; /* above the enchantments */
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  padding: 2rem 1rem;
  text-align: center;
}

/* Short screens (most phones): less space between things, so the buttons
   and the signature still fit without scrolling. */
@media (max-height: 720px) {
  .title-screen {
    gap: 0.8rem;
    padding: 1rem;
  }

  .title-screen .credit {
    margin-top: -0.4rem;
  }
}

/* The smallest phones: a smaller Mr. Froggles too. */
@media (max-height: 600px) {
  .title-screen .froggles {
    width: min(260px, 55vw, 18dvh);
  }

  .signature {
    padding: 0.4rem;
  }
}

/* Shrinks on short screens, so the buttons below still fit. */
.froggles {
  width: min(260px, 55vw, 28dvh);
  height: auto;
  animation: hover 3s ease-in-out infinite;
}

/* Mr. Froggles bobs gently up and down, as if flying in place. */
@keyframes hover {
  50% {
    transform: translateY(-14px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .froggles {
    animation: none;
  }
}

.logo {
  margin: 0;
  font-family: Fredoka, system-ui, sans-serif;
  font-weight: 700;
  font-size: clamp(2.5rem, 7vw, 5rem);
  line-height: 1.05;
  color: var(--froggles-dark);
  text-wrap: balance;
}

.tagline {
  margin: 0;
  max-width: 34rem;
  font-size: clamp(1.15rem, 2.4vw, 1.6rem);
  text-wrap: balance;
}

.credit {
  margin: -0.5rem 0 0; /* sits close under the legend it credits */
  font-size: clamp(0.95rem, 1.8vw, 1.15rem);
  font-style: italic;
  color: var(--froggles-dark);
  text-wrap: balance;
}

/* Keeps a name together on one line. */
.nowrap {
  white-space: nowrap;
}

/* Start, the main thing to press, in solid purple, with Read the legend
   below it in white. They're links to the next screen and the story, and
   both as wide as the wider one. */
.title-buttons {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: max-content;
  gap: 0.75rem;
  margin: 0.5rem 0 0;
}

.start,
.read-legend {
  padding: 0.5rem 1.5rem;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 3px solid var(--enchantment);
  border-radius: 999px;
  font-family: Fredoka, system-ui, sans-serif;
  font-size: 1.25rem;
  text-decoration: none;
}

.start {
  min-height: 56px;
  background: var(--enchantment);
  color: var(--paper);
  font-size: 1.6rem;
}

.start:hover {
  border-color: var(--ink);
}

/* Solid, so the swirls behind don't show through. */
.read-legend {
  background: var(--paper);
  color: var(--enchantment);
}

.read-legend:hover {
  background: var(--enchantment);
  color: var(--paper);
}

.start:focus-visible,
.read-legend:focus-visible {
  outline: 4px solid var(--ink);
  outline-offset: 4px;
}

/* ---- The story ---- */

/* The whole legend as a storybook, one page at a time
   (game/story-page.js). */
.story-book {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  padding: 1rem 1rem 2rem;
}

.story-pages {
  width: min(960px, 100%);
}

.story-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.9rem;
  padding: 1.5rem 1.75rem;
  border: 4px solid var(--ink);
  border-radius: 24px;
  background: #fff;
  box-shadow: 0 8px 0 var(--ink);
  font-size: clamp(1.1rem, 1.7vw, 1.25rem);
  line-height: 1.45;
}

/* Wider screens: the picture beside the words, so a page fits the window. */
@media (min-width: 760px) {
  .story-page {
    display: grid;
    grid-template-columns: minmax(0, 300px) 1fr;
    align-content: start;
    column-gap: 1.75rem;
    row-gap: 0.75rem;
  }

  .story-picture {
    grid-row: 1 / span 6;
    align-self: center;
  }

  .story-picture.story-froggles {
    justify-self: center;
  }

  .story-page h2 {
    text-align: left;
  }

  .story-end {
    justify-content: flex-start;
  }
}

/* Without the script, the pages show one after another. */
.story-pages:not(.one-at-a-time) .story-page + .story-page {
  margin-top: 1.5rem;
}

.story-page[hidden] {
  display: none;
}

.story-picture {
  width: min(100%, 420px);
  border: 4px solid var(--ink);
  border-radius: 18px;
}

.story-picture.story-froggles {
  width: 140px;
  border: none;
  border-radius: 0;
}

.story-page h2 {
  margin: 0;
  font-family: Fredoka, system-ui, sans-serif;
  font-size: clamp(1.8rem, 5vw, 2.6rem);
  line-height: 1.1;
  color: var(--froggles-dark);
  text-align: center;
  outline: none;
}

.story-page p {
  align-self: stretch;
  margin: 0;
}

.story-end {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  margin-top: 0.5rem;
}

/* Back a page, which page this is, and Next page. */
.story-nav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 0.75rem;
  width: min(960px, 100%);
  /* Stays at the bottom of the screen while a long page scrolls. */
  position: sticky;
  bottom: 0;
  padding: 0.75rem 0;
  background: var(--paper);
}

.story-nav[hidden] {
  display: none;
}

.story-prev {
  justify-self: start;
}

.story-next {
  justify-self: end;
}

.story-nav .story-button:disabled {
  visibility: hidden;
}

.story-count {
  margin: 0;
  font-family: Fredoka, system-ui, sans-serif;
  color: var(--froggles-dark);
}

a.story-button {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.story-next,
.story-play {
  background: var(--enchantment);
  color: #fff;
}

.story-prev,
.story-again {
  background: #fff1a8;
  color: var(--ink);
}

@media (max-width: 600px) {
  .story-page {
    padding: 1.1rem 1.2rem;
  }

  .story-nav .story-button {
    padding: 0 0.8rem;
    font-size: 1.05rem;
    white-space: nowrap;
  }
}

/* ---- Next screen ---- */

/* This page can be taller than a short screen, so let it scroll. */
body.scrolls {
  overflow: auto;
}

.next-screen {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2rem;
  padding: 2rem 1rem;
  text-align: center;
}

.next-screen h1 {
  margin: 0;
  font-family: Fredoka, system-ui, sans-serif;
  font-size: clamp(2rem, 6vw, 4rem);
  line-height: 1.1;
  color: var(--froggles-dark);
  text-wrap: balance;
}

/* The six kingdoms: two by two on a phone, three in a row on anything
   wider. */
.kingdoms {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
  width: min(560px, 100%);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 700px) {
  .kingdoms {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    width: min(820px, 100%);
  }
}

/* Each kingdom fills its grid cell, so a row's cards match in height. */
.kingdoms li {
  display: flex;
}

/* Each kingdom: its picture in a white frame, its name underneath. */
.kingdom {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.75rem 0.75rem 1rem;
  border: 3px solid var(--ink);
  border-radius: 24px;
  color: #fff;
  font-family: Fredoka, system-ui, sans-serif;
  font-size: clamp(1.1rem, 3.5vw, 1.6rem);
  line-height: 1.15;
  cursor: pointer;
  box-shadow: 0 6px 0 var(--ink);
  transition: transform 0.08s, box-shadow 0.08s;
}

.kingdom:hover {
  transform: translateY(-3px) rotate(-1deg);
  box-shadow: 0 9px 0 var(--ink);
}

.kingdom:active {
  transform: translateY(4px);
  box-shadow: 0 2px 0 var(--ink);
}

.kingdom:focus-visible {
  outline: 4px solid var(--ink);
  outline-offset: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .kingdom,
  .kingdom:hover,
  .kingdom:active {
    transition: none;
    transform: none;
  }
}

.kingdom-name {
  flex: 1;
  display: flex;
  align-items: center;
}

.kingdom-art {
  display: block;
  width: 100%;
  height: auto;
  border: 3px solid var(--ink);
  border-radius: 16px;
  background: #fff;
}

/* Dark enough for white text in every one. */
.kingdom-pond { background: #1f7a4c; }
.kingdom-woods { background: #4a5d23; }
.kingdom-clouds { background: #2f5fb8; }
.kingdom-moon { background: var(--enchantment); }
.kingdom-snow { background: #1b5e80; }
.kingdom-beach { background: #b8561b; }

/* A kingdom that has its screen is a link; it looks the same as the rest. */
a.kingdom {
  text-decoration: none;
}

/* ---- Kingdom screens ---- */

/* Each kingdom screen picks a theme on <body>: the page background, the
   title color, and the Back button's color. */
body.night {
  --page: #2b1f4a;
  --title: #fff1a8;
  --back: #fff1a8;
}

body.day {
  --page: #bfe6ff;
  --title: var(--ink);
  --back: #ffd23f;
}

body.forest {
  --page: #dcefc4;
  --title: var(--froggles-dark);
  --back: #ff8fb1;
}

body.pond {
  --page: #d9f2ff;
  --title: #1d4e7a;
  --back: #9bd67a;
}

body.snow {
  --page: #e3f1ff;
  --title: #23486f;
  --back: #9fd3f5;
}

body.beach {
  --page: #fff1d6;
  --title: #8a4b14;
  --back: #ffd23f;
}

body.night,
body.day,
body.forest,
body.pond,
body.snow,
body.beach {
  background: var(--page);
  overflow: auto;
}

.kingdom-screen {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  padding: 1rem;
}

/* On wider screens, Back sits in the corner beside the title rather than on
   a row of its own, so the picture and story box fit the window. */
@media (min-width: 700px) {
  .kingdom-screen {
    position: relative;
  }

  .kingdom-screen > .back {
    position: absolute;
    top: 1rem;
    left: 1rem;
  }
}

.kingdom-screen h1 {
  margin: 0;
  font-family: Fredoka, system-ui, sans-serif;
  font-size: clamp(2rem, 6vw, 3.5rem);
  color: var(--title);
}

/* Back sits in the top-left corner. */
.back {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 48px;
  padding: 0 1.25rem 0 0.9rem;
  border: 3px solid var(--ink);
  border-radius: 999px;
  background: var(--back);
  color: var(--ink);
  font-family: Fredoka, system-ui, sans-serif;
  font-size: 1.25rem;
  text-decoration: none;
  box-shadow: 0 5px 0 var(--ink);
}

.back svg {
  width: 1.4rem;
  height: 1.4rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 3.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.back:active {
  transform: translateY(4px);
  box-shadow: 0 1px 0 var(--ink);
}

.back:focus-visible {
  outline: 4px solid var(--title);
  outline-offset: 4px;
}

/* The picture, with room for the closing card on top of it. */
.scene-box {
  position: relative;
  width: min(1100px, 100%);
}

.scene {
  display: block;
  width: min(1100px, 100%);
  height: auto;
  aspect-ratio: 12 / 7;
  max-height: calc(100dvh - 190px);
  border: 4px solid var(--ink);
  border-radius: 24px;
  background: var(--page);
}

/* Taps go through Mr. Froggles' picture (and his sparkles) to whoever is
   behind, even when he flies over them. Only his head and body can be
   tapped (.froggles-hello, below). */
.scene .froggles-flying,
.scene .sparkles {
  pointer-events: none;
}

/* Mr. Froggles bobs in the air, the same as on the opening screen. */
.froggles-flying {
  animation: hover 3s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .froggles-flying {
    animation: none;
  }
}

/* Tap Mr. Froggles (just his head and body) and he does a flip and says
   hello in a speech bubble (game/quest-page.js). */
.froggles-hello {
  cursor: pointer;
  outline: none;
}

.froggles-hello:focus-visible .froggles-ring {
  stroke: var(--ink);
  stroke-dasharray: 8 6;
}

.scene[data-finale] .froggles-hello {
  pointer-events: none;
}

.froggles-flip {
  transform-box: fill-box;
  transform-origin: center;
}

.froggles-flip.flipping {
  animation: froggles-flip 0.9s ease-in-out;
}

@keyframes froggles-flip {
  50% {
    transform: translateY(-60px) rotate(180deg);
  }

  to {
    transform: rotate(360deg);
  }
}

.froggles-says {
  opacity: 0;
  pointer-events: none;
  transform-box: fill-box;
  transform-origin: 0% 100%;
}

.froggles-says.saying {
  animation: froggles-says 1.8s ease-out;
}

@keyframes froggles-says {
  0% {
    opacity: 0;
    transform: scale(0.3);
  }

  15%,
  80% {
    opacity: 1;
    transform: none;
  }

  100% {
    opacity: 0;
  }
}

.froggles-says path,
.froggles-says rect {
  fill: #fff;
  stroke: var(--ink);
  stroke-width: 5;
  stroke-linejoin: round;
}

.froggles-says text {
  fill: var(--ink);
  font-family: Fredoka, system-ui, sans-serif;
  font-size: 32px;
}

@media (prefers-reduced-motion: reduce) {
  .froggles-flip.flipping {
    animation: none;
  }

  .froggles-says.saying {
    animation: froggles-says-still 1.8s;
  }
}

@keyframes froggles-says-still {
  0%,
  80% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

/* ---- Quests ---- */

/* Mr. Froggles swoops over to cast an enchantment, hovers while it works,
   then flies back. Where to is set per quest (game/quest-page.js). */
.froggles-swoop.casting {
  animation: swoop 4.4s ease-in-out;
}

@keyframes swoop {
  12%,
  80% {
    transform: translate(var(--swoop-x), var(--swoop-y));
  }
}

/* Sparkles stream from him to what he enchants. */
.sparkle {
  fill: #fff1a8;
  stroke: #5b2a9e;
  stroke-width: 4;
  stroke-linejoin: round;
  opacity: 0;
  animation: sparkle-fly 0.8s ease-in both;
}

@keyframes sparkle-fly {
  from {
    transform: translate(var(--from-x), var(--from-y)) scale(0.5) rotate(0deg);
    opacity: 0;
  }
  20%,
  80% {
    opacity: 1;
  }
  to {
    transform: translate(var(--to-x), var(--to-y)) scale(1.2) rotate(90deg);
    opacity: 0;
  }
}

/* A scene with a story box under it leaves room for the title and the box
   by getting narrower, not shorter, so the top of the picture isn't trimmed
   off and the box's button fits in the window without scrolling. The room
   kept is for the tallest the box gets: someone talking, plus the Enchant
   button. The box doesn't get as narrow as the picture, so its words don't
   wrap onto more lines than they need. */
.scene-box:has(+ .story) {
  width: min(1100px, 100%, max(480px, calc((100dvh - 390px) * 12 / 7)));
}

.scene-box + .story {
  width: min(1100px, 100%, max(760px, calc((100dvh - 390px) * 12 / 7)));
}

.scene-box:has(+ .story) .scene {
  max-height: none;
}

/* Phones: the whole kingdom fits on the screen without scrolling. Back is a
   round button beside the title, and the picture takes whatever room is
   left above the story box. It's never so short that the top of the
   picture is trimmed off (12:7 shows all of it), and never taller than it
   is wide, so the friends near the sides stay in view. */
@media (max-width: 600px) {
  .kingdom-screen {
    position: relative;
    height: 100dvh;
    padding: 0.5rem 0.75rem 0.75rem;
    gap: 0.5rem;
  }

  .kingdom-screen > .back {
    position: absolute;
    top: 0.5rem;
    left: 0.75rem;
    width: 48px;
    padding: 0;
    justify-content: center;
  }

  /* Still read out by screen readers, just not shown. */
  .kingdom-screen .back-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .kingdom-screen h1 {
    display: flex;
    align-items: center;
    min-height: 48px;
    padding: 0 3.5rem;
    font-size: clamp(1.5rem, 7vw, 2rem);
    text-align: center;
  }

  .scene-box:has(+ .story) {
    flex: 1 1 0;
    width: 100%;
    min-height: calc((100vw - 1.5rem) * 7 / 12);
    max-height: calc(100vw - 1.5rem);
  }

  .scene-box:has(+ .story) .scene {
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
  }

  .scene-box + .story {
    flex: none;
    width: 100%;
    gap: 0.5rem;
    padding: 0.9rem 1.1rem;
    font-size: 1.1rem;
  }
}

/* Once everyone in a kingdom is helped (data-finale on the scene), Mr.
   Froggles finishes his last cast, twirls twice, and flies off the top of
   the screen. Then the closing card covers the picture. */
.froggles-exit {
  transform-box: fill-box;
  transform-origin: center;
}

.scene[data-finale] .froggles-exit {
  animation: froggles-exit 2.4s ease-in-out 3.8s forwards;
}

@keyframes froggles-exit {
  from {
    transform: translate(0, 0) rotate(0deg) scale(1);
  }
  40% {
    transform: translate(0, 0) rotate(720deg) scale(1);
  }
  to {
    transform: translate(800px, -650px) rotate(720deg) scale(0.5);
  }
}

.finale {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 1rem;
  border-radius: 24px;
  background: rgb(31 26 36 / 0.45);
  animation: fade-in 0.4s ease-out;
}

.finale[hidden] {
  display: none;
}

.finale-card {
  max-width: 32rem;
  padding: 1.5rem 1.75rem;
  border: 4px solid var(--ink);
  border-radius: 24px;
  background: var(--paper);
  color: var(--ink);
  text-align: center;
  box-shadow: 0 8px 0 var(--ink);
  animation: card-pop 0.5s cubic-bezier(0.2, 1.4, 0.4, 1);
}

.finale-card h2 {
  margin: 0 0 0.5rem;
  font-family: Fredoka, system-ui, sans-serif;
  font-size: clamp(1.8rem, 5vw, 2.6rem);
  color: var(--enchantment);
}

.finale-card p {
  margin: 0 0 1.25rem;
  font-size: clamp(1.05rem, 2.4vw, 1.3rem);
  line-height: 1.4;
}

.finale-buttons {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
}

.finale-next {
  display: inline-flex;
  align-items: center;
  background: var(--enchantment);
  color: #fff;
  text-decoration: none;
}

.finale-stay {
  background: #fff1a8;
  color: var(--ink);
}

/* Shorter laptop windows make the picture short too: a more compact card,
   with the buttons side by side, so it still fits over the picture. */
@media (min-width: 601px) and (max-height: 820px) {
  .finale-card {
    padding: 1rem 1.25rem;
  }

  .finale-card h2 {
    margin-bottom: 0.25rem;
    font-size: 2rem;
  }

  .finale-card p {
    margin-bottom: 0.9rem;
    font-size: 1.05rem;
  }

  .finale-buttons .story-button {
    min-height: 46px;
    padding: 0 1.1rem;
    font-size: 1.1rem;
  }
}

/* Phones: the picture can be short, so the closing card covers the whole
   screen instead, and is a little smaller. */
@media (max-width: 600px) {
  .finale {
    position: fixed;
    z-index: 10;
    border-radius: 0;
  }

  .finale-card {
    padding: 1rem 1.1rem;
  }

  .finale-card p {
    margin-bottom: 0.9rem;
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
}

@keyframes card-pop {
  from {
    transform: scale(0.6);
    opacity: 0;
  }
}

/* The picture changes with the quest's step, set on the scene as
   data-step="waiting" | "asked" | "done" (game/quest-page.js). It's on the
   scene, or on each quest's own group when a kingdom has more than one. */

/* Whoever needs help (.helper): a "!" that bobs, and a ring when focused. */
.helper {
  cursor: pointer;
  outline: none;
}

.helper .alert {
  animation: alert-bob 1.2s ease-in-out infinite;
}

@keyframes alert-bob {
  50% {
    transform: translateY(-6px);
  }
}

[data-step]:not([data-step='waiting']) .helper .alert {
  display: none;
}

.helper:focus-visible .helper-ring {
  stroke: #fff1a8;
}

/* Flicker's light: grey while it's out, glowing once the cave is enchanted. */
.firefly-light {
  transition: fill 0.6s;
}

.firefly-glow {
  transition: opacity 0.6s;
}

[data-step='done'] .firefly-light {
  fill: #ffe66d;
}

[data-step='done'] .firefly-glow {
  opacity: 1;
}

/* Moon Cave: a moonbeam shines down, carries Twinkle back up into the sky,
   and she twinkles. */
.moonbeam {
  opacity: 0;
  transition: opacity 0.6s 0.8s;
}

[data-step='done'] .moonbeam {
  opacity: 0.45;
}

.star-lift {
  transition: transform 1.8s ease-in-out 1.4s;
}

[data-step='done'] .star-lift {
  transform: translate(245px, -89px);
}

.star-body {
  transform-box: fill-box;
  transform-origin: center;
}

[data-step='done'] .star-body {
  animation: twinkle 1.6s ease-in-out 3.2s infinite;
}

@keyframes twinkle {
  50% {
    transform: scale(1.15) rotate(8deg);
  }
}

/* Moon Cave: the tiny twig grows into a twisty tree, then Dot flies up and
   hangs upside down from its branch. */
.tiny-twig {
  transition: opacity 0.4s 0.6s;
}

[data-step='done'] .tiny-twig {
  opacity: 0;
}

.bat-tree {
  opacity: 0;
  transform: scale(0.1);
  transform-box: fill-box;
  transform-origin: 50% 100%;
  transition: opacity 0.4s 0.6s, transform 0.9s 0.6s;
}

[data-step='done'] .bat-tree {
  opacity: 1;
  transform: none;
}

.bat-flight {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 1.6s ease-in-out 1.6s;
}

[data-step='done'] .bat-flight {
  transform: translate(103px, -100px) rotate(180deg);
}

/* The enchantment loops around the cave, then the cave lights up. */
.cave-glow {
  opacity: 0;
  transition: opacity 0.8s 0.6s;
}

[data-step='done'] .cave-glow {
  opacity: 1;
}

.enchant-swirl {
  opacity: 0;
  stroke-dasharray: 1700;
  stroke-dashoffset: 1700;
}

[data-step='done'] .enchant-swirl {
  opacity: 1;
  animation: draw-swirl 1.2s ease-out forwards;
}

@keyframes draw-swirl {
  to {
    stroke-dashoffset: 0;
  }
}

/* A swirl that fades once it's drawn, so it doesn't cover what it made. */
[data-step='done'] .enchant-swirl.fades {
  animation: draw-swirl 1.2s ease-out forwards, fade-out 0.6s 1.3s forwards;
}

@keyframes fade-out {
  to {
    opacity: 0;
  }
}

/* Whispering Woods: the little leaf grows into a bridge, then Hazel walks
   across to her family. */
.leaf-bridge {
  opacity: 0;
  transform: scale(0.15);
  transform-box: fill-box;
  transform-origin: 15% 70%;
  transition: opacity 0.4s 0.6s, transform 0.8s 0.6s;
}

[data-step='done'] .leaf-bridge {
  opacity: 1;
  transform: none;
}

.fallen-leaf {
  transition: opacity 0.4s 0.6s;
}

[data-step='done'] .fallen-leaf {
  opacity: 0;
}

/* Hazel steps up onto the leaf, walks across on top of it, and steps down
   on the far side. She ends where the last step puts her, even with reduced
   motion (no animation). */
[data-step='done'] .hazel-walk {
  transform: translate(330px, 10px);
  animation: hazel-cross 2s linear 1.4s backwards;
}

@keyframes hazel-cross {
  from {
    transform: none;
  }

  18% {
    transform: translate(60px, -50px);
  }

  50% {
    transform: translate(165px, -62px);
  }

  80% {
    transform: translate(270px, -50px);
  }

  to {
    transform: translate(330px, 10px);
  }
}

/* Cloud Kingdom: a breeze swirls up, the kite soars into the sky, and its
   string shows once it's up. */
.breeze,
.kite-string {
  opacity: 0;
  transition: opacity 0.6s 0.8s;
}

.kite-string {
  transition-delay: 2.6s;
}

[data-step='done'] .breeze,
[data-step='done'] .kite-string {
  opacity: 1;
}

.kite-flight {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 1.6s ease-in-out 1.2s;
}

[data-step='done'] .kite-flight {
  transform: translate(-150px, -495px) rotate(90deg);
}

/* Cloud Kingdom: the feather becomes wings on Lulu's back, then she flies
   up over the castle, flapping. */
.lone-feather {
  transition: opacity 0.4s 0.6s;
}

[data-step='done'] .lone-feather {
  opacity: 0;
}

.unicorn-wings {
  opacity: 0;
  transform: scale(0.1);
  transform-box: fill-box;
  transform-origin: 0% 100%;
  transition: opacity 0.4s 0.8s, transform 0.7s 0.8s;
}

[data-step='done'] .unicorn-wings {
  opacity: 1;
  transform: none;
}

.wing-flap {
  transform-box: fill-box;
  transform-origin: 0% 100%;
}

[data-step='done'] .wing-flap {
  animation: wing-flap 0.5s ease-in-out 1.6s infinite alternate;
}

@keyframes wing-flap {
  to {
    transform: rotate(-18deg) scaleY(0.85);
  }
}

.unicorn-flight {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 1.8s ease-in-out 1.8s;
}

[data-step='done'] .unicorn-flight {
  transform: translate(-60px, -490px) scale(0.8);
}

/* Whispering Woods: the mushroom grows tall and carries Olive up to her
   nest. */
.mushroom-lift {
  transition: transform 1.6s ease-in-out 1.2s;
}

[data-step='done'] .mushroom-lift {
  transform: translateY(-190px);
}

/* Whispering Woods: acorns grow all over the oak tree, then one drops into
   Nutmeg's paws. */
.acorn,
.acorn-drop {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}

.acorn {
  transform: scale(0.2);
  transition: opacity 0.4s 0.6s, transform 0.6s 0.6s;
}

.acorn:nth-child(even) {
  transition-delay: 0.9s;
}

[data-step='done'] .acorn {
  opacity: 1;
  transform: none;
}

.acorn-drop {
  transition: opacity 0.4s 1.2s, transform 0.7s ease-in 2s;
}

[data-step='done'] .acorn-drop {
  opacity: 1;
  transform: translateY(128px);
}

/* Frosty Peak: the rocks give way to an ice slide, and Pom slides down
   it. */
.frost-rocks {
  transition: opacity 0.4s 0.6s;
}

[data-step='done'] .frost-rocks {
  opacity: 0;
}

.ice-slide path {
  stroke-dasharray: 400;
  stroke-dashoffset: 400;
  transition: stroke-dashoffset 0.9s ease-out 0.6s;
}

[data-step='done'] .ice-slide path {
  stroke-dashoffset: 0;
}

.pom-ride {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 1.2s ease-in 1.6s;
}

[data-step='done'] .pom-ride {
  transform: translate(68px, 194px) rotate(-20deg);
}

/* Frosty Peak: Bo shivers until the snowball becomes an igloo, then goes
   over to its door. */
[data-step='waiting'] .shiver,
[data-step='asked'] .shiver {
  animation: shiver 0.16s linear infinite alternate;
}

@keyframes shiver {
  to {
    transform: translateX(1.5px);
  }
}

.shiver-lines,
.snowball {
  transition: opacity 0.4s 0.6s;
}

[data-step='done'] .shiver-lines,
[data-step='done'] .snowball {
  opacity: 0;
}

.igloo {
  opacity: 0;
  transform: scale(0.2);
  transform-box: fill-box;
  transform-origin: 50% 100%;
  transition: opacity 0.4s 0.6s, transform 0.8s 0.6s;
}

[data-step='done'] .igloo {
  opacity: 1;
  transform: none;
}

.bo-walk {
  transition: transform 1.2s ease-in-out 1.6s;
}

[data-step='done'] .bo-walk {
  transform: translate(126px, 2px);
}

/* Frosty Peak: the big snowflake becomes a red scarf on Mitten, and she
   hops for joy. */
.big-snowflake {
  transition: opacity 0.4s 0.6s;
}

[data-step='done'] .big-snowflake {
  opacity: 0;
}

.scarf {
  opacity: 0;
  transform: scale(0.2);
  transform-box: fill-box;
  transform-origin: 50% 0%;
  transition: opacity 0.4s 0.9s, transform 0.6s 0.9s;
}

[data-step='done'] .scarf {
  opacity: 1;
  transform: none;
}

.hare-hop {
  transform-box: fill-box;
  transform-origin: 50% 100%;
}

[data-step='done'] .hare-hop {
  animation: hare-hop 0.9s ease-out 1.6s 2;
}

@keyframes hare-hop {
  45% {
    transform: translateY(-26px);
  }
}

/* Seashell Beach: a big wave rolls up the sand to Tilly and back out, and
   she rides it into the sea. */
.big-wave {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: 50% 0%;
}

[data-step='done'] .big-wave {
  animation: wave-roll 2.6s ease-in-out 0.6s;
}

@keyframes wave-roll {
  0% {
    opacity: 0;
    transform: scaleY(0.2);
  }

  30%,
  65% {
    opacity: 1;
    transform: none;
  }

  100% {
    opacity: 0;
    transform: scaleY(0.2);
  }
}

.tilly-swim {
  transition: transform 1.4s ease-in-out 1.5s;
}

[data-step='done'] .tilly-swim {
  transform: translate(-60px, -210px);
}

.tilly-ripples {
  opacity: 0;
  transition: opacity 0.6s 2.9s;
}

[data-step='done'] .tilly-ripples {
  opacity: 1;
}

/* Seashell Beach: the palm tree swings and tosses the beach ball over to
   Splash. The ball goes sideways steadily and up then down (an arc), and
   she jumps for joy with it on her nose. */
.palm-toss {
  transform-box: fill-box;
  transform-origin: 50% 100%;
}

[data-step='done'] .palm-toss {
  animation: palm-toss 1s ease-in-out 0.6s;
}

@keyframes palm-toss {
  40% {
    transform: rotate(-6deg);
  }

  70% {
    transform: rotate(7deg);
  }
}

.ball-across {
  transition: transform 1.1s linear 1.2s;
}

[data-step='done'] .ball-across {
  transform: translateX(226px);
}

.ball-arc {
  transition: transform 1.1s cubic-bezier(0.3, -1.4, 0.7, 1) 1.2s;
}

[data-step='done'] .ball-arc {
  transform: translateY(112px);
}

[data-step='done'] .splash-jump {
  animation: splash-jump 0.9s ease-out 2.5s 2;
}

@keyframes splash-jump {
  45% {
    transform: translateY(-30px);
  }
}

/* Seashell Beach: the tiny pink shell grows into a big swirly shell on
   Shelly's back, her old one disappears, and she dances side to side. */
.little-shell,
.tiny-shell {
  transition: opacity 0.4s 0.6s;
}

[data-step='done'] .little-shell,
[data-step='done'] .tiny-shell {
  opacity: 0;
}

.big-shell {
  opacity: 0;
  transform: scale(0.2);
  transform-box: fill-box;
  transform-origin: 50% 100%;
  transition: opacity 0.4s 0.9s, transform 0.7s 0.9s;
}

[data-step='done'] .big-shell {
  opacity: 1;
  transform: none;
}

[data-step='done'] .shelly-dance {
  animation: shelly-dance 0.5s ease-in-out 1.8s 4 alternate;
}

@keyframes shelly-dance {
  from {
    transform: translateX(-10px);
  }

  to {
    transform: translateX(10px);
  }
}

/* Home Pond: Wiggles the tadpole wiggles his tail, then once he's
   enchanted, the tadpole fades and a little frog hops onto the pad. */
.tadpole-tail {
  transform-box: fill-box;
  transform-origin: 100% 50%;
  animation: wiggle 0.6s ease-in-out infinite alternate;
}

@keyframes wiggle {
  to {
    transform: rotate(14deg);
  }
}

.tadpole {
  transition: opacity 0.4s 0.6s;
}

[data-step='done'] .tadpole {
  opacity: 0;
}

.froglet {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: 50% 100%;
}

[data-step='done'] .froglet {
  animation: froglet-hop 1.4s ease-out 0.9s both;
}

/* He's drawn in the middle of the pad, so he starts back where the tadpole
   was and hops over onto it. */
@keyframes froglet-hop {
  from {
    opacity: 0;
    transform: translate(-30px, 4px) scale(0.3);
  }
  30% {
    opacity: 1;
    transform: translate(-30px, 4px);
  }
  55% {
    opacity: 1;
    transform: translate(-15px, -30px);
  }
  80%,
  to {
    opacity: 1;
    transform: none;
  }
}

/* Home Pond: the water lily blooms, then Bumble flies down into it. */
.lily-bud {
  transition: opacity 0.4s 0.6s;
}

[data-step='done'] .lily-bud {
  opacity: 0;
}

.lily-bloom {
  opacity: 0;
  transform: scale(0.2);
  transform-box: fill-box;
  transform-origin: 50% 100%;
  transition: opacity 0.4s 0.6s, transform 0.8s 0.6s;
}

[data-step='done'] .lily-bloom {
  opacity: 1;
  transform: none;
}

.bee-flight {
  transition: transform 1.4s ease-in-out 1.6s;
}

[data-step='done'] .bee-flight {
  transform: translate(88px, 62px);
}

/* Home Pond: the rock becomes a lily pad, the pad floats Pebble to shore,
   and she steps off onto the grass. */
.pond-rock {
  transition: opacity 0.4s 0.6s;
}

[data-step='done'] .pond-rock {
  opacity: 0;
}

.new-pad {
  opacity: 0;
  transform: scale(0.2);
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity 0.4s 0.6s, transform 0.8s 0.6s;
}

[data-step='done'] .new-pad {
  opacity: 1;
  transform: none;
}

.snail-raft {
  transition: transform 1.8s ease-in-out 1.5s;
}

[data-step='done'] .snail-raft {
  transform: translate(-140px, -162px);
}

.snail-ashore {
  transition: transform 0.7s ease-out 3.4s;
}

[data-step='done'] .snail-ashore {
  transform: translate(-16px, -26px);
}

/* Cloud Kingdom: the rain stops, the rain cloud becomes a rainbow, then
   Pip flies up to sit on top of it. */
.rain {
  animation: rain-fall 0.8s linear infinite;
}

@keyframes rain-fall {
  from {
    transform: translateY(-8px);
  }
  to {
    transform: translateY(8px);
  }
}

.rain-cloud {
  transition: opacity 0.4s 0.6s;
}

[data-step='done'] .rain-cloud {
  opacity: 0;
}

.rainbow {
  opacity: 0;
  transform: scale(0.2);
  transform-box: fill-box;
  transform-origin: 50% 100%;
  transition: opacity 0.4s 0.6s, transform 0.8s 0.6s;
}

[data-step='done'] .rainbow {
  opacity: 1;
  transform: none;
}

.bird-flight {
  transition: transform 1.6s ease-in-out 1.5s;
}

[data-step='done'] .bird-flight {
  transform: translateY(-150px);
}

@media (prefers-reduced-motion: reduce) {
  [data-step='done'] .froglet {
    animation: none;
    opacity: 1;
  }

  .scene[data-finale] .froggles-exit {
    animation: fade-out 0.6s 3.8s forwards;
  }

  .helper .alert,
  [data-step='done'] .wing-flap,
  .tadpole-tail,
  [data-step='waiting'] .shiver,
  [data-step='asked'] .shiver,
  [data-step='done'] .hare-hop,
  [data-step='done'] .hazel-walk,
  [data-step='done'] .big-wave,
  [data-step='done'] .palm-toss,
  [data-step='done'] .splash-jump,
  [data-step='done'] .shelly-dance,
  .finale,
  .finale-card,
  .froggles-swoop.casting,
  .sparkle,
  .rain,
  [data-step='done'] .star-body {
    animation: none;
  }

  [data-step='done'] .enchant-swirl {
    animation: none;
    stroke-dashoffset: 0;
  }

  [data-step='done'] .enchant-swirl.fades {
    opacity: 0;
  }

  .cave-glow,
  .moonbeam,
  .star-lift,
  .tiny-twig,
  .bat-tree,
  .bat-flight,
  .firefly-light,
  .firefly-glow,
  .leaf-bridge,
  .fallen-leaf,
  .mushroom-lift,
  .acorn,
  .acorn-drop,
  .pond-rock,
  .new-pad,
  .snail-raft,
  .snail-ashore,
  .frost-rocks,
  .ice-slide path,
  .pom-ride,
  .shiver-lines,
  .snowball,
  .igloo,
  .bo-walk,
  .big-snowflake,
  .scarf,
  .tilly-swim,
  .tilly-ripples,
  .ball-across,
  .ball-arc,
  .little-shell,
  .tiny-shell,
  .big-shell,
  .tadpole,
  .lily-bud,
  .lily-bloom,
  .bee-flight,
  .rain-cloud,
  .rainbow,
  .breeze,
  .kite-string,
  .kite-flight,
  .lone-feather,
  .unicorn-wings,
  .unicorn-flight,
  .bird-flight {
    transition: none;
  }
}

/* The story box under the picture. */
.story {
  width: min(1100px, 100%);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 1.25rem 1.5rem;
  border: 4px solid var(--ink);
  border-radius: 24px;
  background: var(--paper);
  color: var(--ink);
  font-size: clamp(1.15rem, 2.4vw, 1.4rem);
  line-height: 1.4;
}

.story p {
  margin: 0;
}

.story-speaker {
  font-family: Fredoka, system-ui, sans-serif;
  color: var(--enchantment);
}

.story-button {
  min-height: 52px;
  padding: 0 1.5rem;
  border: 3px solid var(--ink);
  border-radius: 999px;
  font-family: Fredoka, system-ui, sans-serif;
  font-size: 1.3rem;
  cursor: pointer;
  box-shadow: 0 5px 0 var(--ink);
}

.story-button:active {
  transform: translateY(4px);
  box-shadow: 0 1px 0 var(--ink);
}

.story-button:focus-visible {
  outline: 4px solid var(--enchantment);
  outline-offset: 4px;
}

.enchant {
  background: var(--enchantment);
  color: #fff;
}

.signature {
  position: relative;
  padding: 1rem;
  text-align: center;
  font-size: 0.9rem;
  opacity: 0.7;
}
