/* =========================================================================
   Destiny — application styles

   Follows the Destiny Design System. Warm greige paper, two warm darks, no
   accent hue. Flat and still: no gradients, no drop shadows, no looping or
   ambient motion. Every padding, margin, gap and radius below is a token from
   tokens.css, so every one of them is divisible by four.
   ========================================================================= */

/* ------------------------------------------------------------------ reset */

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

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
}

/* Components set their own display, which quietly beats the hidden
   attribute. Nothing in this app hides an element any other way. */
[hidden] {
  display: none !important;
}

body {
  min-height: 100dvh;
  background: var(--paper);
  color: var(--coffee);
  font-family: var(--font-ui);
  font-size: var(--text-base);
  font-weight: 400;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img,
svg {
  display: block;
  max-width: 100%;
}

button {
  font: inherit;
  color: inherit;
  border: 0;
  background: none;
  cursor: pointer;
}

:focus-visible {
  outline: 2px solid var(--ink-soft);
  outline-offset: var(--space-1);
  border-radius: var(--radius-xs);
}

::selection {
  background: var(--paper-sunken);
  color: var(--ink);
}

.u-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------- texture */

/* The ASCII cup. Fixed behind everything, decorative, and inert: it takes no
   pointer events, and no rule in this file responds to :hover, :focus, scroll
   or any other thing a seeker can do. It moves on its own or not at all. */
.ascii {
  position: fixed;
  inset: 0;
  z-index: 0;
  display: grid;
  /* Bottom, not centre: the saucer sits on the edge of the screen like a cup
     on a table, and the steam is what rises behind the reading. Centred, the
     cup landed under the headline and the upload tile, and a raised opaque
     surface covered exactly the half that made it read as a cup. */
  place-items: end center;
  overflow: hidden;
  pointer-events: none;
  /* layout and paint, not strict: strict adds size containment, and this box
     is sized by its insets, so that is a rule with nothing to do and a subtle
     bug waiting in it. */
  contain: layout paint;
}

/* The grid is a fixed 54 x 36 characters and CSS scales the type, which is how
   the cup fits a phone and a laptop with no resize handler in the script. */
.ascii__art {
  margin: 0;
  font-family: var(--font-mono);
  font-size: clamp(9px, 2.95vw, 18px);
  line-height: 1.05;
  letter-spacing: 0.16em;
  white-space: pre;
  /* A long, shallow breath on the whole grid. The cup itself never deforms:
     a wobbling cup reads as a rendering fault rather than as atmosphere. */
  animation: ascii-breathe 34s var(--ease-in-out) infinite alternate;
}

@keyframes ascii-breathe {
  from { transform: translate3d(0, 0.6%, 0) scale(1); }
  to   { transform: translate3d(0, -0.6%, 0) scale(1.03); }
}

/* Eight levels, sparse to dense, with 7 reserved for the cup's own walls.
   Here rather than inline on every span so the dark theme can reach them.

   The strength climbs with the level rather than sitting on one opacity for
   the whole grid, which is what makes this read as a cup: the walls hold the
   shape at full weight while the grounds and the steam stay quiet enough to
   set a fortune over. --ascii-opacity scales the lot.

   Each level declares a flat colour first, so a browser without color-mix
   gets the field at full strength rather than no field at all. */
.ascii__art span { color: var(--ascii-2); }

.ascii__art .a0 { color: color-mix(in srgb, var(--ascii-3) calc(var(--ascii-opacity) * 60%), transparent); }
.ascii__art .a1 { color: color-mix(in srgb, var(--ascii-3) calc(var(--ascii-opacity) * 75%), transparent); }
.ascii__art .a2 { color: color-mix(in srgb, var(--ascii-2) calc(var(--ascii-opacity) * 90%), transparent); }
.ascii__art .a3 { color: color-mix(in srgb, var(--ascii-2) calc(var(--ascii-opacity) * 105%), transparent); }
.ascii__art .a4 { color: color-mix(in srgb, var(--ascii-4) calc(var(--ascii-opacity) * 120%), transparent); }
.ascii__art .a5 { color: color-mix(in srgb, var(--ascii-4) calc(var(--ascii-opacity) * 140%), transparent); }
.ascii__art .a6 { color: color-mix(in srgb, var(--ascii-1) calc(var(--ascii-opacity) * 160%), transparent); }
.ascii__art .a7 { color: color-mix(in srgb, var(--ascii-1) calc(var(--ascii-opacity) * 210%), transparent); }

/* Off by default, per the system. Set data-grain="on" on <html> to enable a
   single static paper tooth. It is never animated. */
.grain {
  display: none;
}

[data-grain='on'] .grain {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 120px;
  mix-blend-mode: multiply;
  opacity: 0.04;
}

/* ------------------------------------------------------------ screen shell */

.app {
  position: relative;
  z-index: 2;
}

.screen {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  min-height: 100dvh;
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding: var(--space-6);
  padding-top: calc(env(safe-area-inset-top, 0px) + var(--space-6));
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--space-6));
}

/* Every screen but the reveal is a fixed frame: the action stays in view and
   the middle scrolls if a small phone runs out of room. */
.screen--fixed {
  height: 100dvh;
}

.screen__body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-6);
}

.screen--fixed .screen__body {
  overflow-y: auto;
  overscroll-behavior: contain;
}

.screen__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* Screens answer the action with a short fade and lift. Nothing loops. */
.screen.is-entering {
  animation: screen-in var(--dur-slow) var(--ease-out) both;
}

.screen.is-leaving {
  animation: screen-out var(--dur-base) var(--ease-in-out) both;
}

@keyframes screen-in {
  from { opacity: 0; transform: translate3d(0, var(--space-2), 0); }
  to { opacity: 1; transform: none; }
}

@keyframes screen-out {
  from { opacity: 1; transform: none; }
  to { opacity: 0; }
}

/* ------------------------------------------------------------------- type */

/* Titles are set in the grotesque at semibold. Only two things are titles: the
   wordmark and the omen. Everything the Fortune Teller actually says stays in
   the antique serif below, because that is a voice and not a label. */
.display {
  font-family: var(--font-title);
  font-size: var(--text-2xl);
  font-weight: var(--weight-title);
  letter-spacing: -0.015em; /* the grotesque sets loose at display size */
  line-height: 1.1;
  color: var(--ink);
  text-wrap: balance;
}

/* Nothing in the app puts an <em> in a title today, and Bricolage Grotesque
   ships no true italic, so this would synthesise a slant if anything ever did.
   Kept as the deliberate answer to that rather than removed and rediscovered. */
.display em {
  font-style: italic;
}

/* Mono is for short meta labels only: never body, headings or buttons. */
.meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.5;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-faint);
}

.lede {
  font-size: var(--text-md);
  line-height: 1.5;
  color: var(--coffee);
  max-width: 34ch;
}

/* -------------------------------------------------------------------- bar */

.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: var(--tap-min);
}

.back {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  margin-left: calc(var(--space-3) * -1);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-soft);
  border-radius: var(--radius-sm);
  transition: color var(--dur-fast) var(--ease-out);
}

/* ---------------------------------------------------------------- the cup */


/* --------------------------------------------------------------- buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-md);
  font-family: var(--font-ui);
  font-size: var(--text-base);
  font-weight: 500;
  text-align: center;
  transition: opacity var(--dur-fast) var(--ease-out);
}

.btn--primary {
  background: var(--ink);
  color: var(--on-dark);
}

.btn--secondary {
  border: 1px solid var(--ink);
  color: var(--ink);
}

.btn:disabled {
  opacity: 0.4;
  pointer-events: none;
}

.link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  font-size: var(--text-sm);
  transition: opacity var(--dur-fast) var(--ease-out);
}

/* ---------------------------------------------------------------- surfaces */

.card {
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--paper-raised);
}

.divider {
  height: 1px;
  border: 0;
  background: var(--line);
}

.file-input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* --------------------------------------------------- the reading moment */

/* The one orchestrated moment in the app: a quiet wait, then the reveal
   unfolds line by line. The cup itself does not move. */
.wait {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  flex: 1;
  min-height: 0;
  justify-content: center;
}

.wait__line {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--text-xl);
  font-weight: 400;
  line-height: 1.35;
  color: var(--coffee);
  min-height: 2.7em;
  max-width: 20ch;
}

.wait__line.is-in {
  animation: line-in var(--dur-slow) var(--ease-out) both;
}

.wait__line.is-out {
  animation: line-out var(--dur-base) var(--ease-in-out) both;
}

@keyframes line-in {
  from { opacity: 0; transform: translate3d(0, var(--space-2), 0); }
  to { opacity: 1; transform: none; }
}

@keyframes line-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

/* A single finite sweep, not a loop: it eases toward the edge and waits there
   for the reading rather than pretending to know how long this takes. */
.wait__track {
  height: 1px;
  background: var(--line);
}

.wait__fill {
  height: 1px;
  width: 0;
  background: var(--ink);
  transition: width 14s var(--ease-out);
}

.wait__fill.is-running {
  width: 92%;
}

.wait__fill.is-done {
  width: 100%;
  transition-duration: var(--dur-fast);
}

/* ---------------------------------------------------------------- reveal */

.reveal {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  padding-block: var(--space-8) var(--space-12);
}

.reveal__omen {
  font-family: var(--font-title);
  font-size: var(--text-2xl);
  font-weight: var(--weight-title);
  letter-spacing: -0.015em;
  line-height: 1.1;
  color: var(--ink);
  text-wrap: balance;
}

.stanzas {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  position: relative;
}

/* The one permitted ink blot: one, static, behind the reading. */
.stanza {
  position: relative;
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--text-md);
  font-weight: 400;
  line-height: 1.55;
  color: var(--coffee);
  text-wrap: pretty;
}

.closing {
  padding-top: var(--space-8);
  border-top: 1px solid var(--line);
}

.closing__line {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 400;
  line-height: 1.2;
  color: var(--ink);
  text-wrap: balance;
}

.reveal__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* Staged reveal: each block arrives on --dur-slow with --ease-out. */
.stage {
  animation: stage-in var(--dur-slow) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * 120ms);
}

@keyframes stage-in {
  from { opacity: 0; transform: translate3d(0, var(--space-3), 0); }
  to { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------ empty state */

.empty {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.empty__note {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--text-xl);
  line-height: 1.4;
  color: var(--coffee);
  max-width: 28ch;
}

.empty__hint {
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--paper-raised);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--ink-soft);
}

/* ----------------------------------------------------------- share sheet */

.sheet {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.sheet__scrim {
  position: absolute;
  inset: 0;
  background: var(--ink);
  opacity: 0.32;
  animation: scrim-in var(--dur-base) var(--ease-out) both;
}

@keyframes scrim-in {
  from { opacity: 0; }
  to { opacity: 0.32; }
}

.sheet__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-height: 92dvh;
  overflow-y: auto;
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding: var(--space-6);
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--space-6));
  background: var(--paper-raised);
  border-top: 1px solid var(--line);
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  animation: sheet-rise var(--dur-slow) var(--ease-out) both;
}

@keyframes sheet-rise {
  from { transform: translate3d(0, 100%, 0); }
  to { transform: none; }
}

.sheet__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

.sheet__card {
  width: 100%;
  max-width: 280px;
  aspect-ratio: 4 / 5;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--paper-sunken);
}

.sheet__card img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.sheet__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.sheet__status {
  min-height: 1.5em;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

/* ------------------------------------------------------------------ chip */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--paper-raised);
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--ink-soft);
  transition: opacity var(--dur-fast) var(--ease-out);
}

.chip[aria-pressed='true'],
.chip[aria-checked='true'] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* ------------------------------------------------------------- icon button */

/* An icon alone never names a control: every one of these carries an
   aria-label, and the glyph inside is aria-hidden. See scripts/icons.js. */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  margin-right: calc(var(--space-2) * -1);
  border-radius: var(--radius-pill);
  color: var(--ink-soft);
  transition: color var(--dur-fast) var(--ease-out);
}

.icon {
  flex: none;
}

.bar--end {
  justify-content: flex-end;
  gap: var(--space-2);
}

/* Two options, sitting inboard of the settings icon so that icon keeps the
   corner it has always had. Each is a chip in every respect that matters, so
   the two selectable controls in the app teach one meaning of "chosen": the
   filled one. Mono, because EN and TR are labels rather than words. */
.lang__opt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-min);
  min-height: var(--tap-min);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--paper-raised);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.07em;
  color: var(--ink-soft);
  transition: opacity var(--dur-fast) var(--ease-out);
}

.lang__opt[aria-checked='true'] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.lang {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

/* ------------------------------------------------------------- main screen */

/* The wireframe centres the opening screen. The system left-aligns by default,
   so the centring is opt-in per element rather than a change to the defaults. */
.main {
  align-items: center;
  text-align: center;
}

/* Empty, the invitation sits in the middle. Once there are photographs the
   column rises to the top and the bar holds the floor, which is how every
   app with a composer in it behaves. */
.main.is-filled {
  justify-content: flex-start;
}

.display--center,
.lede--center {
  text-align: center;
  margin-inline: auto;
}

/* ------------------------------------------------------------------ tiles */

.add {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}

.tile {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  border: 1px dashed var(--line);
  border-radius: var(--radius-lg);
  background: var(--paper-raised);
  color: var(--ink-soft);
  transition: border-color var(--dur-fast) var(--ease-out);
}

.tile--add {
  width: min(44vw, 160px);
}

/* The fifth slot, once there are photographs to sit beside. */
.tile--more {
  width: 100%;
  border-radius: var(--radius-md);
}

.tile__label {
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

/* ------------------------------------------------------------ photo squares */

/* One cup, up to four photographs of it. Four columns so the row never
   reflows as they are added, and the squares simply get their own cell. */
.photos {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

.photo {
  position: relative;
  aspect-ratio: 1;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--paper-sunken);
  overflow: hidden;
}

.photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.photo__drop {
  position: absolute;
  top: var(--space-1);
  right: var(--space-1);
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--space-6);
  height: var(--space-6);
  border-radius: var(--radius-pill);
  background: var(--ink);
  color: var(--on-dark);
}

/* The visible mark is 24px so it does not swallow the photograph; the thing
   your thumb actually hits is the full 44. */
.photo__drop::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--tap-min);
  height: var(--tap-min);
  transform: translate(-50%, -50%);
}

.photos__hint {
  margin-top: var(--space-3);
}

/* -------------------------------------------------------------- the compose */

.compose {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* One row that runs off the edge, rather than two that push the bar around.
   It bleeds to the screen edges so the last tag is visibly cut, which is the
   only affordance a scrolling row gets. */
.tags {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--space-2);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  margin-inline: calc(var(--space-6) * -1);
  padding-inline: var(--space-6);
  /* overflow-x also clips the y axis, and the focus ring lives out there. */
  margin-block: calc(var(--space-2) * -1);
  padding-block: var(--space-2);
}

.tags::-webkit-scrollbar {
  display: none;
}

.tags .chip {
  flex: none;
}

/* The chat bar: one round surface holding the field and its send. Pill radius
   and 64px of height, so it reads as a thing you talk into rather than a form
   field with a button parked next to it. */
.prompt {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--space-16);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--paper-raised);
  transition: border-color var(--dur-fast) var(--ease-out);
}

.prompt:focus-within {
  border-color: var(--ink-soft);
}

.prompt__field {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--tap-min);
  border: 0;
  background: none;
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--ink);
}

.prompt__field::placeholder {
  color: var(--ink-faint);
}

/* The bar itself carries the focus ring, so the field does not draw a second. */
.prompt__field:focus {
  outline: none;
}

/* Pill, to sit inside a pill. */
.prompt__send {
  flex: none;
  border-radius: var(--radius-pill);
  padding-inline: var(--space-3);
}

/* ---------------------------------------------------------------- settings */

.setting {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

.setting__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.setting__label {
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--ink);
}

.setting__note {
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 34ch;
}

/* ----------------------------------------------------------------- hover */

/* Only where there is a real pointer. On touch, :hover latches onto the last
   thing tapped, which left the next screen's button sitting dimmed. */
@media (hover: hover) {
  .back:hover {
    color: var(--ink);
  }

  .btn:hover {
    opacity: 0.82;
  }

  .link:hover,
  .chip:hover {
    opacity: 0.72;
  }

  .icon-btn:hover {
    color: var(--ink);
  }

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

/* --------------------------------------------------------------- desktop */

@media (min-width: 640px) {
  .screen {
    gap: var(--space-12);
    padding: var(--space-8);
    padding-top: calc(env(safe-area-inset-top, 0px) + var(--space-8));
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--space-8));
  }

  .screen__body {
    gap: var(--space-8);
  }

  .display {
    font-size: var(--text-3xl);
  }

  .reveal__omen,
  .closing__line {
    font-size: var(--text-2xl);
  }

  .stanza {
    font-size: var(--text-lg);
  }

  .reveal {
    gap: var(--space-12);
    padding-block: var(--space-12) var(--space-16);
  }

  .tags {
    margin-inline: calc(var(--space-8) * -1);
    padding-inline: var(--space-8);
  }

  .tile--add {
    width: 180px;
  }

  .sheet {
    justify-content: center;
    align-items: center;
    padding: var(--space-6);
  }

  .sheet__panel {
    max-width: 420px;
    border: 1px solid var(--line);
    border-radius: var(--radius-xl);
    animation-name: sheet-pop;
  }

  @keyframes sheet-pop {
    from { opacity: 0; transform: translate3d(0, var(--space-4), 0); }
    to { opacity: 1; transform: none; }
  }
}

/* --------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }

  .stage,
  .screen.is-entering,
  .screen.is-leaving {
    animation: none;
    opacity: 1;
    transform: none;
  }

  /* The cup stays, it stops moving. The blanket rule above would otherwise
     collapse the breath to 0.01ms and leave the grid snapped to the end of its
     keyframe; the script separately draws one frame and never asks for
     another, so the grounds and the steam hold still too. */
  .ascii__art {
    animation: none !important;
    transform: none;
  }
}
