/* Burpee Club. The look follows notes/Burpee Club/Ideas/Screens/Final screens.html,
   scaled from its 236px-wide mockup phone to a real one. */

:root {
  --app: #2C2C2A;
  --raised: #444441;
  --line: #5F5E5A;
  --text: #F1EFE8;
  --muted: #B4B2A9;
  --gold: #EF9F27;        /* stars and team progress only */
  --gold-dark: #BA7517;
  --gold-light: #FAC775;
  --coral: #F0997B;  --coral-ink: #4A1B0C;
  --teal: #5DCAA5;   --teal-ink: #04342C;
  --purple: #AFA9EC; --purple-ink: #26215C;
  --blue: #85B7EB;   --blue-ink: #042C53;
  --pink: #ED93B1;   --pink-ink: #4B1528;
  --green: #97C459;  --green-ink: #173404;
  --font: 'Fredoka', ui-rounded, 'SF Pro Rounded', system-ui, sans-serif;
  /* Motion is quick, with a bounce: the spring overshoots a little. The glide doesn't, for an
     arc that lands on empty or full, where an overshoot would wrap its dash around. */
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --glide: cubic-bezier(0.22, 1, 0.36, 1);
  color-scheme: dark;
}

/* A person's colour, for everything that's theirs: card, avatar and dots. */
.coral  { --person: var(--coral);  --ink: var(--coral-ink); }
.teal   { --person: var(--teal);   --ink: var(--teal-ink); }
.purple { --person: var(--purple); --ink: var(--purple-ink); }
.blue   { --person: var(--blue);   --ink: var(--blue-ink); }
.pink   { --person: var(--pink);   --ink: var(--pink-ink); }
.green  { --person: var(--green);  --ink: var(--green-ink); }

*, *::before, *::after { box-sizing: border-box; }
/* No grey flash on tap, as in a native app. Focus outlines stay. No double-tap zoom either,
   which quick taps on the day chips set off: pinch-zoom still works, for anyone who needs it.
   Safari only double-tap zooms a page that's zoomed in already, and only keeps to this rule for
   a tap on something that takes clicks, like a button. So each dialog has an empty onclick:
   otherwise a double tap on its title or around its buttons zooms in on it. */
html { -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
/* Controls aren't text to copy: a long press or a drag on one selects nothing, and a link shows
   no long-press menu, as in a native app. That covers the swatches and goal chips, which are labels.
   Text worth copying, like the invite link and the group's code and secret, stays selectable. */
button, a, label { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
body { margin: 0; background: var(--app); color: var(--text); font-family: var(--font); font-size: 16px; line-height: 1.35; }
p, h1, h2 { margin: 0; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
button:focus-visible { outline: 2px solid var(--gold-light); outline-offset: 3px; }
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.icon { width: 1.15em; height: 1.15em; vertical-align: -0.22em; color: var(--gold); }

/* One column, with the log button pinned to the bottom. iOS softens the very top of the
   screen, so the top row starts a little below it. */
.home {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 100vh;
  min-height: 100dvh;
  max-width: 440px;
  margin: 0 auto;
  padding: calc(env(safe-area-inset-top) + 30px) 20px max(20px, env(safe-area-inset-bottom));
}
.top { display: flex; justify-content: space-between; font-size: 17px; }
.top-right { display: flex; align-items: center; gap: 14px; }
/* The gear isn't gold: gold is only for stars and team progress. */
.gear { display: flex; padding: 2px; color: var(--muted); }
.gear .icon { width: 1.4em; height: 1.4em; color: currentColor; }
/* While someone's waiting to join, a coral dot on the gear says so. */
.gear.waiting { position: relative; }
.gear.waiting::after { content: ''; position: absolute; top: 1px; right: 1px; width: 9px; height: 9px; border-radius: 50%; background: var(--coral); box-shadow: 0 0 0 2px var(--app); }

/* The team ring, around this week's star. Its top margin depends only on the screen's
   height, so the star stays put when what's under it changes, as when you sit a week out.
   A tall phone's spare height goes to it and to the gap above the buttons. */
.ring { display: block; width: min(74vw, 300px); height: auto; margin: clamp(0px, 6svh - 24px, 48px) auto 0; }
.ring-track, .ring-fill, .ring-stripes { fill: none; stroke-width: 16; }
.ring-track { stroke: var(--raised); }
.ring-fill { stroke: var(--gold); stroke-linecap: round; }
.ring-stripes { stroke: var(--gold-dark); stroke-dasharray: 5 7.17; animation: flow 1.8s linear infinite; }
.tip { fill: var(--gold-light); animation: pulse 2s ease-in-out infinite; }
/* The tip sits at twelve o'clock and turns to the arc's end, so it rides along as the arc moves. */
.tip-turn { transform-box: view-box; transform-origin: center; }
/* An empty week has no arc or tip to show, and an earned one no tip. */
.ring.empty :is(.ring-fill, .ring-flow, .tip-turn), .ring.earned .tip-turn { opacity: 0; }
.star { fill: var(--raised); stroke: var(--gold); stroke-width: 2; stroke-linejoin: round; }
.star.lit { fill: var(--gold); stroke: var(--gold-light); }
.glow { transform-box: fill-box; transform-origin: center; animation: glow 2.4s ease-in-out infinite; }
.glow-outer { fill: var(--gold); opacity: 0.12; }
.glow-inner { fill: var(--gold); opacity: 0.22; }

/* Everyone's card. A card with no partner on its row, as when someone's alone in the
   group, sits in the middle. */
.cards { --gap: 12px; display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); }
.cards > :last-child:nth-child(odd) { grid-column: 1 / -1; justify-self: center; width: calc((100% - var(--gap)) / 2); }
.card { --name-size: 17px; --status-size: 14px; display: flex; align-items: center; gap: 12px; padding: 12px; border-radius: 20px; background: var(--raised); text-align: left; }
/* Room for the name and a status on two lines, as "out this week" takes on a narrow phone, so a
   card is one height whatever its status says, and the dots under it never move. 1.35 is the
   line height. */
.card > div { display: flex; flex-direction: column; justify-content: center; min-height: calc(1.35 * (var(--name-size) + 2 * var(--status-size))); }
.avatar { flex: none; width: 56px; height: 56px; }
.avatar-track, .avatar-arc { fill: none; stroke-width: 4; }
.avatar-track { stroke: var(--app); }
.avatar-arc { stroke: var(--person); stroke-linecap: round; }
.avatar.empty .avatar-arc { opacity: 0; }
.avatar-face { fill: var(--person); }
.avatar-initial { fill: var(--ink); font-family: var(--font); font-size: 15px; font-weight: 500; }
.badge { fill: var(--text); }
.badge-check { fill: none; stroke: var(--app); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.name { font-size: var(--name-size); }
.status { font-size: var(--status-size); color: var(--person); }

/* This week: a letter per day, a dot per person. Tapping it opens the week page. */
.strip { display: grid; grid-template-columns: repeat(7, 1fr); row-gap: 6px; justify-items: center; font-size: 14px; color: var(--muted); text-decoration: none; }
.letter.today { color: var(--coral); }
.dots { display: flex; gap: 3px; }
.dot { width: 12px; height: 12px; border-radius: 50%; background: var(--raised); }
.dot.logged { background: var(--person); }

.actions { display: flex; flex-direction: column; gap: 10px; margin-top: auto; }
/* How the week stands, at the top of the buttons, where it grows into the spare space rather than pushing anything down. */
.status-line { text-align: center; color: var(--muted); }
.cta { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; padding: 18px; border-radius: 22px; background: var(--coral); color: var(--coral-ink); font-size: 19px; font-weight: 500; text-decoration: none; }
.cta:disabled { cursor: default; }
.cta.logged { background: transparent; color: var(--coral); box-shadow: inset 0 0 0 2px var(--coral); }
.cta.logged .icon { color: currentColor; }
.switch { align-self: center; padding: 6px 12px; font-size: 14px; color: var(--muted); }

/* Someone sitting out, or without a goal yet: a quiet card. */
.card.away .avatar-face { fill: var(--line); }
.card.away .avatar-initial { fill: var(--text); }
.card.away .status { color: var(--muted); }
.avatar-moon { color: var(--text); }

/* Your days, to add a missed workout or take one back, in a dialog. On Monday mornings,
   what opens it is a gold reminder that last week is still open. */
.reminder { align-self: center; padding: 8px 16px; border-radius: 999px; box-shadow: inset 0 0 0 2px var(--gold); font-size: 15px; }
.reminder .icon { color: var(--gold); }
.hint { font-size: 14px; color: var(--muted); }
.two-lines { min-height: 2.7em; }
.chips { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.chip { padding: 10px 0; border: 1.5px solid var(--line); border-radius: 12px; font-size: 15px; }
.chip.logged { border-color: var(--person); background: var(--person); color: var(--ink); }
.chip:disabled { border-style: dashed; color: var(--line); cursor: default; }
.cta.quiet { padding: 14px; background: transparent; color: var(--text); box-shadow: inset 0 0 0 2px var(--line); font-size: 16px; }

/* A dialog, front and center over the dimmed page. It's an HTML popover, so it opens and
   closes without JavaScript: with its own button, or with a tap outside it. */
.dialog { width: min(100% - 40px, 360px); margin: auto; padding: 22px 20px 20px; border: 0; border-radius: 22px; background: var(--raised); color: var(--text); text-align: center; }
.dialog::backdrop { background: rgb(0 0 0 / 0.6); }
.dialog > * + *, .dialog-body > * + * { margin-top: 12px; }
.dialog-title { font-size: 20px; font-weight: 500; }

/* Three or four people: a smaller ring, tighter cards, and the dots in a 2x2 block. */
.compact .ring { width: min(58vw, 240px); }
.compact .cards { --gap: 8px; }
.compact .card { --name-size: 15px; --status-size: 13px; gap: 8px; padding: 8px 10px; border-radius: 16px; }
.compact .avatar { width: 42px; height: 42px; }
.compact .dots { display: grid; grid-template-columns: repeat(2, 9px); gap: 3px; }
.compact .dot { width: 9px; height: 9px; }

/* Opened from the home screen, home is one still screen, as in an app: the page doesn't
   bounce, and home only scrolls inside itself if a small phone can't fit it all. iOS has
   reported home-screen apps as both display modes. Home carries the page colour itself:
   iOS reportedly skips its top-edge blur over a fixed, opaque, full-width box.
   Upright, the ring gets the height home has left with everything under it showing (a card
   on two lines, the status line above the buttons, Monday's reminder), so it's one size
   whatever's under it, and the star stays put. A phone on its side hasn't the height for that, so there home scrolls. */
@media (display-mode: standalone), (display-mode: fullscreen) {
  html { overscroll-behavior: none; }
  .home { position: fixed; inset: 0; min-height: 0; overflow-y: auto; overscroll-behavior: none; background: var(--app); container-type: size; }
  .tour { position: fixed; inset: 0; height: auto; background: var(--app); }
  @media (orientation: portrait) {
    .ring { width: min(74vw, 300px, 100cqh - 460px); }
    .compact .ring { width: min(58vw, 240px, 100cqh - 492px); }
  }
  /* On its side, nothing squeezes to fit, or the drawings shrink away to nothing. */
  @media (orientation: landscape) {
    .home > * { flex-shrink: 0; }
  }
}

.title { font-size: 28px; font-weight: 500; }
a { color: var(--coral); }

/* Signing in and joining a group: a title, a few fields, and the button right under them,
   so it stays above the phone's keyboard. */
.entry { display: flex; flex-direction: column; gap: 14px; max-width: 440px; margin: 0 auto; padding: calc(env(safe-area-inset-top) + 38px) 20px max(20px, env(safe-area-inset-bottom)); }
.brand { display: flex; align-items: center; gap: 6px; margin-bottom: 18px; font-size: 17px; }
/* The brand with the gear beside it, for someone in no group. */
.entry > .top { align-items: center; margin-bottom: 18px; }
.entry > .top .brand { margin-bottom: 0; }
.form { display: flex; flex-direction: column; gap: 14px; margin-top: 8px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.label { font-size: 14px; color: var(--muted); }
/* 17px text, so iPhones don't zoom in on the field. */
.field input { width: 100%; padding: 15px 16px; border: 1.5px solid var(--raised); border-radius: 16px; background: var(--raised); color: var(--text); font: inherit; font-size: 17px; }
.field input::placeholder { color: var(--muted); opacity: 0.6; }
.field input:focus { outline: none; border-color: var(--coral); }
.field input[aria-invalid="true"] { border-color: var(--coral); }
/* Keeps the field's colours when the password manager fills it in. */
.field input:-webkit-autofill { -webkit-text-fill-color: var(--text); box-shadow: inset 0 0 0 40px var(--raised); caret-color: var(--text); }
.errorlist { margin: 0; padding: 0; list-style: none; font-size: 14px; color: var(--coral); }
.form .cta { margin-top: 6px; }
/* A button that reads as a link, for signing out. */
.link { color: var(--coral); text-decoration: underline; }
.link.quiet { color: var(--muted); }

/* Picking your colour and goal: radio buttons drawn as swatches and chips, no JS.
   The real inputs stay in place, unseen, so the keyboard and screen readers still use them. */
fieldset { min-width: 0; margin: 0; padding: 0; border: 0; }
legend { padding: 0; margin-bottom: 6px; }
.swatches { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.swatch { position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 12px 4px 10px; border-radius: 16px; background: var(--raised); font-size: 14px; color: var(--muted); cursor: pointer; }
.swatch input, .chip.pick input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: inherit; }
.blob { display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; border-radius: 50%; background: var(--person); color: var(--ink); font-size: 18px; font-weight: 500; }
/* The tick is always there, shrunk away, so picking a colour can pop it in. It leaves without
   the bounce, which would shrink it past nothing and flip it. */
.tick { display: block; width: 24px; height: 24px; scale: 0; transition: scale 0.15s ease-in; }
.swatch:has(input:checked) { box-shadow: inset 0 0 0 2px var(--person); color: var(--text); }
.swatch:has(input:checked) .tick { scale: 1; transition: scale 0.3s var(--spring); }
.swatch.taken { opacity: 0.5; cursor: default; }
.chip.pick { position: relative; text-align: center; cursor: pointer; }
.chip.pick:has(input:checked) { border-color: var(--coral); background: var(--coral); color: var(--coral-ink); }
.swatch:has(input:focus-visible), .chip.pick:has(input:focus-visible) { outline: 2px solid var(--gold-light); outline-offset: 2px; }

/* Settings, behind the gear: a back link, then rows like cards. */
.back { align-self: flex-start; color: var(--muted); text-decoration: none; }
.menu { display: flex; flex-direction: column; gap: 10px; margin: 6px 0 0; padding: 0; list-style: none; }
.menu > a, .menu > li { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; border-radius: 16px; background: var(--raised); color: var(--text); text-decoration: none; }
.menu .what { display: block; font-size: 17px; }
.menu .hint { display: block; margin-top: 2px; }
.chevron { font-size: 22px; color: var(--muted); }
.pip { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: var(--person); }
.note { padding: 10px 14px; border-radius: 14px; background: var(--raised); font-size: 14px; }

/* The invite: a QR code dark on light, which phones scan best, then the link and the code and secret. */
.qr { width: min(70vw, 260px); margin: 6px auto 0; padding: 14px; border-radius: 20px; background: var(--text); }
.qr svg { display: block; width: 100%; height: auto; }
.qr .qrline { stroke: var(--app); }
.link-text { overflow-wrap: anywhere; font-size: 14px; color: var(--muted); text-align: center; }
.pair { display: grid; grid-template-columns: auto 1fr; gap: 6px 14px; margin: 0; padding: 14px 16px; border-radius: 16px; background: var(--raised); }
.pair dt { color: var(--muted); }
.pair dd { margin: 0; }
/* Someone waiting to join: let them in, or not now. */
.answers { display: flex; flex: none; align-items: center; gap: 14px; }

/* The week page: one grid for this week and the last four, so every week's days sit under the same letters.
   A name column, then a column per day. */
.week-title { display: flex; align-items: baseline; justify-content: space-between; }
.week-grid { display: grid; grid-template-columns: 3.5em repeat(7, 1fr); row-gap: 8px; align-items: center; justify-items: center; font-size: 14px; color: var(--muted); }
.week-grid .who { justify-self: stretch; overflow: hidden; color: var(--text); white-space: nowrap; text-overflow: ellipsis; }
.week-grid .moon { color: var(--muted); }
.spot { width: 14px; height: 14px; border-radius: 50%; background: var(--raised); }
.spot.small { width: 9px; height: 9px; }
.spot.logged { background: var(--person); }
.spot.future { background: transparent; box-shadow: inset 0 0 0 1.5px var(--raised); }
.compact .spot { width: 12px; height: 12px; }
.compact .spot.small { width: 8px; height: 8px; }
.standing, .week-head { grid-column: 1 / -1; justify-self: stretch; }
.standing > span { color: var(--person); }
.standing .icon { width: 1em; height: 1em; color: currentColor; }
.week-head { display: flex; align-items: center; justify-content: space-between; margin-top: 8px; padding-top: 10px; border-top: 1px solid var(--line); color: var(--text); font-size: 15px; }

/* The moment after you log: the mascot hops in with a speech bubble, over the ring and cards, or the star. */
.moment { justify-content: center; align-items: center; text-align: center; }
.moment .ring { width: min(42vw, 170px); margin: 0 auto; }
.moment .cards { align-self: stretch; text-align: left; }
.mascot { width: 128px; height: auto; animation: hop 0.7s ease-out both; }
/* Then the rest comes in after it: the speech bubble, what you did for the week, and the way home. */
.moment > :not(.mascot) { animation: rise 0.45s ease-out 0.3s both; }
.moment > .bubble { animation-delay: 0.15s; }
.moment > .cta { animation-delay: 0.45s; }
/* A speech bubble, its tail pointing up at the mascot. */
.bubble { position: relative; max-width: 300px; padding: 12px 18px; border-radius: 18px; background: var(--raised); font-size: 17px; }
.bubble::before { content: ''; position: absolute; top: -8px; left: 50%; margin-left: -8px; border: 8px solid transparent; border-top: 0; border-bottom-color: var(--raised); }
/* Visible overflow, or the glow is clipped at the edges as it pulses bigger. */
.big-star { width: min(55vw, 220px); height: auto; overflow: visible; }
.pop { transform-box: fill-box; transform-origin: center; animation: pop 2.4s ease-in-out infinite; }
.twinkle { fill: var(--gold-light); animation: twinkle 1.8s ease-in-out infinite; }
.twinkle.later { animation-delay: 0.6s; }
.twinkle.latest { animation-delay: 1.2s; }
.gained { font-size: 17px; }
.gold { color: var(--gold); }
.cta.outline { width: auto; margin-top: 8px; padding: 12px 30px; background: transparent; color: var(--coral); box-shadow: inset 0 0 0 2px var(--coral); font-size: 17px; }

/* How it works: four cards side by side, one screen each, that you swipe between or step through
   with Next and Back. Each card's content is at most 400px wide, and a card too tall for the
   screen, as on a phone on its side, scrolls on its own rather than squeezing its drawings. */
.tour { display: flex; height: 100vh; height: 100dvh; overflow-x: auto; overflow-y: hidden; scroll-snap-type: x mandatory; scroll-behavior: smooth; overscroll-behavior: none; scrollbar-width: none; }
.tour::-webkit-scrollbar { display: none; }
.tour-card { flex: none; display: flex; flex-direction: column; gap: 16px; width: 100%; overflow-y: auto; scroll-snap-align: start; scroll-snap-stop: always; padding: calc(env(safe-area-inset-top) + 30px) max(20px, (100% - 400px) / 2) max(20px, env(safe-area-inset-bottom)); text-align: center; }
.tour-card > * { flex-shrink: 0; }
.tour .top a { color: var(--muted); text-decoration: none; }
/* The picture takes the spare height, so the words and the button sit at the bottom, as home's log button does. */
.picture { display: flex; flex: 1 0 auto; flex-direction: column; align-items: center; justify-content: center; gap: 16px; }
.picture .ring { width: min(56vw, 230px, 30svh); margin: 0; }
.picture .mascot { width: min(44vw, 170px); }
.picture .cards { align-self: stretch; }
/* Room for three lines on every card, so the titles line up from card to card. */
.say { min-height: 4.05em; font-size: 17px; color: var(--muted); }
.steps { display: flex; justify-content: center; gap: 8px; }
.steps > span { width: 8px; height: 8px; border-radius: 50%; background: var(--raised); }
.steps > .on { background: var(--coral); }

/* Feedback: every tap answers at once, waiting shows, and what changes moves. Home morphs in
   place, so its elements stay put and these transitions carry each one to its new value.
   There are no :hover rules: iPhones leave them stuck on after a tap. */

/* A tap squishes, then springs back. The release waits a moment, so a tap that swaps in the
   cheer still looks pressed in the frame its cross-fade starts from. */
:is(a, button).cta, :is(a, button).switch, .reminder, .chip, .swatch, .menu > a { transition: scale 0.3s var(--spring) 50ms; }
.chip { transition: scale 0.3s var(--spring) 50ms, background-color 0.3s ease-out, border-color 0.3s ease-out, color 0.3s ease-out; }
.swatch { transition: scale 0.3s var(--spring) 50ms, box-shadow 0.2s ease-out, color 0.2s ease-out; }
:is(a, button).cta:active, :is(a, button).switch:active, .reminder:active, .chip:active, .swatch:not(.taken):active, .menu > a:active { scale: 0.96; transition: scale 0.08s ease-out; }
/* Text links dim instead, and the gear gives a little turn. */
a:not(.cta, .gear, .menu > a), .link { transition: opacity 0.25s ease-out; }
a:not(.cta, .gear, .menu > a):active, .link:active { opacity: 0.6; transition: none; }
.gear { transition: rotate 0.4s var(--spring) 50ms; }
.gear:active { rotate: 40deg; transition: rotate 0.1s ease-out; }

/* While a tap is on its way, the button stays pressed, and pulses gently if the wait goes on.
   A day chip shows where it's going, half filled or half emptied, until the answer settles it. */
:is(.cta, .switch).htmx-request { scale: 0.96; animation: wait 1.2s ease-in-out 0.4s infinite; }
.chip.htmx-request { border-color: var(--person); background: color-mix(in srgb, var(--person) 45%, transparent); color: var(--text); animation: wait 1.2s ease-in-out 0.4s infinite; }

/* What changes moves: arcs and the tip spring to their new place, or glide when they land on empty or full. */
.ring-fill, .ring-reach, .avatar-arc { transition: stroke-dashoffset 0.7s var(--spring), opacity 0.3s ease-out; }
.ring-flow { transition: opacity 0.3s ease-out; }
.tip-turn { transition: transform 0.7s var(--spring), opacity 0.3s ease-out; }
.ring:is(.empty, .earned) :is(.ring-fill, .ring-reach, .tip-turn), .avatar:is(.empty, .full) .avatar-arc { transition-timing-function: var(--glide), ease-out; }
.star { transition: fill 0.5s ease-out, stroke 0.5s ease-out; }
.dot { transition: background-color 0.4s ease-out; }

/* A friend who has just logged: as their workout lands on your home, their card gives one springy bounce. */
.card.fresh { animation: cheer 0.6s ease-in-out; }

/* In the cheer after logging, the ring and your card grow from where home had them, once they've risen in. */
.grows :is(.ring-fill, .ring-reach, .avatar-arc) { animation: grow-arc 0.7s var(--spring) 0.5s both; }
.grows .tip-turn { animation: grow-turn 0.7s var(--spring) 0.5s both; }
.grows:is(.earned, .full) :is(.ring-fill, .ring-reach, .avatar-arc, .tip-turn) { animation-timing-function: var(--glide); }

/* A dialog rises and fades in over a backdrop that fades too. Closing stays instant: iPhones
   can't keep a closing popover on top while it fades. */
.dialog { transition: opacity 0.2s ease-out, translate 0.3s var(--spring), scale 0.3s var(--spring); }
.dialog::backdrop { transition: background-color 0.2s ease-out; }
@starting-style {
  .dialog:popover-open { opacity: 0; translate: 0 12px; scale: 0.96; }
  .dialog:popover-open::backdrop { background-color: transparent; }
}

/* Screens cross-fade into each other, where the browser can (Safari 18.2 and on). */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0.2s; }

@keyframes flow { to { stroke-dashoffset: -12.17; } }
@keyframes pulse { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } }
@keyframes glow { 0%, 100% { opacity: 0.5; transform: scale(0.88); } 50% { opacity: 1; transform: scale(1.1); } }
@keyframes hop { 0% { opacity: 0; transform: translateY(14px) scale(0.92); } 60% { opacity: 1; transform: translateY(-6px); } 100% { transform: none; } }
@keyframes rise { from { opacity: 0; transform: translateY(10px); } }
@keyframes pop { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }
@keyframes twinkle { 0%, 100% { opacity: 0.15; } 50% { opacity: 1; } }
@keyframes wait { 50% { opacity: 0.6; } }
@keyframes cheer { 30% { scale: 1.07; } 60% { scale: 0.98; } }
@keyframes grow-arc { from { stroke-dashoffset: var(--from-offset); } }
@keyframes grow-turn { from { transform: rotate(var(--from-turn)); } }
/* With Reduce Motion on, nothing moves: no animation or transition anywhere, and a press only dims. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after, ::backdrop { animation: none !important; transition: none !important; }
  :is(a, button).cta:active, :is(a, button).switch:active, .reminder:active, .chip:active, .swatch:not(.taken):active, .menu > a:active, :is(.cta, .switch).htmx-request { scale: none; opacity: 0.7; }
  .gear:active { rotate: none; opacity: 0.6; }
  .tour { scroll-behavior: auto; }
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none; }
}
