/* ---- Screens ---- */
/* Screens, drawers and the scrim only get transitions after first paint
   (html.booted, set in main.js). WebKit applies stylesheets in stages while
   loading and will animate from the unstyled state, so without this gate the
   drawers visibly slide across the app on every load. */
.screen {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  opacity: 0;
  transform: scale(0.985);
  pointer-events: none;
  background: var(--paper);
}
html.booted .screen {
  transition: opacity 0.28s ease, transform 0.28s ease;
}
.screen.active {
  opacity: 1;
  transform: scale(1);
  pointer-events: auto;
}

/* ---- Shared buttons and controls ---- */
button {
  font-family: inherit;
  font-weight: 500;
  border: 2.5px solid var(--ink);
  background: var(--paper-light);
  color: var(--ink);
  padding: 8px 14px;
  border-radius: 10px;
  cursor: pointer;
  font-size: 14px;
  box-shadow: var(--sh-2);
  transition: transform 0.08s ease, box-shadow 0.08s ease;
  position: relative;
  appearance: none;
  -webkit-appearance: none;
  line-height: 1.2;
}
button:hover { transform: translateY(-1px); }
button:active { transform: translateY(3px); box-shadow: 0 1px 0 rgba(58, 38, 24, 0.28); }
button:focus { outline: none; }
button:focus-visible { outline: 2px dashed var(--ink); outline-offset: 3px; }

.icon-btn {
  width: 44px;
  height: 44px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
}

.back-btn {
  width: 44px;
  height: 44px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  background: var(--paper-light);
}
/* Logo lockup */
.logo {
  display: flex;
  align-items: center;
  gap: 12px;
  font-weight: 700;
  font-size: 19px;
  letter-spacing: 0.01em;
  color: var(--ink);
}
.header-logo-img {
  display: block;
  width: 158px;
  height: auto;
  max-height: 48px;
  object-fit: contain;
  filter: drop-shadow(0 3px 0 rgba(58,38,24,0.12));
}

/* ---- Shared screen header (home, about) ---- */
.screen-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  padding-top: max(14px, env(safe-area-inset-top));
  background: var(--paper-light);
  border-bottom: 2.5px solid var(--ink);
  flex-shrink: 0;
  min-height: 70px;
  position: relative;
  z-index: 10;
}
.screen-header .header-controls {
  display: flex;
  gap: 8px;
}

/* ---- Intro screen ---- */
#screen-intro {
  align-items: center;
  justify-content: center;
  /* Same dark wash as the pause scrim and the song-end card. It used to be a
     cream one, which bleached the backdrop toward paper; darkening keeps its
     own colour, and one treatment covers every "set's there, not the subject"
     moment. */
  background:
    linear-gradient(rgba(46, 30, 18, 0.72), rgba(46, 30, 18, 0.72)),
    url('../assets/background-landscape.webp') center / cover no-repeat,
    var(--paper);
}
/* Plate follows the window's orientation, which works because the stage is
   capped at 150dvh: a landscape window always gives a landscape stage. Back
   when the cap was a flat 1024px the two could disagree and this rule had to
   compensate. Repeated at every plate swap below; keep them in step. */
@media (orientation: portrait) {
  #screen-intro {
    background:
      linear-gradient(rgba(46, 30, 18, 0.72), rgba(46, 30, 18, 0.72)),
      url('../assets/background-portrait.webp') center / cover no-repeat,
      var(--paper);
  }
}
.intro-content {
  max-width: 460px;
  text-align: center;
  padding: 28px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}
.intro-logo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-bottom: 8px;
}
.intro-logo-img {
  display: block;
  width: min(420px, 86vw);
  height: auto;
  filter: drop-shadow(0 6px 0 rgba(58,38,24,0.16));
}
.intro-xylo-img {
  display: block;
  width: min(360px, 78vw);
  height: auto;
  margin: -4px 0 2px;
  filter: drop-shadow(0 5px 0 rgba(58,38,24,0.15));
}
.intro-content p {
  font-size: 15px;
  line-height: 1.6;
  /* cream, not ink: the wash behind it is dark now */
  color: var(--paper-light);
  font-weight: 400;
  max-width: 380px;
}
.intro-continue {
  margin-top: 12px;
  background: var(--c4);
  color: var(--ink);
  font-size: 16px;
  font-weight: 600;
  padding: 12px 28px;
  border-radius: 14px;
}
.intro-continue:hover { background: #62a942; }

/* ---- Stage backdrop ----
   One paper-craft illustration per orientation. Both are composed with a
   quiet centre, and gameplay and the song grid sit over that. */
#screen-home {
  position: relative;
  /* Sky gradient paints straight away while the illustration loads. Anchored
     to the bottom so the ground stays put; on wide screens that also crops off
     the illustration's own painted swag, which the hanging valance replaces. */
  background:
    url('../assets/background-landscape.webp') center bottom / cover no-repeat,
    radial-gradient(120% 90% at 50% -20%, #6fb8dd 0%, #9fd0e8 32%, var(--paper) 70%);
}
@media (orientation: portrait) {
  #screen-home {
    background:
      url('../assets/background-portrait.webp') center / cover no-repeat,
      radial-gradient(120% 90% at 50% -20%, #6fb8dd 0%, #9fd0e8 32%, var(--paper) 70%);
  }
}

/* Curtain valance hanging over the top edge; the song grid scrolls under it.
   The header is transparent so the logo and buttons sit on the cloth.

   Stretched to fill, never repeated — a repeat left a visible seam wherever
   the tile didn't divide the width evenly. The cost of stretching is that the
   art squashes if the box gets shorter than its own aspect, so each layout
   gets a crop whose natural shape is close to the band it needs:

     landscape/desktop   the full drop
     portrait tablets    a 1566-wide slice
     portrait phones     a 716-wide slice — narrower renders taller, which is
                         what deepens the band
     landscape phones    the full width, cropped down to 190 rows — see the
                         short-viewport rule near the bottom of this file */
.curtain-top {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  aspect-ratio: 2172 / 250;
  max-height: 21vh;
  background: url('../assets/curtain-top-landscape.webp') top center / 100% 100% no-repeat;
  z-index: 9;
  pointer-events: none;
  filter: drop-shadow(0 5px 0 rgba(58, 38, 24, 0.18));
}
@media (orientation: portrait) {
  .curtain-top {
    aspect-ratio: 1566 / 250;
    background-image: url('../assets/curtain-top-portrait.webp');
  }
}

@media (orientation: portrait) and (max-width: 560px) {
  /* Narrower window on the same art, so it renders taller: at 390 wide that's
     a 136px valance with 92px of solid red, and the header row sits in cloth
     with 16px above and 16 below. The slice's left and right edges were cut at
     matching scallop depth so it meets both screen edges the same way. */
  .curtain-top {
    aspect-ratio: 716 / 250;
    max-height: 28vh;
    background-image: url('../assets/curtain-top-phone.webp');
  }
  /* At 40px the logo was a small mark adrift in a lot of red, which is why the
     row looked unsettled wherever it was put. Only the height cap changes —
     the 122px width still governs, so the art scales by its own aspect. Scoped
     by id because the max-width: 640px rule setting 40px comes later. */
  #screen-home .header-logo-img,
  #screen-about .header-logo-img {
    max-height: 60px;
  }
}
#screen-home .screen-header,
#screen-about .screen-header {
  background: transparent;
  border-bottom: none;
  /* sit the logo and buttons on the drapes, below the wooden rail */
  padding-top: max(26px, env(safe-area-inset-top));
}
/* Phones ride higher on the deeper cloth: the row was 28px down, now 14.
   Both paddings move, not just the top one — the header centres its row, so
   top padding alone won't place it, and shortening the header would pull the
   song grid up too. Top + bottom still sum to 46. Has to come after the rule
   above (same specificity). On a notched phone env() is bigger than 14px and
   wins, which is what you want. */
@media (orientation: portrait) and (max-width: 560px) {
  #screen-home .screen-header,
  #screen-about .screen-header {
    padding-top: max(16px, env(safe-area-inset-top));
    /* 16 + 60 (the taller logo row) + 14 = 90, the height the header already
       had, so the song grid below it doesn't move. */
    padding-bottom: 14px;
  }
}
#screen-home main {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Scroll container pulled up under the valance, with the pull given back as
     padding so nothing moves. Cards then clip behind the drapes and appear
     along the scallop curves instead of at a hard line in open sky. */
  margin-top: -140px;
  padding: 166px 22px 32px;
  position: relative;
  z-index: 1;
  background: transparent;
}

/* Ambient pieces, held in a centred sky-band box the same width as the song
   grid so no window size can push them onto the tree, curtains or ropes at
   the edges. Decorative: behind the content, no pointer events. */
.ambient {
  position: absolute;
  inset: 0;
  overflow: visible;
  z-index: 0;
  pointer-events: none;
}
.amb {
  position: absolute;
  filter: drop-shadow(0 3px 0 rgba(58, 38, 24, 0.16));
}
/* One placement has to clear all three plates, which leaves less room than
   you'd think: the tablet's open sky is just the 25-35% band, the landscape
   plate's tree sits left at 10-30%, the phone plate's tree sits right at
   55-90%. What's clear sky on all three, and clear of the mascot, bubble and
   grid, is about 25-33% down at 25-33% and 65-87% across. */
.amb-cloud-1 { left: 25%;  top: 25%; width: clamp(64px, 7vw, 110px); animation: ambDrift 34s ease-in-out infinite alternate; }
.amb-cloud-2 { right: 13%; top: 29%; width: clamp(48px, 5.4vw, 84px); opacity: 0.9; animation: ambDrift 26s ease-in-out infinite alternate-reverse; }
.amb-note-1  { left: 36%;  top: 30%; width: clamp(26px, 2.6vw, 42px); opacity: 0.85; animation: ambFloat 6.5s ease-in-out infinite alternate; }
.amb-note-2  { right: 31%; top: 31%; width: clamp(30px, 3vw, 48px); opacity: 0.85; animation: ambFloat 5.2s ease-in-out infinite alternate-reverse; }
@keyframes ambDrift {
  from { transform: translateX(0); }
  to   { transform: translateX(26px); }
}
@keyframes ambFloat {
  from { transform: translateY(0) rotate(-2deg); }
  to   { transform: translateY(-14px) rotate(3deg); }
}

/* mascot + intro label float in the backdrop sky */
.home-intro {
  /* The stage gets a beat before the choices arrive. Twenty cards at once is
     a lot to land on; open scenery under the mallet lets the place register
     first and the grid is one scroll away. Proportional so the pause feels
     the same on any screen. */
  margin-bottom: clamp(24px, 13vh, 150px);
  text-align: center;
  position: relative;
  z-index: 2;
  padding-top: clamp(94px, 16vmin, 190px);  /* clears the hanging valance */
}
.mascot-slot {
  display: inline-block;
  width: clamp(116px, 16vmin, 176px);
  height: clamp(116px, 16vmin, 176px);
  margin-bottom: 6px;
  animation: mascotBob 3.2s ease-in-out infinite;
  transform-origin: bottom center;
}
.mascot-slot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  filter: drop-shadow(0 4px 0 rgba(58,38,24,0.18));
}
@keyframes mascotBob {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-5px) rotate(1.5deg); }
}
/* Paper-sticker pill so the label reads over the busy backdrop.
   Own line, centered — width hugs the text but the block stays block so
   it doesn't share the mascot's line. */
.home-intro .label {
  display: block;
  width: fit-content;
  margin: 0 auto;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-soft);
  background: var(--paper-light);
  border: 2px solid var(--ink);
  border-radius: 999px;
  padding: 7px 16px;
  box-shadow: 0 2.5px 0 rgba(58, 38, 24, 0.22);
  transform: rotate(-1deg);
}
/* Speech-bubble variant: the mallet's own line, so sentence case and a
   tail pointing up at the mascot replace the uppercase sticker look. */
.home-intro .speech-bubble {
  text-transform: none;
  letter-spacing: 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
  max-width: min(300px, 82vw);
  border-radius: 12px;
  padding: 8px 14px;
  position: relative;
}
.home-intro .speech-bubble::before {
  content: '';
  position: absolute;
  top: -7px;
  left: 50%;
  width: 11px;
  height: 11px;
  margin-left: -6px;
  background: var(--paper-light);
  border-left: 2px solid var(--ink);
  border-top: 2px solid var(--ink);
  transform: rotate(45deg);
}

.song-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 16px;
  max-width: 1200px;
  margin: 0 auto;
  position: relative;
  z-index: 2;
}
/* Desktop: never more than three columns. A five-wide wall of cards
   reads as a menu board; three centered columns keep the choice count
   calm and let the stage show on both sides. */
@media (min-width: 900px) {
  .song-grid {
    grid-template-columns: repeat(3, 1fr);
    max-width: 820px;
    gap: 18px;
  }
}
.song-card {
  background:
    repeating-linear-gradient(90deg,
      rgba(58,38,24,0.05) 0px, rgba(58,38,24,0.05) 2px,
      transparent 2px, transparent 7px),
    linear-gradient(180deg, var(--wood-light) 0%, var(--wood) 55%, var(--wood-dark) 100%);
  border: 2.5px solid var(--ink);
  border-radius: 16px 15px 17px 14px; /* hand-cut feel */
  /* Top padding clears the corner pegs. At 20px the title plate started 2.5px
     under them and overlapped them sideways, so the pegs read as sitting on
     the sign instead of holding the board it hangs from. */
  padding: 24px 16px 16px;
  cursor: pointer;
  box-shadow: var(--sh-2);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 13px;
  min-height: 104px;
  font-family: inherit;
  text-align: left;
  transition: transform 0.1s ease, box-shadow 0.1s ease;
  position: relative;
  overflow: hidden;
  animation: cardDrop 0.4s cubic-bezier(0.16, 1, 0.3, 1) backwards;
}
/* corner pegs / screws, like a real wooden sign — set into the board's own
   margin, clear of the title plate below them */
.song-card::before, .song-card::after {
  content: '';
  position: absolute;
  top: 7px;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--wood-dark);
  border: 2px solid var(--ink);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.25);
  pointer-events: none;
}
.song-card::before { left: 8px; }
.song-card::after  { right: 8px; }
@keyframes cardDrop {
  0%   { opacity: 0; transform: translateY(10px) rotate(-1deg); }
  100% { opacity: 1; transform: translateY(0) rotate(0deg); }
}
.song-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 0 rgba(58, 38, 24, 0.3);
}
.song-card:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 rgba(58, 38, 24, 0.3);
}
/* title sits on an inset cream label plate so text never fights the grain */
.song-card-title {
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
  line-height: 1.25;
  background: var(--paper-light);
  border: 2px solid var(--ink);
  border-radius: 8px;
  padding: 7px 11px;
  box-shadow: 0 2px 0 rgba(58,38,24,0.18);
  align-self: stretch;
}
/* Difficulty: 1–3 little mallets, centered under the title (the key
   below the grid explains them) */
.song-card-meta {
  display: flex;
  gap: 3px;
  align-items: center;
  justify-content: center;
  align-self: stretch;
  /* Pinned to the card bottom so the mallets line up across a grid row —
     following the title left them at different heights on cards whose
     titles wrap to a different number of lines. */
  margin-top: auto;
}
.song-card-meta .mallet { width: 19px; height: 19px; }
.song-card-meta .mallet svg { display: block; width: 100%; height: 100%; overflow: visible; }
.song-card-meta .mallet rect {
  fill: var(--wood-light);
  stroke: var(--ink);
  stroke-width: 1.6;
}
.song-card-meta .mallet .head { fill: var(--c3); }
.song-card-meta .mallet:not(.on) rect {
  fill: transparent;
  stroke: rgba(58, 38, 24, 0.35);
}

/* Legend pill under the grid. max-width guards the page from ever growing
   sideways (the old fixed-width legend overflowed 375px phones). */
.difficulty-key {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 18px;
  width: fit-content;
  max-width: calc(100% - 8px);
  margin: 26px auto 0;
  position: relative;
  z-index: 2;
  background: var(--paper-light);
  border: 2px solid var(--ink);
  border-radius: 999px;
  padding: 9px 18px;
  box-shadow: 0 2.5px 0 rgba(58, 38, 24, 0.22);
  transform: rotate(0.6deg);
}
.difficulty-key .key-item {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.difficulty-key .mallet { width: 15px; height: 15px; }
.difficulty-key .song-card-meta { gap: 2px; margin-top: 0; }  /* legend is a row, not a card */

/* ---- About screen ----
   A paper card on the stage backdrop, same world as everything else. */
#screen-about {
  background:
    url('../assets/background-landscape.webp') center bottom / cover no-repeat,
    radial-gradient(120% 90% at 50% -20%, #6fb8dd 0%, #9fd0e8 32%, var(--paper) 70%);
}
@media (orientation: portrait) {
  #screen-about {
    background:
      url('../assets/background-portrait.webp') center / cover no-repeat,
      radial-gradient(120% 90% at 50% -20%, #6fb8dd 0%, #9fd0e8 32%, var(--paper) 70%);
  }
}
/* With the valance behind it, "About" is ink on red at 2.16:1 — it used to
   sit on a cream bar. Put it on a paper plate like the song title: 10.9:1,
   and it reads as a sign hung on the drapes rather than text lost in them. */
#screen-about .logo span {
  background: var(--paper);
  border: 2.5px solid var(--ink);
  border-radius: 11px 10px 12px 9px;
  box-shadow: 0 2px 0 rgba(58, 38, 24, 0.18);
  padding: 3px 16px;
  font-size: 17px;
}

#screen-about main {
  flex: 1;
  overflow-y: auto;
  padding: 40px 20px;
  display: flex;
  justify-content: center;
  /* `safe` keeps a card taller than the viewport from having its top
     clipped; browsers without it simply keep the flex-start above. */
  align-items: flex-start;
  align-items: safe center;
}
.about-card {
  max-width: 520px;
  width: 100%;
  background: var(--paper-light);
  border: 2.5px solid var(--ink);
  border-radius: 16px 15px 17px 14px; /* hand-cut feel, like the song cards */
  padding: 24px 26px 18px;
  box-shadow: var(--sh-2);
  transform: rotate(-0.4deg);
}
.about-card .logo-big {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 10px;
}
.about-logo-img {
  display: block;
  width: min(300px, 70vw);
  height: auto;
  filter: drop-shadow(0 5px 0 rgba(58,38,24,0.14));
}
/* With the section headings gone, the lede needs its own breathing room */
.about-lede { text-align: center; margin-bottom: 16px; }
.about-card h2 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-soft);
  margin: 20px 0 6px;
}
.about-card p {
  font-size: 15px;
  line-height: 1.65;
  color: var(--ink-soft);
  font-weight: 400;
}
/* "Want it as an app? here, do this" — the instruction on the left, a shot of
   the finished thing on the right, and a sketched arrow tying the two
   together. Reads as a little diagram rather than a paragraph of setup. */
.install {
  display: flex;
  align-items: center;
  gap: 8px;
}
.install-copy {
  flex: 1;
  min-width: 0;
}
/* Tucked into the row's own gap rather than given a column of its own —
   at 390px every pixel it takes comes straight out of the copy's width. */
.install-arrow {
  flex: none;
  width: 34px;
  height: auto;
  margin: 0 -2px;
  color: rgba(58, 38, 24, 0.45);
}
/* Sized for a tight crop of the icon on a home screen; height auto so a
   crop that includes the label underneath works without a code change. */
.install-shot {
  flex: none;
  display: block;
  width: 76px;
  height: auto;
  border: 2.5px solid var(--ink);
  border-radius: 18px;
  box-shadow: var(--sh-2);
}
/* Narrow phones: let the arrow turn the corner and stack the diagram. */
@media (max-width: 380px) {
  .install { flex-wrap: wrap; justify-content: center; }
  .install-copy { flex-basis: 100%; }
  .install-arrow { align-self: center; margin-top: 0; width: 36px; }
}

.about-card .credit {
  margin-top: 26px;
  padding-top: 16px;
  border-top: 2px dashed rgba(58, 38, 24, 0.18);
  font-size: 12px;
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 600;
}
.about-card .credit a {
  /* --c5 is 2.38:1 on cream; this deeper blue is 5.09:1 (AA needs 4.5) */
  color: #1c6a8e;
  text-decoration: none;
  border-bottom: 2px solid rgba(28, 106, 142, 0.45);
}
.about-card .credit a:hover { border-bottom-color: #1c6a8e; }

/* ---- Player screen ---- */
/* The floating controls have to reappear exactly where the header controls
   were, so both read their offsets from these. Change them in one place. */
#screen-player {
  --hdr-pad-top: max(10px, env(safe-area-inset-top));
  --hdr-pad-x: 16px;
}
#screen-player .player-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 10px 16px;
  padding-top: var(--hdr-pad-top);
  background: var(--paper-light);
  border-bottom: 2.5px solid var(--ink);
  position: relative;
  z-index: 10;
  flex-shrink: 0;
  min-height: 68px;
}
/* The title is a pill floating over the top of the stage, not a header item.
   In the header it was cramped (8px of air above it on a phone, wedged between
   56px buttons and the bottom rule) and long titles pushed the header onto two
   rows. Out here it hugs its text and centres on the stage instead of on
   whatever the buttons leave over. Absolutely positioned, so it costs the play
   area nothing. */
.player-title {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 8;                       /* over the paused scrim, like #pausedHint */
  /* max-content, or shrink-to-fit picks a narrower box than the text needs
     and wraps titles that had room to sit on one line. */
  width: max-content;
  max-width: calc(100% - 24px);
  text-align: center;
  font-size: clamp(13.5px, 3.6vw, 17px);
  font-weight: 600;
  line-height: 1.15;
  color: var(--ink);
  background: var(--paper);
  border: 2.5px solid var(--ink);
  border-radius: 11px 10px 12px 9px;   /* hand-cut, like the song cards */
  box-shadow: 0 2px 0 rgba(58, 38, 24, 0.18);
  padding: 5px 12px;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: break-word;
  transition: opacity 0.3s ease, visibility 0s;
}
/* Goes down with the house lights. It no longer rides the header, so nothing
   else would take it off screen once the song starts — and the floating
   controls appear right where it sits. */
#screen-player.performing .player-title {
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0s 0.3s;
}
.player-header .header-controls {
  display: flex;
  gap: 10px;
  align-items: center;
}

/* Play button — hero */
#playBtn {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  padding: 0;
  /* Green reads "go" and matches Play Again on the end card, which does the
     same thing. Ink icon, not cream: cream on this green is 2.1:1, under the
     3:1 needed for graphics, where ink is 5.79:1. */
  background: var(--c4);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
}
#playBtn:hover { background: #62a942; }
/* Pausing is a different action, so it keeps the blue and the cream mark. */
#playBtn.playing { background: var(--c5); color: var(--paper-light); }
#playBtn svg { width: 22px; height: 22px; fill: currentColor; }
#playBtn.playing svg { filter: drop-shadow(0 1.5px 0 rgba(0,0,0,0.2)); }

/* Game area — the stage backdrop shows through a translucent paper wash.
   The wash keeps falling notes legible over the busy edges (curtains,
   tree); lower the alpha for more theater, raise it for more calm. */
#game {
  flex: 1;
  position: relative;
  overflow: hidden;
  background: var(--paper);
  display: flex;
  border-bottom: 2.5px solid var(--ink);
}
/* Backdrop on a pseudo-layer so it can be desaturated. At full colour the
   art was luminance-identical to the notes in 6 of 8 lanes; muted, the falling
   notes are the only saturated things on stage. Before the lanes in the tree,
   so they paint over it. */
#game::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(rgba(237, 224, 192, 0.62), rgba(237, 224, 192, 0.62)),
    url('../assets/background-landscape.webp') center / cover no-repeat;
  filter: saturate(0.45);
  pointer-events: none;
}
@media (orientation: portrait) {
  #game::before {
    background:
      linear-gradient(rgba(237, 224, 192, 0.62), rgba(237, 224, 192, 0.62)),
      url('../assets/background-portrait.webp') center / cover no-repeat;
  }
}
.lane {
  flex: 1;
  position: relative;
  border-right: 1px dashed rgba(58, 38, 24, 0.18);
  touch-action: manipulation;
  cursor: pointer;
}
.lane:last-child { border-right: none; }

.lane-label {
  position: absolute;
  bottom: 14px;
  left: 50%;
  transform: translateX(-50%);
  width: 30px;
  height: 30px;
  padding: 0;
  font-family: inherit;
  background: var(--paper-light);
  border: 2.5px solid var(--ink);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 14px;
  color: var(--ink);
  box-shadow: 0 2.5px 0 rgba(58, 38, 24, 0.25);
  z-index: 4;
  cursor: pointer;
}
/* Inflate the visible 30px circle to a ≥44px touch/click target */
.lane-label::after {
  content: '';
  position: absolute;
  inset: -10px;
  border-radius: 50%;
}
/* Keep the centering transform through hover/active (the global button
   styles must not shift a lane button off its lane) */
.lane-label:hover { transform: translateX(-50%); }
.lane-label:active { transform: translateX(-50%) scale(0.92); box-shadow: 0 1px 0 rgba(58, 38, 24, 0.25); }
.lane-label:focus-visible { outline: 2px dashed var(--ink); outline-offset: 3px; }

#hitline {
  position: absolute;
  /* HIT_LINE_PCT in constants.js — keep the two in step */
  top: 70%;
  left: -6px;
  right: -6px;
  height: 20px;
  background: var(--ink);
  background-image: repeating-linear-gradient(
    135deg,
    rgba(255,255,255,0.06) 0,
    rgba(255,255,255,0.06) 5px,
    transparent 5px,
    transparent 10px
  );
  border-top: 2px solid #1f1108;
  border-bottom: 2px solid #1f1108;
  z-index: 6;
  pointer-events: none;
  box-shadow: 0 4px 0 rgba(58, 38, 24, 0.2);
  transform: rotate(-0.4deg);
}
#hitline.pulse {
  animation: hitlinePulse 220ms cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes hitlinePulse {
  0%, 100% { transform: rotate(-0.4deg) scaleY(1);   filter: brightness(1); }
  25%      { transform: rotate(-0.4deg) scaleY(1.5); filter: brightness(1.6); }
}

.note {
  position: absolute;
  /* Lanes run 150-200px on a desktop stage; a note filling that would be a
     wide bar. Capped and centred in its lane instead — on a phone the lane
     is narrower than the cap, so nothing changes there. */
  left: 0;
  right: 0;
  width: min(calc(100% - 14px), 104px);
  margin-inline: auto;
  height: 46px;
  border-radius: 10px 11px 9px 12px; /* slightly irregular = hand-cut paper */
  /* 4px: the outline is what carries note-vs-backdrop contrast, and at
     propped-viewing distance 2.5px sits right at the acuity floor */
  border: 4px solid var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  color: var(--paper-light);
  font-size: 19px;
  text-shadow: none;
  will-change: transform, opacity;
  z-index: 3;
  overflow: hidden;
  box-shadow:
    0 4px 0 rgba(58, 38, 24, 0.4),
    inset 0 1.5px 0 rgba(255, 255, 255, 0.32),
    inset 0 -2px 0 rgba(0, 0, 0, 0.14);
}
/* Short screens (landscape phones): smaller notes so the runway can hold
   a full bar — see PX_PER_BEAT_MIN, which must shrink with this. */
@media (max-height: 480px) {
  .note { height: 34px; font-size: 16px; }
}
.note[data-text-tone="dark"] { text-shadow: 0 1.5px 0 rgba(255,255,255,0.24); }
.note[data-text-tone="light"] { text-shadow: 0 1.5px 0 rgba(0,0,0,0.28); }
/* Paper grain baked onto each note via a tiny inline noise texture */
.note::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0.18;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='g'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%' height='100%' filter='url(%23g)' opacity='0.5'/></svg>");
}
.note::after {
  content: '';
  position: absolute;
  inset: 0;
  background: #ffffff;
  opacity: 0;
  pointer-events: none;
  border-radius: inherit;
}
.note.hit::after {
  animation: noteFlash 260ms cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes noteFlash {
  0%   { opacity: 0; }
  8%   { opacity: 0.95; }
  35%  { opacity: 0.4; }
  100% { opacity: 0; }
}

/* Burst at the hit line when a note lands — brighter and bigger than the
   old version so the hit moment reads clearly from across the room */
.spark {
  position: absolute;
  top: 70%;              /* rides the hit line — see #hitline */
  left: 50%;
  width: 34px;
  height: 10px;
  pointer-events: none;
  z-index: 2;
  opacity: 0;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: currentColor;
}
.spark.active {
  animation: sparkPulse 500ms cubic-bezier(0.1, 0.8, 0.3, 1);
}
@keyframes sparkPulse {
  0%   { opacity: 0;    box-shadow: 0 0 0 0 currentColor;        transform: translate(-50%, -50%) scale(0.4); }
  15%  { opacity: 1;    box-shadow: 0 0 52px 18px currentColor;  transform: translate(-50%, -50%) scale(1.25); }
  100% { opacity: 0;    box-shadow: 0 0 110px 34px currentColor; transform: translate(-50%, -50%) scale(2.4); }
}

.phrase-marker {
  position: absolute;
  left: 14px;
  right: 14px;
  height: 0;
  border-top: 2px dashed rgba(58, 38, 24, 0.22);
  z-index: 1;
  pointer-events: none;
  will-change: transform;
}

/* Paused: the set dims so the mallet is the only thing awake, otherwise the
   frozen notes mid-air compete with it. Above the hit line (6), below the
   sleeping mallet, and never takes taps so the bars stay playable. */
#pausedScrim {
  position: absolute;
  inset: 0;
  z-index: 7;
  /* house lights down, not washed out — darkening keeps the stage's own
     colour underneath instead of bleaching it toward the paper */
  background: rgba(46, 30, 18, 0.5);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s ease;
}
#pausedScrim.visible { opacity: 1; }

/* Paused: the mallet has a nap. A pre-reader can't use the word, and the
   mascot says "we stopped, nothing is wrong" better than a label does. */
#pausedHint {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
  z-index: 9;
  opacity: 0;
  transition: opacity 0.2s;
}
#pausedHint img {
  display: block;
  width: 96px;
  height: auto;
  filter: drop-shadow(0 4px 0 rgba(58, 38, 24, 0.18));
  animation: pausedBreathe 3.4s ease-in-out infinite;
}
/* Cream on the dimmed set — an ink pill would sink into the scrim */
#pausedHint span {
  background: var(--paper-light);
  color: var(--ink);
  padding: 8px 18px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transform: rotate(-1deg);
  box-shadow: 0 3px 0 rgba(58, 38, 24, 0.3);
}
@keyframes pausedBreathe {
  0%, 100% { transform: translateY(0) scale(1); }
  50%      { transform: translateY(-3px) scale(1.02); }
}
#pausedHint.visible { opacity: 1; }

/* Countdown — sits at ~30% from top, behind notes */
#countdown {
  position: absolute;
  top: 28%;
  left: 50%;
  transform: translate(-50%, -50%);
  opacity: 0;
  pointer-events: none;
  z-index: 1;
  line-height: 0;
  will-change: opacity, transform;
}
#countdown img {
  display: block;
  height: 180px;
  width: auto;
  filter: drop-shadow(0 4px 0 rgba(58, 38, 24, 0.18));
}
#countdown.tick {
  /* Kept at 640ms: the fastest song at 100% tempo has a ~500ms beat, and a
     longer window would leave one numeral still on screen as the next lands. */
  animation: countdownTick 640ms cubic-bezier(0.4, 0, 0.2, 1);
}
/* A swell, not a flash. The old curve hit full size and peak opacity in 108ms
   on a hard ease-out then blew out to 1.2x, which over a 180px numeral is a
   punch, four times in a row. Now it rises over ~200ms, holds, and fades.
   Scale travel 0.9 -> 1.06 instead of 0.7 -> 1.2, dimmer peak. */
@keyframes countdownTick {
  0%   { opacity: 0;    transform: translate(-50%, -50%) scale(0.90); }
  32%  { opacity: 0.42; transform: translate(-50%, -50%) scale(1); }
  60%  { opacity: 0.38; transform: translate(-50%, -50%) scale(1.02); }
  100% { opacity: 0;    transform: translate(-50%, -50%) scale(1.06); }
}

/* Song-end card */
.song-end {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 20;   /* over the header and tempo bar, both z-index 10 */
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.32s ease;
  /* Darkens rather than washes out — same house-lights-down treatment as
     #pausedScrim, so the two overlays read as one idea. */
  background: rgba(46, 30, 18, 0.0);
}
.song-end.visible {
  opacity: 1;
  pointer-events: auto;
  background: rgba(46, 30, 18, 0.5);
}
.song-end-card {
  background: var(--paper-light);
  border: 2.5px solid var(--ink);
  border-radius: 18px;
  box-shadow: var(--sh-3);
  padding: 28px 36px 26px;
  max-width: 380px;
  width: calc(100% - 48px);
  text-align: center;
  transform: translateY(8px) scale(0.97);
  transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1);
}
.song-end.visible .song-end-card {
  transform: translateY(0) scale(1);
}
.song-end-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 12px;
  color: var(--ink-soft);
  font-weight: 600;
  margin-bottom: 6px;
}
.song-end-mascot {
  display: flex;
  justify-content: center;
  margin-bottom: 8px;
}
.song-end-mascot img {
  display: block;
  width: 86px;
  height: 86px;
  object-fit: contain;
  pointer-events: none;
  filter: drop-shadow(0 4px 0 rgba(58,38,24,0.15));
}
.song-end.visible .song-end-mascot {
  animation: mascotPop 520ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes mascotPop {
  0%   { transform: scale(0.4) translateY(10px); opacity: 0; }
  60%  { transform: scale(1.08) translateY(0); opacity: 1; }
  100% { transform: scale(1) translateY(0); opacity: 1; }
}
.song-end-title {
  font-size: 22px;
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 22px;
  line-height: 1.2;
}
.song-end-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}
.song-end-btn {
  padding: 11px 18px;
  font-size: 14px;
  font-weight: 600;
  border-radius: 12px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.song-end-btn.primary {
  background: var(--c4);
  color: var(--ink);
}
.song-end-btn.primary:hover { background: #62a942; }

/* Player controls */
#controls {
  background: var(--paper-light);
  padding: 14px 18px;
  padding-bottom: max(14px, env(safe-area-inset-bottom));
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 22px;
  flex-shrink: 0;
  min-height: 80px;
  position: relative;
  z-index: 10;
}
/* House lights: the header slides away while the song plays (see
   setPerforming in player.js — it sets the matching negative margin).
   The tempo bar stays: it's the one live parent control. */
#screen-player .player-header {
  transition: margin 0.4s ease, opacity 0.3s ease, visibility 0s;
}
#screen-player.performing .player-header {
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: margin 0.4s ease, opacity 0.3s ease, visibility 0s 0.4s;
}
.control-group {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.tempo-group { flex: 1; max-width: 420px; }
/* The wrapper exists so the notch lands exactly where the thumb centres. The
   thumb travels from half its width to width-minus-half, not 0..100%, so a
   plain 66.7% background stop sits a few px off — noticeable on the one
   control that magnets to that point. */
.tempo-track {
  position: relative;
  display: flex;
  flex: 1;
  min-width: 0;
}
.tempo-track #tempo { flex: 1; width: auto; min-width: 0; }
.tempo-track::after {
  content: '';
  position: absolute;
  top: 50%;
  left: calc(11px + (100% - 22px) * 0.666667);   /* 11px = half the thumb */
  width: 2.5px;
  height: 8px;
  margin-left: -1.25px;
  transform: translateY(-50%);
  background: var(--ink);
  border-radius: 2px;
  opacity: 0.5;
  pointer-events: none;
}
/* Paper-cut bookends. Sized by eye rather than to a shared height: the
   turtle is squat and solid, the rabbit is mostly ear, so matching their
   boxes would leave the rabbit looking the lighter of the two. */
.tempo-end {
  display: block;
  flex-shrink: 0;
  width: auto;
  filter: drop-shadow(0 1.5px 0 rgba(58, 38, 24, 0.22));
}
.tempo-slow { height: 26px; }
.tempo-fast { height: 32px; }
.control-label {
  text-transform: uppercase;
  letter-spacing: 0.09em;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-soft);
}

#tempo:focus-visible { outline: 2px dashed var(--ink); outline-offset: 4px; }
#tempo {
  -webkit-appearance: none;
  appearance: none;
  width: 140px;
  height: 10px;
  background: var(--paper-deep);
  border: 2.5px solid var(--ink);
  border-radius: 20px;
  outline: none;
  box-shadow: inset 0 1.5px 0 rgba(58, 38, 24, 0.15);
}
#tempo::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 22px;
  height: 22px;
  background: var(--c3);
  border: 2.5px solid var(--ink);
  border-radius: 50%;
  cursor: pointer;
  box-shadow: 0 2.5px 0 rgba(58, 38, 24, 0.32);
  transition: transform 0.12s ease;
}
/* the thumb swells under the finger — the only tactile cue a slider gets */
#tempo:active::-webkit-slider-thumb { transform: scale(1.14); }
#tempo:active::-moz-range-thumb { transform: scale(1.14); }
#tempo::-moz-range-thumb {
  width: 22px;
  height: 22px;
  background: var(--c3);
  border: 2.5px solid var(--ink);
  border-radius: 50%;
  cursor: pointer;
  box-shadow: 0 2.5px 0 rgba(58, 38, 24, 0.32);
}

/* Floating pause — the one control that earns a spot during the show */
.floating-pause {
  position: absolute;
  /* centred on where #playBtn was — past the cog and one gap, then half the
     4px width difference between that 56px button and this 52px one */
  top: calc(var(--hdr-pad-top) + 2px);
  right: calc(var(--hdr-pad-x) + 56px);
  width: 52px;
  height: 52px;
  padding: 0;
  border-radius: 50%;
  background: var(--c5);
  color: var(--paper-light);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 8;
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.3s ease 0.25s, visibility 0s 0.55s;
}
.floating-pause svg { width: 20px; height: 20px; fill: currentColor; }
/* Leaving shouldn't cost two taps. Sits opposite the pause cluster, where
   the header's back button lives, so it's the same reach either way — and
   far enough from pause that a grabbing hand won't hit it by accident. */
.floating-back {
  position: absolute;
  /* dead on where #backToHome was */
  top: calc(var(--hdr-pad-top) + 6px);
  left: var(--hdr-pad-x);
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 50%;
  background: var(--paper-light);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 8;
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.3s ease 0.25s, visibility 0s 0.55s;
}/* Restart rides along, secondary: smaller, paper-colored, left of pause */
.floating-restart {
  position: absolute;
  /* dead on where #resetBtn was: past the cog (44), the play button (56)
     and the two 10px gaps in the header's control row */
  top: calc(var(--hdr-pad-top) + 6px);
  right: calc(var(--hdr-pad-x) + 120px);
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 50%;
  background: var(--paper-light);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 8;
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.3s ease 0.25s, visibility 0s 0.55s;
}
/* visibility, not just opacity. An opacity-0 button keeps its tab stop and
   focus ring, so keyboard users hit three invisible controls and screen
   readers saw duplicate Back/Restart. Same fix as the header; the delay lets
   the fade finish first. */
#screen-player.performing .floating-pause,
#screen-player.performing .floating-restart,
#screen-player.performing .floating-back {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transition: opacity 0.3s ease 0.25s, visibility 0s;
}

/* ---- Mini xylophone ---- */
.mini-xylo {
  padding: 8px 10px;
  background: var(--paper-light);
  border: 2.5px solid var(--ink);
  border-radius: 12px;
  box-shadow: var(--sh-2);
  display: flex;
  align-items: center;
  justify-content: center;
  height: 50px;
}
.mini-xylo:hover { transform: translateY(-1px); }
.mini-xylo:active { transform: translateY(3px); box-shadow: 0 1px 0 rgba(58, 38, 24, 0.28); }
.mini-xylo svg { display: block; width: 140px; height: 30px; overflow: visible; }
/* Colors doorway placement inside the settings drawer */
.drawer .mini-xylo { display: flex; margin-bottom: 4px; }

/* ---- Drawers (settings + palette) ---- */
/* Scrim behind an open drawer — dims the page and closes on tap */
#drawerScrim {
  position: absolute;
  inset: 0;
  background: rgba(58, 38, 24, 0.28);
  z-index: 150;
  opacity: 0;
  pointer-events: none;
}
html.booted #drawerScrim {
  transition: opacity 0.25s ease;
}
#drawerScrim.visible {
  opacity: 1;
  pointer-events: auto;
}

.drawer {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(400px, 92vw);
  background: var(--paper-light);
  border-left: 2.5px solid var(--ink);
  padding: 30px 28px 40px;
  z-index: 200;
  transform: translateX(105%);
  /* visibility is belt-and-suspenders: a closed drawer can never be
     seen (or block taps) even if the transform is interfered with;
     the delay lets the slide-out animation finish first */
  visibility: hidden;
  overflow-y: auto;
  box-shadow: -10px 0 28px rgba(58, 38, 24, 0.18);
}
html.booted .drawer {
  transition: transform 0.25s ease, visibility 0s linear 0.25s;
}
.drawer.open {
  transform: translateX(0);
  visibility: visible;
}
html.booted .drawer.open {
  transition: transform 0.25s ease;
}
.drawer h2 {
  font-size: 11px;
  margin-bottom: 18px;
  font-weight: 600;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.09em;
}
.drawer h2:not(:first-of-type) { margin-top: 40px; }
.drawer .close {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  font-weight: 600;
  line-height: 0;
}

/* Buttons whose artwork carries its own cut edge — the paper chrome would
   be a second border around a shape that already has one. */
.icon-btn.sticker,
.back-btn.sticker,
.floating-back.sticker,
.floating-restart.sticker {
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
}
.icon-btn.sticker img,
.back-btn.sticker img,
.floating-back.sticker img,
.floating-restart.sticker img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 2px 0 rgba(58, 38, 24, 0.28));
}
.icon-btn.sticker:active,
.back-btn.sticker:active,
.floating-back.sticker:active,
.floating-restart.sticker:active {
  transform: translateY(2px);
  box-shadow: none;
}
.drawer p {
  font-size: 13.5px;
  color: var(--ink-soft);
  line-height: 1.7;
  font-weight: 400;
  margin-top: 14px;
}

/* Drawer title — the word a cog-tapper expects, before any charm */
.drawer .drawer-title {
  font-size: 20px;
  font-weight: 700;
  color: var(--ink);
  text-transform: none;
  letter-spacing: normal;
  margin-bottom: 26px;
  padding-right: 50px; /* leave room for close button */
}

/* The palette is the instrument it's matching — eight bars in a wooden
   frame, longest (lowest note) at the left. Tap a bar to recolor it. */
.palette {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 4px;
  padding: 16px 12px;
  background:
    repeating-linear-gradient(90deg,
      rgba(58,38,24,0.05) 0px, rgba(58,38,24,0.05) 2px,
      transparent 2px, transparent 7px),
    linear-gradient(180deg, var(--wood-light) 0%, var(--wood) 55%, var(--wood-dark) 100%);
  border: 2.5px solid var(--ink);
  border-radius: 14px 13px 15px 12px;  /* hand-cut, like the song cards */
  box-shadow: var(--sh-2);
}
.palette-bar {
  position: relative;
  flex: 1;
  min-width: 0;
  cursor: pointer;
}
.palette-bar input[type="color"] {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 2.5px solid var(--ink);
  border-radius: 5px;
  background: none;
  cursor: pointer;
  box-shadow: 0 2.5px 0 rgba(58, 38, 24, 0.3);
}
.palette-bar input[type="color"]:focus-visible {
  outline: 2px dashed var(--ink);
  outline-offset: 3px;
}
.palette-bar input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.palette-bar input[type="color"]::-webkit-color-swatch { border: none; border-radius: 3px; }
.palette-bar input[type="color"]::-moz-color-swatch { border: none; border-radius: 3px; }
/* Number + note name ride on the bar; clicks pass through to the input */
.palette-face {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  pointer-events: none;
  font-weight: 700;
}
.palette-face b { font-size: 15px; line-height: 1; }
.palette-face i {
  font-size: 9px;
  font-style: normal;
  opacity: 0.72;
  letter-spacing: 0.04em;
}
.palette-reset {
  margin-top: 22px;
  width: 100%;
  padding: 12px;
  font-size: 13px;
  font-weight: 500;
}
/* The intro line sits above the instrument, not buried under it */
#paletteDrawer > p { margin: -14px 0 16px; }

/* Preroll segmented */
.segments {
  display: flex;
  border: 2.5px solid var(--ink);
  border-radius: 10px;
  overflow: hidden;
  background: var(--paper-light);
  box-shadow: var(--sh-2);
}
.segments button {
  flex: 1;
  border: none;
  border-right: 2px solid var(--ink);
  border-radius: 0;
  box-shadow: none;
  padding: 11px 12px;
  background: var(--paper-light);
  font-size: 13px;
  font-weight: 500;
}
.segments button:last-child { border-right: none; }
.segments button:hover { transform: none; }
.segments button:active { transform: none; box-shadow: none; }
.segments button.active {
  background: var(--ink);
  color: var(--paper-light);
  transform: none;
  box-shadow: inset 0 2px 0 rgba(0,0,0,0.25);
}


.settings-action {
  width: 100%;
  padding: 12px;
  font-size: 13px;
  font-weight: 500;
}

.settings-footer {
  margin-top: 48px;
  padding-top: 22px;
  border-top: 2px dashed rgba(58, 38, 24, 0.18);
}
.settings-footer .version {
  font-size: 11px;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 600;
}

/* ---- Responsive ---- */
@media (max-width: 640px) {
  .control-label { display: none; }
  #controls { gap: 16px; padding: 12px 14px; }
  .header-logo-img { width: 122px; max-height: 40px; }
  #screen-home main { padding: 160px 14px 24px; }
  .home-intro { padding-top: 94px; }
  .song-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
  .difficulty-key { gap: 10px; padding: 8px 12px; }
  .difficulty-key .key-item { font-size: 10px; letter-spacing: 0.05em; }
  .difficulty-key .mallet { width: 13px; height: 13px; }
}
@media (max-width: 480px) {
  #controls { gap: 10px; padding: 10px 10px; }
}
@media (max-height: 420px) {
  #screen-player { --hdr-pad-top: 6px; --hdr-pad-x: 14px; }
  #screen-player .player-header { min-height: 56px; padding: var(--hdr-pad-top) var(--hdr-pad-x); }
  #controls { min-height: 64px; padding: 10px 14px; }
  .lane-label { width: 26px; height: 26px; font-size: 13px; bottom: 8px; }
  .screen-header { min-height: 58px; padding: 8px 14px; }
  #screen-home main { padding: 156px 14px 20px; }
  .home-intro { margin-bottom: 12px; padding-top: 40px; }
  .mascot-slot { width: 72px; height: 72px; }
  /* The drapes used to be dropped here for a plain paper bar, which made the
     frame vanish at exactly the sizes where the stage is thinnest and read as
     a different app. Keep them, just shallower: 13vh is about 47px on a
     360px-tall screen. */
  .curtain-top { max-height: 13vh; }
  /* short screens crop the backdrop's sky away entirely when anchored
     to the bottom — show the sky/hills band instead so the mascot
     isn't floating over dirt */
  #screen-home { background-position: center 30%; }
  .intro-content { gap: 12px; padding: 16px; }
  #countdown { top: 32%; }
  #countdown img { height: 120px; }
  .song-end-card { padding: 22px 26px 20px; }
  .song-end-title { font-size: 18px; margin-bottom: 18px; }
}

/* Phones on their side are the one layout with no height to give the drop: the
   band is very wide and gets capped short, so stretching the tall art into it
   flattened the scallops to about half depth. This crop starts below the plain
   red at the top, so the shallow band is the art's own shape instead of a
   squeezed version of the tall one. The cap sits above the natural height at
   every phone width, so it never squashes again. */
@media (orientation: landscape) and (max-height: 500px) {
  .curtain-top {
    aspect-ratio: 2172 / 190;
    max-height: 20vh;
    background-image: url('../assets/curtain-top-landscape-short.webp');
  }
}

/* Safari's landscape chrome can squeeze the game area very short —
   keep the lane labels inside it */
@media (max-height: 300px) {
  .lane-label { width: 22px; height: 22px; font-size: 11px; bottom: 4px; }
  /* At this height the sky really is a strip. One cloud, nothing else. */
  .amb-note-1, .amb-note-2, .amb-cloud-2 { display: none; }
}

/* Portrait phones — beef up header and footer so the play area takes a sensible proportion */
@media (orientation: portrait) and (max-width: 760px) {
  #screen-player { --hdr-pad-top: max(18px, env(safe-area-inset-top)); }
  #screen-player .player-header {
    min-height: 92px;
    padding-bottom: 18px;
  }
  .screen-header {
    min-height: 90px;
    padding-top: max(20px, env(safe-area-inset-top));
    padding-bottom: 20px;
  }
  #controls {
    min-height: 130px;
    padding-top: 22px;
    padding-bottom: max(22px, calc(22px + env(safe-area-inset-bottom)));
  }
  #screen-intro {
    padding-top: max(0px, env(safe-area-inset-top));
    padding-bottom: max(0px, env(safe-area-inset-bottom));
  }
}

/* The title used to wrap onto a second header row here, because sharing one
   row with four controls left it ~76px of text width. It floats over the
   stage now, so the header is a plain single row again. */


/* ---- Reduced motion ----
   The always-on ambient loops stop. One-shot gameplay feedback (flashes,
   pulses) is over in a moment and stays. */
@media (prefers-reduced-motion: reduce) {
  .amb,
  .mascot-slot,
  .song-card,
  #pausedHint img {
    animation: none !important;
  }
  .screen {
    transform: none;
    transition: opacity 0.28s ease;
  }
  /* Chrome still retreats (it's a user-initiated state change) but snaps
     instead of sliding */
  #screen-player .player-header,
  .floating-pause,
  .floating-restart,
  .floating-back {
    transition: none;
  }
}

/* ---- Paper stock ----
   Last in the file on purpose: the component `background:` shorthands above
   would reset these.

   A real sheet has form — light on the top edge, shadow at the bottom, and
   tooth that belongs to the sheet rather than to the screen. A flat fill under
   one global noise overlay is what reads as plastic. Opaque surfaces only; the
   home header stays transparent so the drapes show through. */
.drawer,
#controls,
#screen-player .player-header,
.about-card,
.song-end-card {
  background-image:
    linear-gradient(180deg,
      rgba(255, 253, 244, 0.6) 0%,
      rgba(255, 253, 244, 0) 26%,
      rgba(58, 38, 24, 0.03) 74%,
      rgba(58, 38, 24, 0.065) 100%),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='f'><feTurbulence type='fractalNoise' baseFrequency='1.1' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.20 0 0 0 0 0.14 0 0 0 0 0.06 0 0 0 0.20 0'/></filter><rect width='100%' height='100%' filter='url(%23f)'/></svg>");
  background-blend-mode: normal, multiply;
  background-size: auto, 160px 160px;
  background-repeat: no-repeat, repeat;
}

/* ---- Phone plate ----  (last, so it beats the portrait swaps above)

   The 2:3 plate suits tablets, but phones are near 0.45, so cover was cutting
   31-37% off its width — and the curtains live at the extreme edges, so they
   went first. This plate is cut at 840x1871 (0.449) and gets the worst phone
   down to 6%.

   Crossover is 0.55, the midpoint between the two plates. Below it this one
   wins; above it the 2:3 plate fits better (an iPad would lose 35% of its
   height to this one). */

/* Portrait tablets, on the 2:3 plate. Its open sky is two bands either side
   of the mascot: 16-40% and 62-86%, measured at 834x1194. The base placement
   is one set of numbers clearing all three plates, and here it put cloud-1 and
   note-1 6% apart in the left band while the right pair sat 18% apart —
   bunched on one side, spread on the other. Give each pair its own band and
   stagger the heights so they don't line up. Before the phone block so that
   one still wins at the crossover. */
@media (orientation: portrait) and (min-aspect-ratio: 11/20) {
  .amb-cloud-1 { left: 17%;  top: 24%; }
  .amb-note-1  { left: 32%;  top: 30%; }
  .amb-note-2  { right: 32%; top: 25%; }
  .amb-cloud-2 { right: 17%; top: 30%; }
}

@media (orientation: portrait) and (max-aspect-ratio: 11/20) {
  #screen-intro {
    background:
      linear-gradient(rgba(46, 30, 18, 0.72), rgba(46, 30, 18, 0.72)),
      url('../assets/background-phone.webp') center / cover no-repeat,
      var(--paper);
  }
  #screen-home,
  #screen-about {
    background:
      url('../assets/background-phone.webp') center / cover no-repeat,
      radial-gradient(120% 90% at 50% -20%, #6fb8dd 0%, #9fd0e8 32%, var(--paper) 70%);
  }
  #game::before {
    background:
      linear-gradient(rgba(237, 224, 192, 0.62), rgba(237, 224, 192, 0.62)),
      url('../assets/background-phone.webp') center / cover no-repeat;
  }

  /* A phone's sky band is crowded: the mascot spans 35-66% of the width and
     the bubble 18-82%, and this plate's tree is on the right, so the pockets
     are only 14-32% and 70-86%. Two pieces side by side in something that
     narrow nearly touch. Stack them instead, a note above each cloud — ~76px
     apart vertically, which no amount of horizontal drift can close. */
  .amb-cloud-1 { left: 16%;  top: 31%; width: 46px; }
  .amb-note-1  { left: 21%;  top: 22%; width: 22px; }
  .amb-cloud-2 { right: 20%; top: 30%; width: 40px; }
  .amb-note-2  { right: 20%; top: 22%; width: 24px; }
  /* 26px of drift is 7% of a phone's width — enough to walk a cloud into
     the tree at the end of its travel. */
  .amb-cloud-1, .amb-cloud-2 { animation-name: ambDriftShort; }
}
@keyframes ambDriftShort {
  from { transform: translateX(0); }
  to   { transform: translateX(10px); }
}

/* Phone landscape, where the stage isn't capped (see tokens.css). Full bleed
   makes the 3:2 plate crop top and bottom rather than fit, so the tree's canopy
   sits higher and the sun and right-hand cloud take the top corners. What's
   left is a band across the middle, either side of the mascot. Sampled against
   the plate across each piece's whole animation, the only clear pockets are
   x 33-42% (one piece fits) and x 55-75%, so it's one on the left and three
   spread and staggered on the right. They used to be hidden here entirely,
   from back when this layout got a much shorter sky than it does now. */
@media (orientation: landscape) and (max-height: 599.98px) {
  .amb-cloud-1 { left: 33%;    top: 32%; }
  .amb-note-1  { left: 57%;    top: 26%; }
  .amb-cloud-2 { right: 32.6%; top: 38%; }
  .amb-note-2  { right: 24.7%; top: 41%; }
}
