/* Onest (OFL, Google Fonts). Files and license: fonts/. */
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(fonts/onest-latin-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Onest";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url(fonts/onest-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Page colors and type. src/app.js reads --color-black and --color-off-white for the light and dark backgrounds. */
:root {
  --color-black: #0a0a0a;
  --color-off-white: #f2f2f0;
  --font-base: "Helvetica Neue", Arial, system-ui, sans-serif;
  --text-body: clamp(17px, 1.25vw, 18px);
  --text-meta: 13px;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* All page text is Onest, tracking −4%. */
html {
  background: var(--color-off-white);
  color: var(--color-black);
  font-family: "Onest", var(--font-base);
  font-size: var(--text-body);
  letter-spacing: -0.04em;
}
body {
  margin: 0;
  padding: 20px 24px;
}
/* The panel sits between the stage and the page edge with the same 32 px on both sides of it, which shows when the
   stage goes dark out to the edges. */
@media (min-width: 761px) {
  body {
    padding-right: 32px;
  }
}
.bottom a {
  color: inherit;
  text-decoration: none;
}

.glass {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 280px;
  /* The stage spans two rows, next to the panel and the footer, so it keeps the icon centered on screen all the way down. */
  grid-template-areas:
    "stage intro"
    "stage controls"
    "stage foot";
  /* On a screen taller than the panel, the spare height goes under the footer, not between the title and the panel. */
  grid-template-rows: auto auto 1fr;
  gap: 24px 32px;
  align-items: start;
}
/* Title above the panel, in the right column. The title, the panel and the footer share one left edge. */
.intro {
  grid-area: intro;
  padding: 56px 0 0;
}
h1 {
  font-size: 40px;
  font-weight: 500;
  line-height: 0.95;
  letter-spacing: -0.065em;
  margin: 0 0 12px;
}
.intro p {
  margin: 0;
  font-size: var(--text-body);
}

/* The stage is pinned: while you adjust settings, the icon is always in view. */
/* The stage is full viewport height and sticks to the top, so the icon always sits in the middle of the screen. */
.stage-wrap {
  grid-area: stage;
  align-self: stretch;
  position: relative;
  isolation: isolate;
}
/* With a dark background the whole stage goes dark, out to the page edges (past the body's 20 / 24 px padding): the glass
   hangs in the dark instead of sitting on a black card. Trying a dark look on with the mouse previews it. The layer's
   opacity is --dark, which app.js eases in the same frames as the glass's own background, so the two never part.
   The panel stays light. */
.stage-wrap::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -20px 0 -20px -24px;
  background: var(--color-black);
  opacity: var(--dark, 0);
  pointer-events: none;
}
[data-tone="dark"] .presets span {
  color: color-mix(in srgb, var(--color-off-white) 62%, transparent);
}
[data-tone="dark"] .presets input:checked + span,
[data-tone="dark"] .presets .slot.is-on > span {
  color: var(--ink);
}
[data-tone="dark"] .slot:not(.is-filled) .well {
  background: rgb(255 255 255 / 0.07);
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.5), inset 0 0 0 0.5px rgb(255 255 255 / 0.08);
  color: color-mix(in srgb, var(--color-off-white) 80%, transparent);
}
[data-tone="dark"] .looks :focus-visible,
[data-tone="dark"] .presets .slot:focus-visible,
[data-tone="dark"] .presets input:focus-visible + span {
  outline-color: var(--color-off-white);
}
.stage-sticky {
  position: sticky;
  top: 20px;
  height: calc(100vh - 40px);
  height: calc(100svh - 40px);
  display: grid;
  place-items: center;
  align-content: center;
  row-gap: 14px;
  width: 100%;
}
/* The glass sizes to the screen, minus the row of presets under it, up to 760 px on a large one. */
.stage {
  position: relative;
  width: min(100%, 760px, calc(100svh - 190px));
  aspect-ratio: 1;
}
/* The keys under the glass share one geometry: --pad of room around the glass on every side of a key, and corners
   that follow the glass: a key's radius is the glass's own plus --pad, so the two curves run side by side. */
.looks {
  --pad: 8px;
  --key: 72px;
  --thumb: calc(var(--key) - 2 * var(--pad));
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
}
.looks .presets {
  flex: none;
}
.caption {
  text-align: center;
  font-size: 12px;
}
/* A file is being dragged over the page: highlight the stage, it can be dropped anywhere. */
.is-over .stage {
  border-radius: 24px;
  outline: 2px dashed var(--ink);
  outline-offset: -10px;
}
.stage canvas {
  display: block;
  cursor: pointer; /* the glass is played */
  touch-action: none; /* the glass is played: a finger held or drawn on it plays, it doesn't scroll the page */
  width: 100%;
  height: 100%;
  border-radius: 24px;
  /* The checkerboard shows only in no-background mode, to show the PNG is transparent. */
  background: repeating-conic-gradient(var(--color-off-white) 0 25%, color-mix(in srgb, var(--color-black) 6%, var(--color-off-white)) 0 50%) 0 0 / 24px 24px;
  /* The whole glass leans up to 6° toward the cursor, 9° with the phone (app.js sets the lean, -1 to 1, to 1.5 with the phone). */
  transform: perspective(1100px) rotateY(calc(var(--lean-x, 0) * 6deg)) rotateX(calc(var(--lean-y, 0) * 6deg));
  /* The canvas edge feathers into the page over its outer 4% (the margin, the glass is in the middle 80%): the glass's
     shadow reaches the edge a shade darker than the page, which would show as a square seam on a dark stage. */
  -webkit-mask: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent), linear-gradient(transparent, #000 4%, #000 96%, transparent);
  -webkit-mask-composite: source-in;
  mask: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent), linear-gradient(transparent, #000 4%, #000 96%, transparent);
  mask-composite: intersect;
}
.unsupported {
  display: none;
}
.is-unsupported .unsupported {
  display: block;
  position: absolute;
  inset: 0;
  margin: 0;
  display: grid;
  place-items: center;
  text-align: center;
  padding: 24px;
}

/* The settings sit straight on the page, no card behind them: the glass is the only object with depth here.
   8 px between controls, 24 px and a hairline between parts. */
.controls {
  grid-area: controls;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0 0 20px;
  font-size: var(--text-meta);
}
.upload input,
.track-input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}
/* Secondary actions (Tilt the glass, and the moves after a download): a white pill, quieter than the keys. */
.soft {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 10px;
  white-space: nowrap;
  background: linear-gradient(180deg, #fff 0%, var(--panel) 100%);
  box-shadow: var(--raise);
  color: var(--ink);
}
.soft:hover {
  filter: brightness(0.98);
}
.soft:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
.look {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
/* After a download: another look, or a link to this one, under Share. Text and an icon with no raised key:
   they're offers, the keys above stay the things to press. */
.after {
  animation: share-in 320ms cubic-bezier(0.3, 1.3, 0.4, 1);
}
.after .soft {
  padding: 8px 10px;
  background: none;
  box-shadow: none;
  color: color-mix(in srgb, var(--ink) 75%, transparent);
  transition: background-color 120ms ease-out, color 120ms ease-out;
}
.after .soft:hover {
  filter: none;
  background: var(--tray);
  color: var(--ink);
}
.after[hidden] {
  display: none;
}
/* What this release added: one line under the title on the first visit, after a small ink dot. Monochrome, no accent color. */
.whatsnew[hidden] {
  display: none;
}
.intro .whatsnew {
  margin-top: 10px;
  font-size: var(--text-meta);
  opacity: 0.6;
  text-wrap: balance; /* two even lines, not a pair of words left on the second */
}
.whatsnew::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 7px;
  border-radius: 50%;
  background: var(--ink);
  vertical-align: 1px;
}
/* The die rolls a third of a turn on every tap. */
.soft svg {
  flex: none;
  transform: rotate(var(--turn, 0deg));
  transition: transform 480ms cubic-bezier(0.3, 1.4, 0.5, 1);
}
@media (prefers-reduced-motion: reduce) {
  .soft svg {
    transition: none;
  }
}
/* Where motion access has to be asked for (iPhone): the one button that asks, on the stage itself, under the glass. */
.tilt {
  position: absolute;
  left: 50%;
  bottom: 1%; /* in the margin under the icon, clear of the glass */
  width: auto;
  padding: 6px 12px;
  font-size: 12px;
  transform: translateX(-50%);
}
.tilt:active {
  transform: translateX(-50%) scale(0.985);
}
.tilt[hidden] {
  display: none;
}
/* Full screen: a small quiet key at the end of a heading row, out of the stage. In full screen its counterpart, the way
   back, sits in the screen's corner. */
.full {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  margin-left: 10px;
  padding: 0;
  border-radius: 9px;
  background: linear-gradient(180deg, #fff 0%, var(--panel) 100%);
  box-shadow: var(--raise);
  color: color-mix(in srgb, var(--ink) 70%, transparent);
}
.full:hover {
  color: var(--ink);
}
.full svg path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.full:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  opacity: 1;
}
/* At the end of the Sound heading. A phone shows the glass large already, and there the heading sits under the stage,
   so the key is for larger screens. */
.group .full {
  order: 2;
  margin-left: auto;
}
@media (max-width: 760px) {
  .group .full {
    display: none;
  }
}
/* The live bars sit right by the word, standing on its baseline: they are the sound itself. The key keeps its place at
   the end. */
.group .bars {
  order: 1;
  height: 10px;
  margin: 1px 0 0 7px;
}

.full.exit {
  display: none;
}
.is-full .full.exit {
  display: grid;
  position: fixed;
  z-index: 21;
  top: 16px;
  right: 16px;
  margin: 0;
  transition: opacity 200ms ease-out;
}
[data-tone="dark"].is-full .full.exit {
  background: color-mix(in srgb, #fff 12%, transparent);
  color: var(--color-off-white);
}
/* In full screen only the glass stays, centred and as large as the screen allows, on the colour of its background.
   After a moment without the mouse, the key and the cursor step aside. */
.is-full .intro,
.is-full .controls,
.is-full .bottom,
.is-full .looks {
  display: none;
}
/* While the browser takes the screen, everything but the glass fades out of the way. */
.is-going-full .intro,
.is-going-full .controls,
.is-going-full .bottom,
.is-going-full .looks {
  opacity: 0;
  transition: opacity 160ms ease-out;
}
.is-full .stage-wrap {
  position: fixed;
  z-index: 20;
  inset: 0;
  background: var(--color-off-white);
}
.is-full .stage-sticky {
  position: static;
  height: 100%;
}
.is-full .stage {
  width: min(92vw, 92svh);
}
/* Only with a mouse: a touch screen has nothing to move to bring them back. */
@media (hover: hover) {
  .is-full.is-resting .full.exit {
    opacity: 0;
  }
  .is-full.is-resting .stage canvas {
    cursor: none;
  }
}
/* Into full screen and back, the glass travels between its place and the middle of the screen and grows or shrinks,
   while the rest of the page fades (a view transition; without one, the change is plain). */
.stage canvas {
  view-transition-name: glass;
}
::view-transition-group(glass) {
  animation-duration: 560ms;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
}
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 320ms;
}
/* The logo row: Upload and the folded text field share one line and trade widths when the field opens.
   The columns are lengths, not fr, so the trade animates. */
.logo-in {
  display: grid;
  grid-template-columns: calc(100% - 58px) 52px;
  gap: 6px;
  transition: grid-template-columns 360ms cubic-bezier(0.3, 1.2, 0.4, 1);
}
.logo-in.is-typing {
  grid-template-columns: 40px calc(100% - 46px);
}
/* Typed text. Folded, the field is a raised Aa key like Upload; open, it's a recessed tray like the sliders,
   with the sample marks inside its right end while it's empty. Inside a tray, flat marks are the ones to tap. */
.type {
  position: relative;
  min-width: 0;
}
.type input {
  display: block;
  width: 100%;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(180deg, #fff 0%, var(--panel) 100%);
  box-shadow: var(--raise);
  color: var(--ink);
  font: inherit;
  font-weight: 500;
  letter-spacing: inherit;
  text-align: center;
  cursor: pointer;
}
.type input::placeholder {
  color: var(--ink);
  opacity: 1;
}
.is-typing .type input {
  padding: 0 16px 0 14px;
  background: var(--tray);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--color-black) 7%, transparent),
    inset 0 0 0 0.5px color-mix(in srgb, var(--color-black) 6%, transparent);
  text-align: left;
  cursor: text;
}
.is-typing .type input:placeholder-shown {
  padding-right: 96px;
}
.is-typing .type input::placeholder {
  font-weight: 400;
  opacity: 0.45;
}
/* Open, the field already says it has focus (a tray with a caret), so its ring is a quiet hairline, not the black one. */
.type input:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
.is-typing .type input:focus-visible {
  outline: 1.5px solid color-mix(in srgb, var(--ink) 60%, transparent); /* still 3:1 against the tray */
}
.samples {
  position: absolute;
  top: 4px;
  right: 3px;
  bottom: 4px;
  display: flex;
  opacity: 0;
  visibility: hidden;
  transition: opacity 120ms ease-out, visibility 0s 120ms;
}
.is-typing input:placeholder-shown + .samples {
  opacity: 1;
  visibility: visible;
  transition: opacity 200ms ease-out 160ms;
}
.samples button {
  width: 30px;
  padding: 0;
  background: none;
  box-shadow: none;
  color: var(--ink);
  font-size: 15px;
  line-height: 32px;
  opacity: 0.55;
}
.samples button:hover {
  opacity: 1;
  filter: none;
  background: linear-gradient(180deg, #fff 0%, var(--panel) 100%);
  box-shadow: var(--raise);
}
.samples button:focus-visible {
  opacity: 1;
  outline: 2px solid var(--ink);
  outline-offset: 1px;
}
/* Black and white, but not flat and sterile: soft black, rounded corners, 0.5px strokes. */
/* On body, not .glass: the footer uses these colors too. */
body {
  --ink: color-mix(in srgb, var(--color-black) 88%, var(--color-off-white));
  --hair: color-mix(in srgb, var(--color-black) 22%, transparent);
  --tray: color-mix(in srgb, var(--color-black) 5%, var(--color-off-white));
  --raised: color-mix(in srgb, white 70%, var(--color-off-white));
  --panel: color-mix(in srgb, white 40%, var(--color-off-white));
  --tick: color-mix(in srgb, var(--color-black) 6%, transparent);
  /* Raised white surface: thin edge, highlight on top, short soft shadow. */
  --raise: inset 0 1px 0 #fff, 0 0 0 0.5px color-mix(in srgb, var(--color-black) 10%, transparent),
    0 1px 2px color-mix(in srgb, var(--color-black) 6%, transparent),
    0 3px 8px -3px color-mix(in srgb, var(--color-black) 12%, transparent);
  /* Dark button: slightly lighter than black,
     a thin highlight on top, 0.5px along the edge. */
  --btn-dark: linear-gradient(180deg, #3a3a3a 0%, #2a2a2a 100%);
  --btn-edge: inset 0 0.5px 0 rgb(255 255 255 / 0.16), 0 0 0 0.5px rgb(0 0 0 / 0.5),
    0 1px 2px rgb(0 0 0 / 0.14);
}
.upload span,
button {
  display: block;
  width: 100%;
  padding: 12px 16px;
  font-weight: 500;
  border: 0;
  border-radius: 999px;
  background: var(--btn-dark);
  box-shadow: var(--btn-edge);
  color: var(--color-off-white);
  font-family: inherit;
  font-size: inherit;
  letter-spacing: inherit;
  text-align: center;
  cursor: pointer;
  transition: transform 120ms ease-out, filter 120ms ease-out;
}
.upload span:hover,
button:hover {
  filter: brightness(1.12);
}
.upload span:active,
button:active {
  transform: scale(0.985);
}
/* Upload is a white key, like Record video: the one black button on the page is Download PNG. */
.upload span {
  background: linear-gradient(180deg, #fff 0%, var(--panel) 100%);
  box-shadow: var(--raise);
  color: var(--ink);
}
.upload span:hover {
  filter: brightness(0.98);
}
.upload > span {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 40px;
  padding: 0 14px;
  overflow: hidden;
  white-space: nowrap;
}
.upload svg {
  flex: none;
}
/* While the text field is open, Upload folds to its arrow. The name only shrinks, so the key keeps it for screen readers. */
.upload i {
  max-width: 8em;
  font-style: normal;
  transition: max-width 360ms cubic-bezier(0.3, 1, 0.4, 1), opacity 160ms ease-out;
}
.is-typing .upload > span {
  gap: 0;
  padding: 0;
}
.is-typing .upload i {
  max-width: 0;
  opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
  .logo-in,
  .upload i {
    transition: none;
  }
}
.upload:focus-within span {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
.hint,
.status {
  margin: 0;
  opacity: 0.6;
  text-wrap: pretty;
}
.status {
  text-align: center;
}
.status:empty,
.sound-status:empty {
  display: none;
}
/* Slider: a gray pill with ticks, a white pill thumb with a label.
   A transparent native range sits on top: mouse, touch, keyboard and screen readers work as usual.
   The range thumb and the visible thumb share one width (--tw), so their positions match. */
.range {
  --tw: 104px;
  --p: 0.5;
  position: relative;
  display: block;
  height: 40px;
  border-radius: 999px;
  background: var(--tray);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--color-black) 7%, transparent),
    inset 0 0 0 0.5px color-mix(in srgb, var(--color-black) 6%, transparent);
}
/* The scale, a ruler: a long tick every 60 px and four short ones between, light along the whole length, darker where
   passed. The short ticks skip the places of the long ones, so no tick is drawn over another. */
.range::before,
.range::after {
  --tick: color-mix(in srgb, var(--color-black) 9%, transparent);
  content: "";
  position: absolute;
  left: 16px;
  right: 16px;
  top: 11px;
  bottom: 11px;
  background:
    repeating-linear-gradient(90deg, var(--tick) 0 1px, transparent 1px 60px) 0 0 / 100% 100% no-repeat,
    repeating-linear-gradient(90deg, transparent 0 12px, var(--tick) 12px 13px, transparent 13px 24px, var(--tick) 24px 25px,
        transparent 25px 36px, var(--tick) 36px 37px, transparent 37px 48px, var(--tick) 48px 49px, transparent 49px 60px)
      0 50% / 100% 6px no-repeat;
  pointer-events: none;
}
/* Where the browser can round, the ruler is a whole number of 60 px steps, centred: it starts and ends on a long tick,
   the same distance from both ends of the tray. */
@supports (width: round(down, 100px, 60px)) {
  .range::before,
  .range::after {
    --len: calc(round(down, 100% - 32px, 60px) + 1px);
    left: round(down, (100% - var(--len)) / 2, 1px);
    right: auto;
    width: var(--len);
  }
}
.range::after {
  --tick: color-mix(in srgb, var(--color-black) 20%, transparent);
  clip-path: inset(0 calc((1 - var(--p)) * 100%) 0 0);
}
/* The part of a track a video takes: the song's loudness on the tray instead of the ruler, the pill is the 12 seconds
   and says where they start; the detent marks the strongest part, where it starts on its own. */
.range.clip[hidden] {
  display: none;
}
.range.clip::before,
.range.clip::after {
  display: none;
}
.range.clip canvas {
  position: absolute;
  inset: 8px 16px;
  width: calc(100% - 32px);
  height: calc(100% - 16px);
}
/* The detent: a dot on the tray under where the look put the thumb. It's hidden while the thumb sits on it. */
.detent {
  position: absolute;
  bottom: 5px;
  left: calc(3px + var(--d, 0.5) * (100% - var(--tw) - 6px) + var(--tw) / 2);
  width: 4px;
  height: 4px;
  margin-left: -2px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ink) 45%, transparent);
  pointer-events: none;
}
.range input {
  -webkit-appearance: none;
  appearance: none;
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: grab;
}
.range input:active {
  cursor: grabbing;
}
.range input::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: var(--tw);
  height: 40px;
}
.range input::-moz-range-thumb {
  width: var(--tw);
  height: 40px;
  border: 0;
}
.thumb {
  position: absolute;
  z-index: 1;
  top: 3px;
  bottom: 3px;
  left: calc(3px + var(--p) * (100% - var(--tw) - 6px));
  width: var(--tw);
  display: grid;
  place-items: center;
  border-radius: 999px;
  background: linear-gradient(180deg, #fff 0%, var(--panel) 100%);
  box-shadow: var(--raise);
  pointer-events: none;
  transition: transform 180ms cubic-bezier(0.3, 1.3, 0.4, 1), box-shadow 180ms ease-out;
}
/* Grabbed, the thumb lifts a little off the tray: a touch larger, a longer shadow. */
.range:has(input:active) .thumb {
  transform: scale(1.045);
  box-shadow: inset 0 1px 0 #fff, 0 0 0 0.5px color-mix(in srgb, var(--color-black) 10%, transparent),
    0 2px 4px color-mix(in srgb, var(--color-black) 8%, transparent),
    0 10px 20px -8px color-mix(in srgb, var(--color-black) 28%, transparent);
}
.thumb > span {
  grid-area: 1 / 1;
  transition: opacity 120ms ease-out;
}
.name {
  font-weight: 500;
}
.val {
  opacity: 0;
  font-variant-numeric: tabular-nums;
}
.val::after {
  content: attr(data-v);
}
/* While dragging or adjusting with the keyboard, the number replaces the label. */
.range:has(input:active) .name,
.range:has(input:focus-visible) .name {
  opacity: 0;
}
.range:has(input:active) .val,
.range:has(input:focus-visible) .val {
  opacity: 1;
}
.range:has(input:focus-visible) .thumb {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
/* Segments: a recessed tray, the selected one is filled. */
fieldset {
  border: 0;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
}
/* The panel's parts open over a hairline, 24 px from the part above. One size of name everywhere: Sound is named
   like Background, 13 px medium. Look shows no name, its sliders carry theirs on the thumbs. */
.group {
  display: flex;
  align-items: center;
  margin: 16px 0 0;
  padding: 16px 0 0;
  border-top: 0.5px solid color-mix(in srgb, var(--color-black) 10%, transparent);
  font-size: var(--text-meta);
  font-weight: 500;
  letter-spacing: -0.04em;
}
.group.rule {
  padding-top: 8px;
}
.group h2 {
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
}
/* Random, the sixth key: an empty well with a die until the first roll, then the roll itself, with the die on its corner.
   Selected, it's raised like a preset. The die turns a third of a turn per roll. */
.presets .seg:has(> .slot) {
  --n: 6;
}
.presets .slot {
  width: auto;
  padding: 0;
  border-radius: calc(var(--thumb) * 0.24 + var(--pad));
  background: none;
  box-shadow: none;
  color: inherit;
  font-weight: inherit;
}
.presets .slot:hover {
  filter: none;
}
.presets .slot:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 1px;
}
.slot .well {
  position: relative;
  display: grid;
  place-items: center;
  background: var(--tray);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--color-black) 8%, transparent),
    inset 0 0 0 0.5px color-mix(in srgb, var(--color-black) 7%, transparent);
  color: color-mix(in srgb, var(--ink) 80%, transparent);
}
.slot canvas {
  display: none;
}
.slot.is-filled .well {
  background: none;
  box-shadow: none;
}
.slot.is-filled canvas {
  display: block;
}
.slot svg {
  transform: rotate(var(--turn, 0deg));
  transition: transform 480ms cubic-bezier(0.3, 1.4, 0.5, 1);
}
/* A new roll comes into its key with a small turn, instead of being swapped in. */
.slot.is-rolling canvas {
  animation: roll-in 460ms cubic-bezier(0.3, 1.35, 0.4, 1);
}
@keyframes roll-in {
  from {
    opacity: 0;
    transform: scale(0.82) rotate(-8deg);
  }
}
.slot.is-filled svg {
  position: absolute;
  top: -5px;
  right: -5px;
  width: 20px;
  height: 20px;
  padding: 4px;
  border-radius: 50%;
  background: linear-gradient(180deg, #fff 0%, var(--panel) 100%);
  box-shadow: var(--raise);
  color: var(--ink);
}
.presets .seg:has(> .slot.is-on) {
  --i: 5;
}
.presets .seg:has(> .slot.is-on)::before {
  opacity: 1;
}
.presets .slot.is-on > span {
  color: var(--ink);
}
/* With a mouse, a key under the glass answers the pointer: its glass lifts 2 px; an empty Random turns its die a little. */
.presets canvas,
.slot .well {
  transition: transform 200ms cubic-bezier(0.3, 1.3, 0.4, 1), box-shadow 200ms ease-out;
}
@media (hover: hover) {
  .presets label:hover canvas,
  .presets .slot:hover .well {
    transform: translateY(-2px);
    box-shadow: 0 6px 12px -6px color-mix(in srgb, var(--color-black) 30%, transparent);
  }
  .slot:not(.is-filled):hover svg {
    transform: rotate(calc(var(--turn, 0deg) + 30deg));
  }
}
@media (prefers-reduced-motion: reduce) {
  .slot svg,
  .presets canvas,
  .slot .well {
    transition: none;
  }
}
/* For screen readers only: the group name already says it on screen. */
.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* Group and toggle labels are medium: they are names. */
legend,
.check span {
  font-weight: 500;
}
legend {
  width: 100%;
  padding: 0;
  margin-bottom: 8px;
}
/* A name under a slider gets the space of a new line of thought: 16 px, not 8. */
.controls > .range + fieldset {
  margin-top: 8px;
}
/* One selected pill per tray, sliding to the new option (like iOS segmented controls):
   --n is how many options, --i is which one is selected. */
.seg {
  --n: 2;
  --i: 0;
  position: relative;
  isolation: isolate;
  display: flex;
  width: 100%;
  gap: 2px;
  padding: 3px;
  border-radius: 999px;
  background: var(--tray);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--color-black) 7%, transparent),
    inset 0 0 0 0.5px color-mix(in srgb, var(--color-black) 6%, transparent);
}
fieldset label {
  flex: 1;
}
fieldset input {
  position: absolute;
  opacity: 0;
}
fieldset span {
  display: block;
  padding: 8px 6px;
  border-radius: 999px;
  text-align: center;
  cursor: pointer;
  opacity: 0.6;
  transition: opacity 160ms ease-out;
}
/* Every control in the panel is 40 px tall: a 34 px option in a tray with 3 px around it. */
.controls .seg > label > span {
  display: grid;
  place-items: center;
  height: 34px;
  padding: 0 6px;
}
.seg::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: 3px;
  bottom: 3px;
  left: 3px;
  width: calc((100% - 6px - (var(--n) - 1) * 2px) / var(--n));
  /* translate, not transform: the stretch below is a scale, which would stretch a transform's shift along with the pill. */
  translate: calc(var(--i) * (100% + 2px)) 0;
  border-radius: 999px;
  background: linear-gradient(180deg, #fff 0%, var(--panel) 100%);
  box-shadow: var(--raise);
  transition: translate 360ms cubic-bezier(0.3, 1.05, 0.4, 1), opacity 160ms ease-out; /* barely past the target: the stretch is the bounce now, and it stays inside the tray */
}
/* On its way to the new option the pill stretches a little and settles (app.js replays is-moving on each change). */
.seg.is-moving::before {
  animation: stretch 380ms cubic-bezier(0.3, 0.9, 0.4, 1);
}
@keyframes stretch {
  45% {
    scale: 1.1 0.94;
  }
}
.seg:has(> label:nth-child(3)) {
  --n: 3;
}
.seg:has(> label:nth-child(4)) {
  --n: 4;
}
.seg:has(> label:nth-child(5)) {
  --n: 5;
}
.seg:has(> label:nth-child(2) input:checked) {
  --i: 1;
}
.seg:has(> label:nth-child(3) input:checked) {
  --i: 2;
}
.seg:has(> label:nth-child(4) input:checked) {
  --i: 3;
}
.seg:has(> label:nth-child(5) input:checked) {
  --i: 4;
}
/* Nothing selected (preset cleared by a manual change): no pill. */
.seg:not(:has(input:checked))::before {
  opacity: 0;
}
fieldset label:hover span {
  opacity: 0.85;
}
fieldset input:checked + span {
  opacity: 1;
  font-weight: 500;
}
fieldset input:focus-visible + span {
  outline: 2px solid var(--ink);
  outline-offset: 1px;
}
/* Thickness only matters when volume is on. Angle, length and spin only when sides are selected, so with All they're gone. */
[data-volume="off"] .thick {
  opacity: 0.35;
  pointer-events: none;
}
[data-sides="all"] .directional {
  display: none;
}
/* Checkboxes as toggles. The whole row is the target, 32 px tall, so a finger finds it. */
.check {
  display: flex;
  justify-content: space-between;
  align-items: center;
  min-height: 32px;
  cursor: pointer;
}
.check input {
  order: 2;
  -webkit-appearance: none;
  appearance: none;
  position: relative;
  width: 36px;
  height: 22px;
  margin: 0;
  border-radius: 22px;
  background: var(--tray);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--color-black) 10%, transparent),
    inset 0 0 0 0.5px color-mix(in srgb, var(--color-black) 8%, transparent);
  cursor: pointer;
  transition: background-color 160ms ease-out;
}
.check input::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: linear-gradient(180deg, #fff 0%, var(--panel) 100%);
  box-shadow: var(--raise);
  transition: transform 200ms cubic-bezier(0.3, 1.4, 0.5, 1);
}
/* On is the knob turning soft black at the right end; the tray stays light, so the one black key stays Download PNG. */
.check input:checked::before {
  background: var(--btn-dark);
  box-shadow: inset 0 0.5px 0 rgb(255 255 255 / 0.18), 0 0 0 0.5px rgb(0 0 0 / 0.5), 0 1px 2px rgb(0 0 0 / 0.2);
  transform: translateX(14px);
}
.check input:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
/* Download and what follows it (Share, the after-download moves, status) dock at the bottom of the screen
   while the panel scrolls, so Download PNG is on the first screen whatever the panel holds above it.
   The hairline and the frost show only while the panel actually runs under the dock (app.js sets is-stuck). */
.dock {
  position: sticky;
  bottom: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  gap: inherit;
  padding: 12px 0;
  border-top: 0.5px solid transparent;
  transition: border-color 160ms ease-out, background-color 160ms ease-out;
}
.dock.is-stuck {
  background: color-mix(in srgb, var(--color-off-white) 86%, transparent);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-top-color: color-mix(in srgb, var(--color-black) 10%, transparent);
}
.actions {
  display: grid;
  gap: 8px;
}
/* A key: its name, and under it in small print what you get; the arrow at the end opens the formats.
   Download PNG is the one black key on the page. */
.key {
  position: relative;
  display: flex;
  height: 50px;
  border-radius: 999px;
  background: linear-gradient(180deg, #fff 0%, var(--panel) 100%);
  box-shadow: var(--raise);
  color: var(--ink);
  transition: transform 120ms ease-out;
}
/* Pressed, Download PNG has a band of light run across it, like light across glass (app.js replays is-flash). */
.key.dark::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(105deg, transparent 38%, rgb(255 255 255 / 0.2) 50%, transparent 62%) 130% 0 / 260% 100% no-repeat;
  opacity: 0;
  pointer-events: none;
}
.key.dark.is-flash::after {
  animation: sheen 720ms cubic-bezier(0.35, 0.6, 0.3, 1);
}
@keyframes sheen {
  from {
    opacity: 1;
    background-position: 130% 0;
  }
  to {
    opacity: 1;
    background-position: -30% 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .key.dark.is-flash::after,
  .seg.is-moving::before,
  .slot.is-rolling canvas {
    animation: none;
  }
  .range:has(input:active) .thumb {
    transform: none;
  }
}
.key.dark {
  background: var(--btn-dark);
  box-shadow: var(--btn-edge);
  color: var(--color-off-white);
}
.key > button {
  height: 100%;
  background: none;
  box-shadow: none;
  color: inherit;
  transition: background-color 120ms ease-out;
}
/* Not while disabled: Record video keeps its progress fill under the cursor while it records. */
.key > button:hover:not(:disabled) {
  filter: none;
  background: color-mix(in srgb, var(--color-black) 3%, transparent);
}
.key.dark > button:hover:not(:disabled) {
  background: rgb(255 255 255 / 0.07);
}
.key > button:active {
  transform: none;
}
.key:has(> button:active) {
  transform: scale(0.985);
}
.key > button:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
.key > button:first-child {
  flex: 1;
  width: auto;
  display: grid;
  place-content: center;
  padding: 0 8px 0 16px;
  border-radius: 999px 0 0 999px;
  line-height: 1.25;
}
.key small {
  font-size: 11px;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  opacity: 0.55;
  white-space: nowrap;
}
/* The arrow: its own part of the key, after a hairline. It turns over while its menu is open. */
.key .opt {
  flex: none;
  display: grid;
  place-items: center;
  width: 40px;
  padding: 0 4px 0 0;
  border-radius: 0 999px 999px 0;
  box-shadow: inset 0.5px 0 0 color-mix(in srgb, var(--color-black) 12%, transparent);
}
.key.dark .opt {
  box-shadow: inset 0.5px 0 0 rgb(255 255 255 / 0.16);
}
.opt svg {
  opacity: 0.7;
  transition: transform 220ms cubic-bezier(0.3, 1.3, 0.4, 1);
}
.opt[aria-expanded="true"] svg {
  transform: rotate(180deg);
}
button:disabled {
  opacity: 0.4;
  cursor: default;
}
/* A key dims as a whole, not just its label. */
.key:has(> button:disabled) {
  opacity: 0.4;
}
.key > button:disabled {
  opacity: 1;
  filter: none; /* the page's button:hover brightening would wash out the recording fill */
}
.is-recording .opt {
  pointer-events: none;
}
/* The formats menu opens over its key and as wide as it (app.js places it). A row is a format with a drawing
   of its framing; the chosen one has a check. Hovering a row lays the tray under it. */
.menu {
  position: fixed;
  inset: auto;
  /* app.js pins its top 8 px over the key; it rises by its own height. Pinned by the bottom, with the popover's own
     fit-content height, a fieldset could be stretched up to the top of the screen. */
  height: auto;
  translate: 0 -100%;
  margin: 0;
  padding: 4px;
  border: 0;
  border-radius: 18px;
  background: linear-gradient(180deg, #fff 0%, var(--panel) 100%);
  box-shadow: inset 0 1px 0 #fff, 0 0 0 0.5px color-mix(in srgb, var(--color-black) 10%, transparent),
    0 2px 4px color-mix(in srgb, var(--color-black) 6%, transparent),
    0 16px 32px -12px color-mix(in srgb, var(--color-black) 24%, transparent);
  color: var(--ink);
  font-size: var(--text-meta);
  letter-spacing: -0.04em;
}
/* A fieldset is display: flex in this sheet, which would keep a closed menu on screen. */
.menu:not(:popover-open) {
  display: none;
}
/* Without popovers (Safari before 17, Firefox before 125) the menu opens in the dock itself, under the keys,
   when app.js marks it is-open. A browser that can't read :popover-open drops the rule above, so this one hides it. */
@supports not selector(:popover-open) {
  .menu {
    position: static;
    display: none;
    gap: 2px;
    translate: none;
  }
  .menu.is-open {
    display: grid;
  }
}
.menu:popover-open {
  display: grid;
  gap: 2px;
  animation: menu-in 220ms cubic-bezier(0.3, 1.2, 0.4, 1);
}
@keyframes menu-in {
  from {
    opacity: 0;
    transform: translateY(6px) scale(0.98);
  }
}
.menu label {
  display: block;
}
.menu span {
  display: grid;
  grid-template-columns: 16px 1fr 10px;
  align-items: center;
  gap: 10px;
  height: 36px;
  padding: 0 12px;
  border-radius: 14px;
  text-align: left;
  white-space: nowrap;
  opacity: 1;
  color: color-mix(in srgb, var(--ink) 72%, transparent);
  transition: background-color 120ms ease-out;
}
.menu label:hover span {
  opacity: 1;
  background: var(--tray);
  color: var(--ink);
}
.menu input:checked + span {
  color: var(--ink);
}
.menu input:checked + span::after {
  content: "";
  width: 9px;
  height: 5px;
  border-left: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(-45deg);
}
.menu input:focus-visible + span {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
}
@media (prefers-reduced-motion: reduce) {
  .menu:popover-open {
    animation: none;
  }
}
/* Share is a small link under the buttons, shown after the first save. */
.share {
  width: auto;
  align-self: center;
  padding: 6px 12px;
  background: none;
  box-shadow: none;
  color: var(--ink);
  animation: share-in 320ms cubic-bezier(0.3, 1.3, 0.4, 1);
}
.share:hover {
  filter: none;
  background: var(--tray);
}
/* On iPhone it's a full dark button: it's the only way to save the file. */
.share.is-save {
  width: 100%;
  align-self: stretch;
  padding: 12px 16px;
  background: var(--btn-dark);
  box-shadow: var(--btn-edge);
  color: var(--color-off-white);
}
.share.is-save:hover {
  background: var(--btn-dark);
  filter: brightness(1.12);
}
.share[hidden] {
  display: none;
}
@keyframes share-in {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
}
/* While video records, the key fills over the recording and counts the seconds down (app.js), so you can see how long
   to wait: one loop, or longer with sound. The status under the keys then speaks only to screen readers, unless it has
   something of its own to say: with no background, that the video records on light. */
.is-recording .key:has([data-video]) {
  opacity: 1;
}
.is-recording [data-video] .res {
  display: none;
}
.is-recording:not([data-background="none"]) .status {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.is-recording .key > [data-video] {
  opacity: 1;
  background:
    linear-gradient(var(--tray), var(--tray)) left / 0% 100% no-repeat,
    linear-gradient(180deg, #fff 0%, var(--panel) 100%);
  animation: rec var(--rec, 4s) linear forwards;
}
@keyframes rec {
  to {
    background-size: 100% 100%, auto;
  }
}
/* The sound meter by the word Sound: eight thin bars, ink colour, growing from the baseline. */
.bars {
  display: inline-flex;
  align-items: flex-end;
  gap: 2px;
  height: 12px;
  margin-left: auto;
}
.bars[hidden] {
  display: none;
}
/* A quiet band is a short stub, not a dot, and the bars are a shade lighter than the word, so the row reads as one
   small meter. A bar grows by its height, not by a scale: a box lands on whole pixels, a transform doesn't, and a bar
   rescaled every frame touches the pixel beside it, which some browsers then leave behind as a hairline of ink. */
.bars i {
  width: 2px;
  height: 30%;
  border-radius: 1px;
  background: color-mix(in srgb, currentColor 55%, transparent);
}
/* Each preset key shows your own logo in that look: a small glass over the name. */
.presets label,
.presets .slot {
  flex: 0 0 var(--key);
  width: var(--key); /* the row's own width is summed from its keys: some browsers take their content, not flex-basis */
}
.presets span {
  display: grid;
  justify-items: center;
  gap: 4px;
  padding-block: var(--pad) calc(var(--pad) - 2px); /* the name's own line spacing makes up the 2 px */
  font-size: 12px;
}
.presets canvas,
.slot .well {
  width: var(--thumb);
  height: var(--thumb);
  border-radius: calc(var(--thumb) * 0.24); /* the glass's own corner; it also hides the dark corners of the Night key */
}
/* A round look (Lens, or a round roll) gets a round key glass, so its lift and shadow follow the circle. */
.presets canvas.is-round,
.slot.is-round .well {
  border-radius: 50%;
}
/* The glass stays at full strength on every key; only the names of the unselected ones dim. */
.presets span {
  opacity: 1;
  color: color-mix(in srgb, var(--ink) 60%, transparent);
}
.presets input:checked + span {
  color: var(--ink);
}
/* Under the glass the tray itself is gone: only the selected key is raised. Softer corners than a pill,
   or the selection turns into an oval. */
.presets .seg {
  border-radius: 22px;
  background: none;
  box-shadow: none;
}
.presets .seg::before {
  border-radius: calc(var(--thumb) * 0.24 + var(--pad));
}
/* Six keys share the row under the glass: the labels go edge to edge and never wrap. */
.presets span {
  padding-inline: 0;
  white-space: nowrap;
}

/* Advanced: secondary settings are collapsed so the download buttons stay on the first screen.
   Opens smoothly (interpolate-size; instantly where it isn't supported). */
.glass {
  interpolate-size: allow-keywords;
}
/* Advanced closes the panel, under a hairline like the groups: the finer look settings. The formats live on the keys. */
.advanced {
  margin-top: 16px;
  padding-top: 8px;
  border-top: 0.5px solid color-mix(in srgb, var(--color-black) 10%, transparent);
}
.advanced summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  min-height: 32px;
  list-style: none;
  font-weight: 500;
  cursor: pointer;
}
.advanced summary::-webkit-details-marker {
  display: none;
}
.advanced summary::after {
  content: "";
  width: 7px;
  height: 7px;
  margin-right: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  opacity: 0.6;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 240ms ease-out;
}
.advanced[open] summary::after {
  transform: translateY(2px) rotate(-135deg);
}
.advanced summary:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  border-radius: 6px;
}
.advanced::details-content {
  block-size: 0;
  overflow: hidden;
  transition: block-size 360ms cubic-bezier(0.3, 0.9, 0.4, 1), content-visibility 360ms allow-discrete;
}
.advanced[open]::details-content {
  block-size: auto;
}
.advanced.instant::details-content {
  transition: none;
}
/* Side padding so thumb and tray shadows aren't clipped. */
.adv-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin: 0 -6px;
  padding: 12px 6px 6px;
}

/* Footer: the ☺ signature. */
.bottom {
  grid-area: foot;
  display: grid;
  gap: 6px;
  padding: 8px 0 0;
  font-size: var(--text-meta);
}
.bottom p {
  margin: 0;
}
.made {
  font-size: var(--text-body);
  font-weight: 500;
}
/* The name links to the live version: clearly clickable, but without the blue browser underline. */
.made a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: var(--hair);
  transition: text-decoration-color 120ms ease-out;
}
.made a:hover {
  text-decoration-color: currentColor;
}

/* Short laptop screen: tighter, so Download PNG stays on the first screen. */
@media (min-width: 761px) and (max-height: 820px) {
  .intro {
    padding-top: 44px;
  }
  h1 {
    margin-bottom: 8px;
  }
  .glass {
    row-gap: 16px;
  }
  .controls {
    padding-bottom: 16px;
  }
  .dock {
    padding: 10px 0;
  }
  /* Sound's own line stays: it says why a sound didn't start. */
  .controls > .hint:not(.caption, .sound-status) {
    display: none;
  }
}

@media (max-width: 760px) {
  /* Dropping and pasting are desktop gestures; on a phone the hint only takes room from the glass. */
  .drop-hint {
    display: none;
  }
  /* On a phone the dock is under the thumb, edge to edge: Download PNG and Record video side by side, tighter. */
  .dock {
    gap: 8px;
    margin-inline: -24px;
    padding: 10px 24px;
  }
  .dock .actions {
    grid-template-columns: 1fr 1fr;
    margin-top: 0;
  }
  /* Half a screen per key: a narrower arrow, and the small print keeps only what changes, the length. */
  .key > button:first-child {
    padding: 0 6px 0 12px;
  }
  .key .opt {
    width: 34px;
  }
  .key .res {
    display: none;
  }
  .dock .share.is-save {
    margin-top: 0;
  }
  .glass {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "intro" "stage" "looks" "controls" "foot";
    grid-template-rows: none;
    /* The page under the glass: its own color, darkening with a dark stage. */
    --band: color-mix(in srgb, var(--color-black) calc(var(--dark, 0) * 100%), var(--color-off-white));
  }
  /* On a phone the glass is the headline: the name and the line sit in one quiet row over it. */
  .intro {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 7px;
    padding: 0;
  }
  h1 {
    margin: 0;
    font-size: 17px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.045em;
  }
  .intro p {
    font-size: 15px;
  }
  .intro p:not(.whatsnew) {
    color: color-mix(in srgb, var(--ink) 60%, transparent);
  }
  .intro .whatsnew {
    flex-basis: 100%;
    margin-top: 4px;
    font-size: 12px;
  }
  /* On phones the glass sticks to the top of the screen and everything under it scrolls under it, the presets too:
     they're picked with the glass in full view, and while the settings are tuned the room goes to them. The wrappers
     step aside, so the glass and the presets are rows of the page itself and the glass stays pinned all the way down. */
  .stage-wrap,
  .stage-sticky {
    display: contents;
  }
  .stage-wrap::before,
  .stage-wrap::after {
    content: none;
  }
  /* The glass takes the width, as long as Upload still shows above the dock with the browser's bars out (svh).
     Pinned, it keeps a band of the page's color 8 px over and under it, edge to edge: a dark stage is this band itself.
     The band, the fade under it and the presets' band are boxes of their own, so a tap beside the glass lands on the
     band, not on a key hidden under it. How far the page's edges are past each, --edge-l and --edge-r, comes from
     app.js, to the fraction of a pixel: 100vw would count a scrollbar and widen the page. The glass's band runs 1 px
     into the presets' band, so the two never show a seam. */
  .stage {
    grid-area: stage;
    position: sticky;
    top: 8px;
    z-index: 5;
    width: min(100%, max(160px, 100svh - 330px)); /* never under 160 px, as on a phone held sideways */
    margin: 8px auto 0;
  }
  .stage::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset: -8px calc(-1 * var(--edge-r, 0px)) -9px calc(-1 * var(--edge-l, 0px));
    background: var(--band);
  }
  /* Once the presets reach the band (app.js marks it), what slides under the glass fades out over 20 px instead of
     being cut on a line. At rest the fade would lie over the presets, so it waits. A dark band has none. */
  .stage::after {
    content: "";
    position: absolute;
    top: calc(100% + 8px);
    left: calc(-1 * var(--edge-l, 0px));
    right: calc(-1 * var(--edge-r, 0px));
    height: 20px;
    background: linear-gradient(var(--color-off-white), color-mix(in srgb, var(--color-off-white) 0%, transparent));
    opacity: 0;
    transition: opacity 160ms ease-out;
    pointer-events: none;
  }
  .is-under .stage::after {
    opacity: 1;
  }
  [data-tone="dark"] .stage::after {
    display: none;
  }
  /* Six keys across the phone: as wide as fits (the page less its 24 px sides, the row's 3 px edges and 2 px gaps), 56 px at most.
     8 px under the glass (the grid's 24 px gap, less 16), on a band of the page's color that runs 10 px under them, so
     a dark stage is dark behind them too until they slide under the glass. */
  .looks {
    grid-area: looks;
    --pad: 6px;
    --key: min(56px, (100vw - 64px) / 6);
    margin: -16px auto 10px;
    position: relative;
    isolation: isolate;
  }
  .looks::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset: 0 calc(-1 * var(--edge-r, 0px)) -10px calc(-1 * var(--edge-l, 0px));
    background: var(--band);
  }
  .presets span {
    font-size: 11px;
  }
}

/* iPhone zooms the page into any text field set under 16px, so on touch screens the field is 16px. */
@media (pointer: coarse) {
  .type input {
    font-size: 16px;
  }
}
