/* ==========================================================================
   TPM Labs — App Launcher
   apps.theomerino.com
   Approved visual state. Tokens below are the design's source of truth.
   ========================================================================== */

:root {
  /* Neutral surface + text ladder (dark is the default appearance) */
  --bg: #07090b;
  --t1: #f4f7f8;
  --t2: rgba(244, 247, 248, .62);
  --t3: rgba(244, 247, 248, .5);
  --t4: rgba(244, 247, 248, .45);
  --t5: rgba(244, 247, 248, .34);
  --hair: rgba(255, 255, 255, .1);

  /* Liquid-glass recipe: fill + hairline border + blur + paired highlight/shadow */
  --glass: rgba(255, 255, 255, .06);
  --glassr: rgba(255, 255, 255, .09);
  --gedge: rgba(255, 255, 255, .12);
  --gedger: rgba(255, 255, 255, .16);
  --gtop: inset 0 1px 0 rgba(255, 255, 255, .2);
  --gtopr: inset 0 1px 0 rgba(255, 255, 255, .28);
  --gblur: 24px;
  --sh: 0 10px 30px rgba(0, 0, 0, .32);
  --shr: 0 18px 44px rgba(0, 0, 0, .42);

  --btn: rgba(255, 255, 255, .9);
  --btnfg: #0a0f12;
  --lum: rgba(255, 255, 255, .045);
  /* --bg at zero alpha. Fading to bare `transparent` interpolates through
     transparent BLACK and greys the ramp; fading to the same hue does not. */
  --bg-t: rgba(7, 9, 11, 0);

  /* Layout — mobile first (<= 759px) */
  --pad: 22px;
  --maxW: 100%;
  --navH: 46px;
  --wm: 16px;

  /* Top-edge geometry. The safe area is handled in exactly ONE place — the
     .main rule below — by adding env(safe-area-inset-top) once. These two are
     the designed distances, and neither may include the safe area itself:
       --navTop  how far the nav floats BELOW the safe area
       --navGap  the gap between the nav's bottom edge and the headline
     Because .nav is offset by the safe area and .main clears it by the same
     amount, the inset cancels between them and --navGap is what you actually
     see on every device — 32px on a Dynamic Island iPhone, a notched iPhone,
     and a desktop browser alike. Do not add env() to either of these. */
  --navTop: 12px;
  --navGap: 32px;

  /* Horizontal page edges. --pad is the DESIGNED inset; --padL/--padR add
     whatever the hardware demands on top of it.
     In portrait and on every desktop and tablet the insets resolve to 0px, so
     --padL and --padR are exactly --pad and nothing moves. They only grow in
     landscape on a notched or Dynamic Island iPhone, where the notch eats into
     one side and content would otherwise sit underneath it.
     Additive, never substitutive — the same rule the top edge follows — so the
     designed padding is never traded away for the inset. Use these for anything
     that touches the page edge; never add env() to --pad itself, or it would be
     counted twice everywhere these are used. */
  --padL: calc(var(--pad) + env(safe-area-inset-left, 0px));
  --padR: calc(var(--pad) + env(safe-area-inset-right, 0px));

  --dir: column;
  --cardMinH: auto;
  --cardR: 24px;

  --panelW: auto;
  --panelOrder: 2;
  --panelPad: 16px;
  --pbt: 1px;
  --pbr: 0px;
  --panelJustify: flex-start;

  --artFlex: none;
  --artH: 264px;

  --eyebrow: 11px;
  --title: 23px;
  --appName: 19px;
  --appLH: 1.15;
  --tag: 12.5px;
  --icon: 48px;
  --iconR: 14px;
  --iconMB: 0px;
  --iconDir: row;
  --iconAlign: center;
  --iconGap: 12px;
  --nameMax: 300px;

  --openH: 50px;
  --openW: 100%;
  --openPad: 0px;
  --openFS: 16px;
  --actionDir: column;
  --actionGap: 12px;
  --actionAlign: stretch;
  --betaJustify: center;

  --featured: none;
  --railCols: 2;

  --ease: cubic-bezier(.22, 1, .36, 1);
}

/* iPad and up — the split bay turns side-by-side */
@media (min-width: 760px) {
  :root {
    --pad: 32px;
    --navH: 52px;
    --wm: 17px;
    --dir: row;
    --cardMinH: 420px;
    --panelW: 344px;
    --panelOrder: 0;
    --panelPad: 32px;
    --pbt: 0px;
    --pbr: 1px;
    --panelJustify: center;
    --artFlex: 1;
    --artH: auto;
    --eyebrow: 11.5px;
    --title: 27px;
    --appName: 40px;
    --appLH: 1.04;
    --tag: 15.5px;
    --icon: 66px;
    --iconR: 18px;
    --iconMB: 24px;
    --iconDir: column;
    --iconAlign: flex-start;
    --iconGap: 0px;
    --nameMax: none;
    --openW: 100%;
    --openPad: 0px;
    --openFS: 16.5px;
    --openH: 52px;
    --featured: block;
    --railCols: 3;
  }
}

/* Desktop */
@media (min-width: 1180px) {
  :root {
    --pad: 44px;
    --navH: 56px;
    --wm: 18px;
    --maxW: 1344px;
    --cardMinH: 560px;
    --panelW: 452px;
    --panelPad: 52px;
    --eyebrow: 12px;
    --title: 29px;
    --appName: 50px;
    --tag: 17px;
    --icon: 76px;
    --iconR: 20px;
    --iconMB: 30px;
    --openH: 54px;
    --openW: auto;
    --openPad: 38px;
    --openFS: 17px;
    --actionDir: row;
    --actionGap: 14px;
    --actionAlign: center;
    --betaJustify: flex-start;
    --railCols: 4;
  }
}

/* Light appearance — neutral, same material quality, not an inversion */
@media (prefers-color-scheme: light) {
  :root {
    --bg: #f4f5f7;
    --t1: #0d1114;
    --t2: rgba(13, 17, 20, .62);
    --t3: rgba(13, 17, 20, .5);
    --t4: rgba(13, 17, 20, .45);
    --t5: rgba(13, 17, 20, .34);
    --hair: rgba(0, 0, 0, .1);
    --glass: rgba(255, 255, 255, .66);
    --glassr: rgba(255, 255, 255, .72);
    --gedge: rgba(0, 0, 0, .09);
    --gedger: rgba(0, 0, 0, .12);
    --gtop: inset 0 1px 0 rgba(255, 255, 255, .9);
    --gtopr: inset 0 1px 0 #fff;
    --btn: #0d1114;
    --btnfg: #f4f7f8;
    --lum: rgba(0, 0, 0, .03);
    --bg-t: rgba(244, 245, 247, 0);
    --sh: 0 10px 30px rgba(0, 0, 0, .1);
    --shr: 0 18px 44px rgba(0, 0, 0, .14);
  }
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

html, body {
  margin: 0;
  padding: 0;
  background-color: var(--bg);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* THE SAFE-AREA SEAM — the real fix, in two parts.
   ------------------------------------------------------------------------
   The ambient glow used to live on `.ambient`, a fixed CHILD div, with its
   gradient centred `at 50% 0%` — brightest exactly on the document's first
   row. The browser, meanwhile, paints the status-bar strip from
   <meta name="theme-color">, which is FLAT var(--bg) with no glow. So the
   strip was #07090b and the document's first row was #07090b + 4.5% white
   (~#121416): a full-width step at precisely the safe-area boundary. That is
   the line. It never showed on desktop because there is no strip there, and
   removing .app's background did nothing because that background was
   identical to body's — it was never the layer that differed.

   1. The glow moves to the ROOT element. A root background propagates to the
      canvas, and the canvas is the one layer specified to paint the entire
      viewport — including the region outside the document box, behind the
      status bar. So wherever the page itself paints through the safe area,
      the glow is now continuous across it instead of starting at its edge.

   2. The glow's origin moves from `0%` to `6%`, so the document's first row
      is exactly var(--bg) — which is exactly what theme-color already is.
      Wherever the browser paints its own flat strip instead of letting the
      page through (Chrome on iOS, Add to Home Screen), the two sides of the
      boundary are now the same colour by construction.

   Either mechanism alone closes one case; together they close both, without
   an overlay, a mask, or any change to spacing or safe-area handling. */
html {
  background-image:
    /* LAYER 1 (on top) — flattens the very first rows to EXACTLY var(--bg),
       then releases smoothly over 160px. This is what makes the top edge
       equal the browser/PWA chrome colour BY CONSTRUCTION rather than by
       approximation: at y=0 the composite is var(--bg) with zero glow, and
       because the release is itself a gradient there is no second transition
       line — the wash simply arrives a little lower down. */
    linear-gradient(180deg, var(--bg) 0, var(--bg-t) 160px),
    /* LAYER 2 — the ambient wash, geometry unchanged from the original. */
    radial-gradient(80% 42% at 50% 0%, var(--lum), transparent 64%);
  background-attachment: fixed, fixed;
  background-repeat: no-repeat, no-repeat;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

.app {
  min-height: 100dvh;
  position: relative;
  overflow-x: hidden;
  /* NO background here on purpose.
     html/body already paint var(--bg), and Safari uses the BODY background for
     the area behind the status bar under `viewport-fit=cover`. Painting an
     identical opaque layer again on .app put a second background edge exactly
     at the top of the document flow — i.e. immediately below the safe-area
     region — and on a 3x screen that edge lands on a fractional device pixel
     and renders as a full-width hairline seam. One background, one edge, no
     seam. Safe-area handling is untouched: it lives in the env() maths on
     .nav, .main and .footer, none of which changed. */
}

/* Ambient neutral luminance. Stays behind content. */
/* The element is kept (index.html is unchanged) but paints nothing: the glow
   it used to carry now lives on the root canvas above, so there is exactly
   ONE layer painting the backdrop. Two painters was the whole problem. */
.ambient {
  display: none;
}

/* --------------------------------------------------------------------------
   Floating glass navigation pill
   -------------------------------------------------------------------------- */

.nav {
  position: fixed;
  top: calc(var(--navTop) + env(safe-area-inset-top, 0px));
  left: var(--padL);
  right: var(--padR);
  z-index: 20;
  display: flex;
  justify-content: center;
  pointer-events: none;
}

.nav__pill {
  width: 100%;
  max-width: var(--maxW);
  height: var(--navH);
  border-radius: 100px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 8px 0 18px;
  box-sizing: border-box;
  pointer-events: auto;
  background: var(--glassr);
  -webkit-backdrop-filter: blur(30px) saturate(190%);
  backdrop-filter: blur(30px) saturate(190%);
  border: 1px solid var(--gedger);
  box-shadow: var(--gtopr), 0 12px 32px rgba(0, 0, 0, .4);
}

.wordmark {
  font-weight: 590;
  font-size: var(--wm);
  line-height: 1;
  letter-spacing: -.012em;
  color: var(--t1);
}

.wordmark__labs {
  font-weight: 400;
  color: var(--t2);
}

.count {
  height: 30px;
  padding: 0 13px;
  border-radius: 100px;
  display: flex;
  align-items: center;
  background: var(--glass);
  font: 500 10px ui-monospace, "SF Mono", Menlo, monospace;
  letter-spacing: .13em;
  color: var(--t3);
}

.nav__menu {
  flex: none;
  width: 34px;
  height: 34px;
  border: none;
  border-radius: 100px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: var(--glass);
  cursor: pointer;
  transition: opacity .2s ease, transform .2s var(--ease);
  outline-offset: 4px;
}

.nav__menu:hover {
  opacity: .9;
}

.nav__menu:active {
  transform: scale(.985);
}

.nav__menu:focus-visible {
  outline: 2px solid var(--t1);
}

.nav__menu-line {
  width: 14px;
  height: 2px;
  border-radius: 2px;
  background: var(--t2);
}

/* --------------------------------------------------------------------------
   Page frame
   -------------------------------------------------------------------------- */

.main {
  position: relative;
  z-index: 1;
  max-width: var(--maxW);
  margin: 0 auto;
  /* Top clearance, spelled out so it cannot drift:
       safe area  (once, and only here)
     + --navTop   the nav's own float below that safe area
     + --navH     the nav pill itself
     + --navGap   the designed breathing room under it
     The first three reproduce exactly where the nav's bottom edge is; only
     --navGap is a design decision. Additive with env(), never substitutive, so
     on a browser tab (inset 0) the spacing is unchanged and on a Dynamic
     Island or notched iPhone the whole stack simply starts lower.
     The headline leads the header, so it needs geometric clearance rather than
     the large buffer the dim eyebrow used to need. */
  padding: calc(env(safe-area-inset-top, 0px) + var(--navTop) + var(--navH) + var(--navGap))
           var(--padR)
           28px
           var(--padL);   /* was 64px + safe-area: the footer is in flow now
                             and carries the bottom inset itself */
  box-sizing: border-box;
  animation: tpm-fade .42s var(--ease) both;
}

/* Persistent, fixed to the viewport so it stays visible on iPhone at all times. */
.footer {
  /* Normal document flow (was position: fixed). Fixed meant it rode over card
     artwork the whole way down the page; it now appears once, at the natural
     bottom. The safe-area inset moves from `bottom:` to padding-bottom so the
     home indicator still cannot clip it. */
  margin: 0;
  padding: 0 var(--padR) calc(18px + env(safe-area-inset-bottom, 0px)) var(--padL);
  text-align: center;
  pointer-events: none;
  font: 400 10.5px -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", system-ui, sans-serif;
  letter-spacing: .08em;
  color: var(--t5);
}

.header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  margin: 0 0 20px;
  padding: 0 2px;
}

.eyebrow {
  margin: 10px 0 0;
  font: 500 var(--eyebrow) -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", system-ui, sans-serif;
  letter-spacing: .16em;
  color: var(--t3);
}

.headline {
  font-weight: 400;
  font-size: var(--title);
  line-height: 1.26;
  letter-spacing: -.028em;
  color: var(--t1);
  margin: 0;
  text-wrap: pretty;
}

.featured-label {
  display: var(--featured);
  margin: 0;
  padding-bottom: 6px;
  font: 500 10.5px ui-monospace, "SF Mono", Menlo, monospace;
  letter-spacing: .15em;
  color: var(--t5);
}

/* --------------------------------------------------------------------------
   Featured app — the split bay
   -------------------------------------------------------------------------- */

.feature {
  position: relative;
}

/* Ambient wash derived from the app's own artwork. Local to this card only. */
.feature__glow {
  position: absolute;
  inset: -22px 8px -28px;
  border-radius: 80px;
  pointer-events: none;
  background:
    radial-gradient(58% 74% at 58% 42%, oklch(0.52 0.13 158 / .48), transparent 70%),
    radial-gradient(46% 60% at 84% 62%, oklch(0.46 0.16 22 / .28), transparent 72%);
  filter: blur(34px);
}

.card {
  position: relative;
  display: flex;
  flex-direction: var(--dir);
  min-height: var(--cardMinH);
  border-radius: var(--cardR);
  overflow: hidden;
  background: #0a0e11;
  text-decoration: none;
  color: inherit;
  box-shadow: var(--shr);
  transition: transform .42s var(--ease);
  outline-offset: 4px;
}

.card__panel {
  order: var(--panelOrder);
  flex: none;
  width: var(--panelW);
  padding: var(--panelPad);
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: var(--panelJustify);
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--gblur)) saturate(180%);
  backdrop-filter: blur(var(--gblur)) saturate(180%);
  border-top: var(--pbt) solid var(--gedge);
  border-right: var(--pbr) solid var(--gedge);
}

.lockup {
  display: flex;
  flex-direction: var(--iconDir);
  align-items: var(--iconAlign);
  gap: var(--iconGap);
}

.icon {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--icon);
  height: var(--icon);
  margin-bottom: var(--iconMB);
  border-radius: var(--iconR);
  overflow: hidden;
  /* Round & Round's own colour, scoped to its icon. Replaced by the real asset. */
  background:
    radial-gradient(120% 100% at 20% 10%, oklch(0.52 0.13 158 / .8), transparent 62%),
    radial-gradient(110% 100% at 90% 95%, oklch(0.46 0.16 22 / .6), transparent 64%),
    #0d1216;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), var(--sh);
}

.icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.icon__label {
  font: 500 7px ui-monospace, "SF Mono", Menlo, monospace;
  letter-spacing: .1em;
  color: rgba(244, 247, 248, .45);
}

.meta {
  min-width: 0;
  flex: 1 1 auto;
  width: 100%;
}

.app-name {
  font-weight: 590;
  font-size: var(--appName);
  line-height: var(--appLH);
  letter-spacing: -.034em;
  color: var(--t1);
  margin: 0;
}

.app-tagline {
  font-weight: 400;
  font-size: var(--tag);
  line-height: 1.42;
  color: var(--t2);
  margin: 6px 0 0;
  max-width: var(--nameMax);
  text-wrap: pretty;
}

.actions {
  display: flex;
  flex-direction: var(--actionDir);
  align-items: var(--actionAlign);
  gap: var(--actionGap);
  margin-top: 16px;
}

/* Open is the only interactive control in the card. */
.open {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--openW);
  height: var(--openH);
  padding: 0 var(--openPad);
  box-sizing: border-box;
  border-radius: 100px;
  background: var(--btn);
  color: var(--btnfg);
  text-decoration: none;
  font-weight: 590;
  font-size: var(--openFS);
  letter-spacing: -.012em;
  transition: opacity .2s ease, transform .2s var(--ease);
  outline-offset: 4px;
}

.open:hover {
  opacity: .9;
}

.open:active {
  transform: scale(.985);
}

.open:focus-visible {
  outline: 2px solid var(--t1);
}

/* Not-yet-launchable state. Same pill geometry as .open so the featured cards
   still line up, but the filled white/black button is replaced by the glass
   material already used by the status pill — clearly present, clearly not a
   control. launcher.js renders this as a <span>, so there is nothing to click
   or focus; pointer-events makes that visible as well as true. */
.soon {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--openW);
  height: var(--openH);
  padding: 0 var(--openPad);
  box-sizing: border-box;
  border-radius: 100px;
  background: var(--glass);
  border: 1px solid var(--gedge);
  color: var(--t3);
  font-weight: 500;
  font-size: var(--openFS);
  letter-spacing: -.012em;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

/* Desktop gives .open an intrinsic width (--openW: auto); the soon pill needs
   a little more room than "Open" to avoid reading as a cramped button. */
@media (min-width: 1180px) {
  .soon { padding: 0 30px; }
}

/* Nothing in this card responds to a pointer. */
.card--soon {
  cursor: default;
}

.status-row {
  display: flex;
  justify-content: var(--betaJustify);
}

.status {
  height: 28px;
  padding: 0 13px;
  border-radius: 100px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: var(--glass);
  border: 1px solid var(--gedge);
}

.status__dot {
  display: block;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--t3);
}

.status__label {
  font: 500 9.5px ui-monospace, "SF Mono", Menlo, monospace;
  letter-spacing: .13em;
  color: var(--t2);
}

/* Artwork — opaque layer, never covered by text at any width */
.card__art {
  order: 1;
  flex: var(--artFlex);
  height: var(--artH);
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(110% 90% at 8% 4%, oklch(0.52 0.13 158 / .55), transparent 58%),
    radial-gradient(90% 80% at 96% 10%, oklch(0.48 0.13 235 / .5), transparent 60%),
    radial-gradient(100% 100% at 58% 112%, oklch(0.46 0.16 22 / .42), transparent 62%),
    linear-gradient(180deg, #0d1216, #080b0e);
}

.card__art img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.art__stripes {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(135deg, rgba(255, 255, 255, .02) 0 2px, transparent 2px 10px);
}

.art__label {
  position: absolute;
  bottom: 13px;
  right: 15px;
  font: 500 9.5px ui-monospace, "SF Mono", Menlo, monospace;
  letter-spacing: .12em;
  color: rgba(244, 247, 248, .34);
}

/* --------------------------------------------------------------------------
   Secondary apps (only rendered once a second real app exists)
   -------------------------------------------------------------------------- */

.rail {
  margin-top: 34px;
}

.rail__label {
  margin: 0 0 14px;
  font: 500 var(--eyebrow) ui-monospace, "SF Mono", Menlo, monospace;
  letter-spacing: .16em;
  color: var(--t3);
}

.rail__grid {
  display: grid;
  grid-template-columns: repeat(var(--railCols), minmax(0, 1fr));
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* --------------------------------------------------------------------------
   Intentional empty capacity
   -------------------------------------------------------------------------- */

.berth {
  margin-top: 30px;
  display: flex;
  align-items: center;
  gap: 12px;
}

.berth__slot {
  flex: none;
  display: block;
  width: 36px;
  height: 36px;
  border-radius: 12px;
  border: 1px dashed var(--hair);
}

.berth__label {
  font-size: 12px;
  line-height: 1.4;
  color: var(--t4);
}

/* --------------------------------------------------------------------------
   Launch response — blurs the whole surface, then floats one card
   -------------------------------------------------------------------------- */

.launch {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0 var(--padR) calc(48px + env(safe-area-inset-bottom, 0px)) var(--padL);
  box-sizing: border-box;
  background: rgba(4, 6, 8, .55);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  animation: tpm-fade .3s var(--ease) both;
}

.launch[hidden] {
  display: none;
}

.launch__card {
  width: 100%;
  max-width: 420px;
  padding: 18px;
  box-sizing: border-box;
  border-radius: 24px;
  display: flex;
  align-items: center;
  gap: 14px;
  background: rgba(255, 255, 255, .09);
  -webkit-backdrop-filter: blur(30px) saturate(190%);
  backdrop-filter: blur(30px) saturate(190%);
  border: 1px solid rgba(255, 255, 255, .16);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .28), 0 18px 44px rgba(0, 0, 0, .42);
  animation: tpm-lift .42s var(--ease) both;
}

.icon--launch {
  width: 44px;
  height: 44px;
  margin-bottom: 0;
  border-radius: 13px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2);
}

.icon--launch .icon__label {
  font-size: 6.5px;
}

.launch__text {
  min-width: 0;
}

.launch__title {
  display: block;
  font-weight: 590;
  font-size: 15px;
  letter-spacing: -.02em;
  color: #f4f7f8;
}

.launch__route {
  display: block;
  margin-top: 3px;
  font: 400 11.5px ui-monospace, "SF Mono", Menlo, monospace;
  color: rgba(244, 247, 248, .5);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Motion
   -------------------------------------------------------------------------- */

@keyframes tpm-fade {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

@keyframes tpm-lift {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}
