/* Pitchear landing: one cinematic screen in the app's Neon palette. */
:root {
  --bg: #0a0a0f; --panel: #14141f; --text: #ededf5; --muted: #8a8aa3;
  --line: rgba(255, 255, 255, .10); --fill: rgba(255, 255, 255, .07);
  --accent: #ff2a4d; --violet: #7b2cff; --cyan: #00f0ff; --magenta: #ff2bd6; --green: #30e37a;
}
* { box-sizing: border-box; }
html { color-scheme: dark; overflow-x: hidden; background: var(--bg); }
/* clip, not hidden: hidden would make the body its own scroller and unstick the header. */
/* touch-action: no double-tap zoom on iOS; the page is not meant to zoom. */
body { margin: 0; background: var(--bg); color: var(--text); min-height: 100vh; max-width: 100%; overflow-x: clip; touch-action: pan-y;
  font: 300 18px/1.7 -apple-system, "SF Pro Text", Inter, system-ui, sans-serif; -webkit-font-smoothing: antialiased; }

/* The app's wave under frosted glass: blur 6, the ground at 14 %. */
#wave { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; filter: blur(6px); }
/* The landing keeps the wave far out of focus: colour and motion, no strands (Alex, 2026-09-30). */
#wave.deep { filter: blur(40px); }
.frost { position: fixed; inset: 0; z-index: 0; pointer-events: none; background: rgba(10, 10, 15, .14); }

/* A plain column: the hero fills the first screen by its own height, the
   header sticks (a grid row would pin it), the footer ends the page. */
.page { position: relative; z-index: 1; max-width: 1280px; margin: 0 auto; padding: 0 32px; }
/* The header stays while the page scrolls, on its own glass. */
/* Stretched to the window's edges (the margin reaches out of the page
   column, the padding brings the content back into line with it). */
header.top { position: sticky; top: 0; z-index: 3; height: 76px; margin: 0 calc(50% - 50vw); padding: 0 calc(50vw - 50%);
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: transparent; transition: background .3s ease; }
/* Nothing at rest; a light glass as soon as the page has moved (site.js). */
header.top.scrolled { background: rgba(10, 10, 15, .35); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); }
/* Back to the landing: the text pages on a phone only. */
.back { display: none; place-items: center; width: 44px; height: 44px; margin-left: -10px; border-radius: 50%; color: var(--text); }
.back svg { width: 26px; height: 26px; }
/* The Android notice: a glass pill in the middle of the screen, faded in
   and out, never in the way of a tap. */
.toast { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(.96); z-index: 20; padding: 14px 22px; border-radius: 999px;
  background: rgba(10, 10, 15, .72); border: 1px solid rgba(255, 255, 255, .14); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  color: var(--text); font-size: 17px; font-weight: 500; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity .25s ease, transform .25s ease; }
.toast.on { opacity: 1; transform: translate(-50%, -50%); }
/* Two quiet download buttons in the header, white in our style with no
   box: the Apple mark + iOS, the Play mark + Android (marks only on a
   phone); shown once the hero's badges have scrolled away. */
.get { display: flex; gap: 6px; opacity: 0; pointer-events: none; transition: opacity .4s ease; }
.get a { display: inline-flex; align-items: center; gap: 9px; height: 48px; padding: 0 16px; border-radius: 999px; color: var(--text); text-decoration: none; font-weight: 700; font-size: 18px; letter-spacing: -.2px; }
.get a:hover { background: var(--fill); }
.get svg { width: 26px; height: 26px; fill: currentColor; }
/* The Play triangle reads larger than the apple at the same box; it is drawn smaller. */
.get a:last-child svg { transform: scale(.82); }
header.top.scrolled .get { opacity: 1; pointer-events: auto; }
footer .get { display: none; }
.mark { position: relative; display: inline-flex; align-items: flex-start; height: 40px; text-decoration: none; transform-origin: center; }
.mark img { position: relative; height: 40px; width: auto; display: block; }
.mark .ear { margin-left: calc(-16 / 240 * 40px); }

/* Hero: words left, the floating phone right. */
.hero { position: relative; display: grid; grid-template-columns: 55fr 45fr; align-items: center; gap: 40px; padding-bottom: 40px; min-height: calc(100vh - 76px); min-height: calc(100dvh - 76px); scroll-snap-align: start; scroll-margin-top: 76px; }
.label { font-weight: 500; font-size: 12px; letter-spacing: .16em; text-transform: uppercase; color: var(--muted); margin: 0 0 18px; }
h1 { font-weight: 900; font-size: clamp(46px, 6.5vw, 96px); line-height: 1.02; letter-spacing: -.04em; margin: 0 0 40px; }
h1 span { display: block; opacity: 0; filter: blur(8px); transform: translateY(40px); animation: rise .9s cubic-bezier(.2, .7, .2, 1) forwards; }
h1 span:nth-child(2) { animation-delay: .1s; } h1 span:nth-child(3) { animation-delay: .2s; }
@keyframes rise { to { opacity: 1; filter: blur(0); transform: none; } }
.sub { color: var(--muted); font-size: 18px; line-height: 1.5; margin: -12px 0 36px; }
.sub.tagline, .stores.foot { display: none; }
.stores { display: flex; gap: 12px; flex-wrap: wrap; }
.store { display: inline-flex; align-items: center; gap: 10px; height: 64px; padding: 0 22px 0 16px; border-radius: 12px;
  background: #000; border: 0; color: #fff; text-decoration: none; transition: transform .25s, box-shadow .25s; }
.store:hover { transform: translateY(-2px); box-shadow: 0 10px 30px rgba(0, 0, 0, .5); }
.store svg { width: 32px; height: 32px; fill: #fff; }
.store small { display: block; font-size: 11px; line-height: 1; letter-spacing: .3px; text-transform: uppercase; opacity: .9; font-weight: 500; }
.store b { display: block; font-size: 23px; line-height: 1.1; font-weight: 600; letter-spacing: -.3px; }

/* A small chevron at the foot of the first screen, nodding: there is more below. */
.down { position: absolute; left: 50%; bottom: 20px; width: 40px; height: 40px; margin-left: -20px; display: grid; place-items: center; color: var(--muted); border-radius: 50%; }
.down svg { width: 24px; height: 24px; animation: nod 2s ease-in-out infinite; }
.down:hover { color: var(--text); }
@keyframes nod { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(6px); } }
/* Gone once the page has moved, or it would peep through the header's glass. */
header.top.scrolled + .hero .down { opacity: 0; transition: opacity .3s; }

/* The phone: tilted in perspective, floating, a glow in the app's colours behind it. */
.stage { position: relative; perspective: 1500px; display: grid; place-items: center end; }
/* Two layers of the same light: the steady one, and a wider one that is
   always breathing but invisible until the pointer is on the phone — so it
   fades in and out instead of starting and stopping mid-breath. Centred by
   margins, leaving transform to the animation alone. */
.glow, .glow::before { position: absolute; left: 50%; top: 50%; border-radius: 50%; pointer-events: none;
  background: radial-gradient(closest-side, rgba(123, 44, 255, .8), transparent 72%),
              radial-gradient(closest-side at 28% 72%, rgba(0, 240, 255, .55), transparent 70%),
              radial-gradient(closest-side at 72% 28%, rgba(255, 43, 214, .55), transparent 70%); }
.glow { width: 640px; height: 780px; margin: -390px 0 0 -320px; filter: blur(80px); opacity: .9; z-index: 0;
  transition: opacity .7s ease; }
.glow::before { content: ""; width: 820px; height: 1000px; margin: -500px 0 0 -410px; filter: blur(100px);
  opacity: 0; transition: opacity .8s ease; animation: breathe 5.2s ease-in-out infinite; will-change: transform, opacity; }
.stage:hover .glow { opacity: 1; }
.stage:hover .glow::before { opacity: .75; }
@keyframes breathe { 0%, 100% { transform: scale(.94); } 50% { transform: scale(1.06); } }
/* No aspect on the shell: the screen sets it from the recordings' own
   frame (1206 × 2622), so nothing is cut and no black band shows. */
.phone { position: relative; z-index: 1; width: 330px; border-radius: 44px; background: #000; border: 1px solid rgba(255, 255, 255, .18);
  /* The body's thickness: the phone is turned so its right side faces
     the viewer; seven one-pixel slices in the frame's greys draw it,
     following the tilt since shadows live in the element's own axes. */
  box-shadow: 1px 0 0 #2a2a34, 2px 0 0 #26262f, 3px 0 0 #22222b, 4px 0 0 #1e1e26, 5px 0 0 #1a1a22, 6px 0 0 #16161d, 7px 0 0 #121218,
    0 40px 120px rgba(0, 0, 0, .6), inset 0 0 0 8px #0a0a0f; padding: 8px; overflow: hidden;
  transform: rotateY(-14deg) rotateZ(3deg); opacity: 0; animation: enter 1.2s ease-out forwards, float 6s ease-in-out 1.2s infinite; }
@keyframes enter { from { transform: rotateY(-14deg) rotateZ(3deg) scale(.9); opacity: 0; } to { transform: rotateY(-14deg) rotateZ(3deg); opacity: 1; } }
@keyframes float { 0%, 100% { transform: rotateY(-14deg) rotateZ(3deg) translateY(0); } 50% { transform: rotateY(-14deg) rotateZ(3deg) translateY(-12px); } }
.screen { width: 100%; aspect-ratio: 1206 / 2622; border-radius: 36px; overflow: hidden; position: relative; background: #000;
  font: 400 12px/1.4 -apple-system, "SF Pro Text", system-ui, sans-serif; }
/* contain, never cover: nothing of a recording is cut; the sliver of
   letterbox is black on black. */
.screen img, .screen video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: #000; display: block; opacity: 0; transition: opacity 1.2s ease; }
.screen .on { opacity: 1; }
/* The skills: one screen that the scroll locks onto. Seven white keys
   drawn with lines alone, as the Pitch keyboard in the app, one per skill,
   no names, tilted away at the top; the pressed key lit in the app's
   answer green (a vertical fade, brightest low, as the app's fill). Above
   the keys, the pressed skill: a big dot icon, the title, two lines,
   arriving the way the title does. The footer sits at the foot. */
.skills { height: calc(100vh - 76px); height: calc(100dvh - 76px); scroll-snap-align: start; scroll-margin-top: 76px;
  display: flex; flex-direction: column; }
.abouts { position: relative; flex: 1; min-height: 0; display: grid; align-content: center; justify-items: center; text-align: center; padding: 16px 0; }
.about { grid-area: 1 / 1; max-width: 670px; visibility: hidden; }
.about.on { visibility: visible; }
.about svg { width: 144px; height: 144px; fill: currentColor; color: var(--text); margin: 0 auto 26px; display: block; }
.about.rounds svg { width: 164px; height: 164px; margin-bottom: 16px; }
.about h2 { margin: 0 0 14px; font-size: 53px; font-weight: 900; letter-spacing: -1.7px; line-height: 1.02; }
.about p { margin: 0; color: var(--muted); font-size: 22px; line-height: 1.5; }
.about.on svg, .about.on h2, .about.on p { animation: rise .7s cubic-bezier(.2, .7, .2, 1) both; }
.about.on h2 { animation-delay: .08s; } .about.on p { animation-delay: .16s; }
/* The icon plays once as its skill comes up, then rests (Alex, 2026-10-01).
   A "tap" is a dot popping in; the icon's own gesture per skill. */
.about svg circle, .about svg g { transform-box: fill-box; transform-origin: center; }
/* A tap comes from the viewer into the screen: the dot arrives large and
   faint, presses down past its size, settles. */
@keyframes tap { 0% { transform: scale(1.8); opacity: 0; } 60% { transform: scale(.9); opacity: 1; } 100% { transform: none; opacity: 1; } }
@keyframes retap { 0% { transform: none; } 40% { transform: scale(1.3); } 75% { transform: scale(.9); } 100% { transform: none; } }
.about.on.pitch circle { animation: tap .6s cubic-bezier(.2, .7, .2, 1) .3s both; }
/* Intervals: one, the other, then both together. */
.about.on.intervals circle:nth-child(1) { animation: tap .5s cubic-bezier(.2, .7, .2, 1) .3s both, retap .5s ease 1.5s; }
.about.on.intervals circle:nth-child(2) { animation: tap .5s cubic-bezier(.2, .7, .2, 1) .9s both, retap .5s ease 1.5s; }
.about.on.chords circle { animation: tap .5s cubic-bezier(.2, .7, .2, 1) .3s both; }
.about.on.progressions circle { animation: tap .5s cubic-bezier(.2, .7, .2, 1) both; }
.about.on.progressions circle:nth-child(1) { animation-delay: .3s; } .about.on.progressions circle:nth-child(2) { animation-delay: .65s; }
.about.on.progressions circle:nth-child(3) { animation-delay: 1s; } .about.on.progressions circle:nth-child(4) { animation-delay: 1.35s; }
/* Scales: the tonic walks the circle through every degree and comes home. */
@keyframes orbit { 0% { transform: translate(0, 0); } 14.3% { transform: translate(6.72px, 3.24px); } 28.6% { transform: translate(8.38px, 10.51px); }
  42.9% { transform: translate(3.73px, 16.35px); } 57.1% { transform: translate(-3.73px, 16.35px); } 71.4% { transform: translate(-8.38px, 10.51px); }
  85.7% { transform: translate(-6.72px, 3.24px); } 100% { transform: translate(0, 0); } }
.about.on.scales circle:nth-child(2) { animation: orbit 1.8s cubic-bezier(.45, 0, .55, 1) .3s both; }
/* Rounds: the icon rocks left, right, and settles. */
@keyframes fan { 0%, 100% { transform: rotate(0); } 30% { transform: rotate(-8deg); } 70% { transform: rotate(8deg); } }
.about.on.rounds g { animation: fan 1.6s cubic-bezier(.45, 0, .55, 1) .3s both; }
/* Tone Recall's wave is redrawn by site.js: the frequency rises to two
   periods and falls back, the ends pinned to the box. */
@media (prefers-reduced-motion: reduce) { .about svg * { animation: none !important; } }
/* The tilt shortens the keyboard on screen to about half its box, so the
   box reaches up behind the text (the negative margin) and the drawn keys
   end just under it. */
.keys { position: relative; flex: none; height: 86vh; height: 86dvh; margin-top: -56vh; display: grid; grid-template-columns: repeat(7, 1fr);
  transform: perspective(1100px) translateY(-110px) rotateX(60deg); transform-origin: 50% 100%;
  /* Everything on the keyboard (lines, plates, the lit key) fades in from
     nothing at the far edge: no top border anywhere. */
  -webkit-mask-image: linear-gradient(transparent, #000 40%); mask-image: linear-gradient(transparent, #000 40%); }
.key { position: relative; appearance: none; background: none; border: 0; padding: 0; margin: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; outline: none; }
/* A key's own silhouette: its column less the plates hanging over it
   (a plate is half a column wide, so it takes a quarter of each
   neighbour down to 60 %), as the app clips its fill. */
.key.r { clip-path: polygon(0 0, 75% 0, 75% 60%, 100% 60%, 100% 100%, 0 100%); }
.key.l { clip-path: polygon(25% 0, 100% 0, 100% 100%, 0 100%, 0 60%, 25% 60%); }
.key.l.r { clip-path: polygon(25% 0, 75% 0, 75% 60%, 100% 60%, 100% 100%, 0 100%, 0 60%, 25% 60%); }
/* Dividers in from nothing at the top and out to nothing at the bottom, as the app draws them. */
.key::before, .key:last-of-type::after { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 2px; pointer-events: none;
  background: linear-gradient(transparent, rgba(255, 255, 255, .26) 22%, rgba(255, 255, 255, .26) 72%, transparent); }
.key:last-of-type::after { left: auto; right: 0; }
@media (hover: hover) { .key:hover { background: linear-gradient(transparent, rgba(255, 255, 255, .06) 60%, transparent); } }
.key.on, .key.on:hover { background: linear-gradient(rgba(48, 227, 122, 0) 0, rgba(48, 227, 122, .95) 74%, rgba(48, 227, 122, 0) 100%); }
.key:focus-visible { outline: 2px solid var(--text); outline-offset: -2px; }
/* The sharps: five dark plates with a light rim over the gaps after C, D,
   F, G and A, half a column wide and 0.6 of the height, square shoulders,
   a rounded foot. Dead: nothing to press, nothing lights. */
.sharp { position: absolute; top: 0; height: 60%; width: calc(100% / 7 * .5); left: calc((var(--after) + 1) * 100% / 7 - 100% / 7 * .25);
  box-sizing: border-box; border-radius: 0 0 9px 9px; background: var(--panel); border: 2px solid rgba(255, 255, 255, .13); border-top: 0; pointer-events: none; z-index: 1;
  backface-visibility: hidden; }
.skills footer { flex: none; }
/* The two screens lock into place at every width. */
html { scroll-snap-type: y mandatory; }

/* Footer line: what App Review needs, quietly; at the very end of the page. */
footer { position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px; color: var(--muted); font-size: 14px; padding: 20px 0 18px; }
footer .links { display: flex; gap: 8px 24px; flex-wrap: wrap; justify-content: center; }
footer .copy { text-align: center; }
footer .copy a { color: var(--muted); text-decoration: underline; text-underline-offset: 3px; }
footer a { color: var(--muted); text-decoration: none; } footer a:hover { color: var(--text); }

/* Text pages: the app's tab head (title 56 / 900 against the right edge,
   a muted line under it), then the text in one glass panel, the wave
   centred behind. */
.legal { width: min(680px, 100%); justify-self: center; margin: 24px auto 48px; }
.legal h1 { text-align: right; font-size: clamp(44px, 12vw, 56px); font-weight: 900; line-height: 1; letter-spacing: -2px; margin: 12px 0 12px; }
.legal .date { text-align: right; color: var(--muted); font-size: 15px; letter-spacing: .8px; margin: 0 0 28px; }
article { padding: 32px 28px; border-radius: 24px; background: rgba(10, 10, 15, .6); border: 1px solid var(--line);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); font-weight: 400; font-size: 17px; line-height: 1.6; }
article h2 { font-size: 20px; letter-spacing: -.3px; margin: 32px 0 8px; font-weight: 700; }
article h2:first-child { margin-top: 0; }
article a { color: var(--text); text-decoration: underline; text-decoration-color: var(--muted); text-underline-offset: 3px; }
ul { padding-left: 20px; } li { margin: 4px 0; }

/* On a phone the page simply flows and scrolls: the mock large and
   undistorted, the title across the width, the points one under another,
   the badges stacked with the App Store first. Only sideways scrolling is
   forbidden (blocked at the root). */
/* A phone (Alex's brief, 2026-10-01): two screens the scroll snaps
   between. One: the wordmark large in the middle (it is the title here),
   "Made by a musician", the title line in the same muted type, the mock.
   Once the page moves the mark shrinks into the header's corner and the
   two download buttons appear beside it. Two: the pressed skill in the
   middle of the screen, the keyboard under it at a distance, the footer
   with the badges, the links in a row and the credit. */
@media (max-width: 900px) {
  /* No snapping on a phone: it jerked with the browser's bars (Alex, 2026-10-01). */
  html { scroll-snap-type: none; }
  .page { padding: 0 22px; }
  .legal { margin: 8px auto 32px; }
  article { padding: 24px 20px; font-size: 16px; }
  header.top { height: 64px; }
  /* The mark rides the scroll (--mk 0 → 1 over the first 160 px, site.js): large in
     the middle at 1.68, down to its place in the header's corner at 1. */
  /* Landing only: the text pages keep the small mark in the corner. */
  .home .mark { --s: calc((1 + .68 * (1 - var(--mk, 0))) * (1 + var(--pull, 0))); transform-origin: 0 0;
    transform: translate(calc((50vw - 22px - 159px * var(--s) / 2) * (1 - var(--mk, 0))), calc(26px * (1 - var(--mk, 0)))) scale(var(--s)); }
  .home header.top .get { opacity: var(--mk, 0); pointer-events: auto; }
  /* The text pages: no mark and no store marks, a way back home instead. */
  body:not(.home) header.top .mark, body:not(.home) header.top .get { display: none; }
  .back { display: grid; }
  header.top .get { opacity: 1; pointer-events: auto; }
  /* A solid header once scrolled: nothing of the page shows under it. */
  header.top.scrolled { background: var(--bg); backdrop-filter: none; -webkit-backdrop-filter: none; }
  /* Solid ground above the header too: Safari's collapsed address bar sits
     over that strip and would show the page through it. */
  header.top.scrolled::before { content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 200px; background: var(--bg); }
  /* Marks only on a phone, large, a little in from the edge, close but tappable. */
  .get { gap: 6px; margin-right: 8px; }
  .get a { width: 46px; height: 46px; padding: 0; justify-content: center; }
  .get a span { display: none; }
  .get svg { width: 30px; height: 30px; }
  .hero { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 0; padding: 58px 22px 26px; margin: 0 -22px; min-height: 0; height: calc(100vh - 64px); height: calc(var(--sh, 100svh) - 64px); overflow: hidden; scroll-snap-align: none; }
  /* Edge to edge, so the glow behind the mock is clipped at the window's edge, not at the column's (a band showed at 22 px). */
  /* Once the second screen is up, the first simply fades away: a taller
     window (the browser's bars gone) would otherwise show its tail above. */
  .hero { opacity: var(--hero-o, 1); }
  .hero > div:first-child { display: contents; }
  .hero .label, .hero h1, .hero .stores { display: none; }
  .down { bottom: 4px; }
  /* The lines fade as the mark passes over them. */
  .sub { font-size: 14px; line-height: 1.4; margin: 0 0 6px; opacity: calc(1 - var(--mk, 0)); }
  .sub.tagline { display: block; margin-bottom: 14px; }
  .stage { flex: 1; min-height: 0; height: auto; width: 100%; margin: 0; place-items: center; }
  .phone { height: 100%; width: auto; aspect-ratio: 1222 / 2638; transform: none; margin: 0 auto;
    animation: enterFlat 1.2s ease-out forwards, floatFlat 6s ease-in-out 1.2s infinite; }
  .screen { width: 100%; height: 100%; aspect-ratio: auto; }
  .glow { width: 92vw; height: 92vw; margin: -46vw 0 0 -46vw; filter: blur(64px); }
  .glow::before { display: none; }
  /* The keyboard runs past the screen's edges: the screen itself clips it here,
     or iOS lets the page pan and zoom sideways. */
  .skills { height: calc(100vh - 64px); height: calc(var(--sh, 100svh) - 64px); scroll-snap-align: none; margin: 0 -22px; padding: 0 22px; overflow: hidden; }
  /* Alex's numbers, set with a slider panel on his phone (2026-10-01). */
  .skills { --keys-h: calc(var(--sh, 100svh) * 0.58); --keys-y: calc(var(--sh, 100svh) * 0.07); --keys-w: 20vw; --about-y: -calc(var(--sh, 100svh) * 0.012); --about-k: 1.02; --foot-top: calc(var(--sh, 100svh) * 0.08); --foot-gap: calc(var(--sh, 100svh) * 0.04); }
  /* The words sit at the top of the screen, the keys below them with air between. */
  .abouts { align-content: start; padding: calc(var(--sh, 100svh) * 0.03) 0 0; z-index: 1; transform: translateY(var(--about-y)) scale(var(--about-k)); }
  .about { max-width: 100%; }
  .about svg { width: calc(var(--sh, 100svh) * 0.13); height: calc(var(--sh, 100svh) * 0.13); margin-bottom: calc(var(--sh, 100svh) * 0.015); } .about.rounds svg { width: calc(var(--sh, 100svh) * 0.146); height: calc(var(--sh, 100svh) * 0.146); margin-bottom: calc(var(--sh, 100svh) * 0.012); }
  .about h2 { font-size: clamp(28px, calc(var(--sh, 100svh) * 0.052), 46px); margin-bottom: calc(var(--sh, 100svh) * 0.014); } .about p { font-size: clamp(13px, calc(var(--sh, 100svh) * 0.019), 16px); line-height: 1.45; padding: 0 6px; }
  /* A gentler tilt than the desktop's: at 60° the far edge vanishes on a narrow screen. */
  /* The keyboard is anchored at its foot by the footer; a taller box is
     what lifts the drawn keys up toward the words. */
  .keys { height: var(--keys-h); margin: calc(94px - var(--keys-h) + var(--keys-y)) calc(-1 * var(--keys-w)) 0; transform: perspective(760px) rotateX(55deg);
    -webkit-mask-image: linear-gradient(transparent, #000 25%); mask-image: linear-gradient(transparent, #000 25%); }
  /* One and the same gap: keys to badges, badges to links, links to the credit. */
  .skills footer { gap: var(--foot-gap); font-size: clamp(12px, calc(var(--sh, 100svh) * 0.0166), 14px); padding: var(--foot-top) 0 calc(var(--sh, 100svh) * 0.019); }
  .stores.foot { display: flex; width: 100%; gap: 10px; }
  .store { flex: 1; height: clamp(44px, calc(var(--sh, 100svh) * 0.064), 54px); justify-content: center; padding: 0 10px; gap: 10px; }
  .store svg { width: 26px; height: 26px; flex: none; }
  .store small { font-size: 9px; } .store b { font-size: 17px; }
  footer .links { gap: 6px 16px; }
}
@keyframes enterFlat { from { transform: scale(.9); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes floatFlat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-12px); } }
@media (prefers-reduced-motion: reduce) { .phone, h1 span, .glow::before { animation: none !important; opacity: 1; filter: none; transform: none; } }
