/* Sounds & Colors, "Full Spectrum" homepage. Standalone stylesheet. */
:root {
  --ink: #0b0d12;
  --navy: #1e2d40;
  --slate: #3d5a80;
  --teal: #2a7f6f;
  --amber: #c8871e;
  --orange: #d4701a;
  --coral: #d94f2a;
  --link: #c4471f;
  --gray: #b0bec5;
  --paper: #fafaf8;
  --font-display: "Outfit", sans-serif;
  --font-body: "Source Sans 3", sans-serif;
  --page: #0b0d12;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
/* Without this, selecting the cover paragraph paints the browser's default blue behind ten app names that keep
   their own colour, and every one of them drops to between 1.6 and 2.7 against it. The paragraph is the one part
   of the page anybody would quote. */
::selection { background: #fff; color: #0b0d12; }
body {
  margin: 0;
  background: var(--page);
  color: #fff;
  font-family: var(--font-body);
  font-size: 1.125rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  transition: background-color 0.9s ease;
  overflow-x: hidden;
}
a { color: inherit; text-decoration: none; }
img { max-width: 100%; display: block; }
h1, h2, h3, p { margin: 0; }
.skip-link { position: absolute; left: -999px; top: 0; z-index: 100; background: #fff; color: var(--ink); padding: 0.5rem 1rem; }
.skip-link:focus { position: fixed; left: 1rem; top: 1rem; }
.skip-link + .skip-link:focus { top: 4rem; }   /* the second one, so the two never sit on top of each other */   /* fixed, or it sits at the top of the document and is off screen once you have scrolled */
:focus-visible { outline: 3px solid #fff; outline-offset: 3px; box-shadow: 0 0 0 7px var(--ink); }
.cell-yt:focus-visible { outline-color: var(--navy); box-shadow: none; }
[id] { scroll-margin-top: 5rem; }
/* The complete recipe, not the half of it that usually works. width and height alone lose to any padding,
   border or min-height the element already carries, and `clip` is deprecated, so clip-path carries it now. */
.sr-only { position: absolute; width: 1px; height: 1px; min-width: 0; min-height: 0; padding: 0; border: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }

/* Grain: the whole page reads as printed, not rendered */
.grain {
  position: fixed; inset: 0; z-index: 50; pointer-events: none;
  opacity: 0.07;
  mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%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: 240px 240px;
}

/* ---------- Nav ---------- */
.top {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;   /* the gap was .lab-deck only, which put the homepage's links 8px off every other page's */
  /* THE BAR GIVES WAY DOWNWARD, NOT INWARD. A reader using text-only zoom keeps a 1440px window while every label
     doubles, and the breakpoint that folds the links into the menu is in rem, which a media query measures against
     the browser's default size and not the zoomed one, so the full menu stayed out and had to fit anyway: the
     wordmark was squeezed to "Sounds &" and "B-Roller" broke over two lines inside its own pill. Wrapping costs a
     second row at a size where a second row is the honest answer, and nothing is clipped or broken. */
  flex-wrap: wrap; row-gap: 0.4rem;
  padding: 0.9rem clamp(1rem, 3vw, 2rem);
  transition: background-color 0.4s, backdrop-filter 0.4s, box-shadow 0.4s;
}
.top.is-scrolled {
  background: rgba(11, 13, 18, 0.55);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  box-shadow: 0 1px 0 rgba(255,255,255,0.08);
}
.brand {
  font-family: var(--font-display); font-weight: 700; font-size: 1rem;
  letter-spacing: 0.02em; display: inline-flex; align-items: center; gap: 0.5rem;
  min-height: 44px; padding: 0.5rem 0; margin: -0.5rem 0;   /* the home link is a 44px target like the rest of the nav */
  /* THE NAME GIVES WAY BEFORE THE MENU DOES. Both the wordmark and the bar's padding are in rem, so a reader who
     raises their browser's default font grows the name while the phone stays the width it is: at 125% on a 320px
     screen the menu button was already 20px past the right edge, and at 200% on a 390px phone it was 109px out
     with no horizontal scroll to reach it and nothing hittable at its centre. The header's only control, gone, at
     the setting a low-vision reader actually uses. A truncated name is a worse logo and a working site. */
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 1 auto;
}
.brand .brand-dot { flex: 0 0 auto; }
.menu-btn, .top-coffee, .deck-pause { flex: 0 0 auto; }
.brand-dot { width: 12px; height: 12px; border-radius: 50%; background: linear-gradient(135deg, var(--slate), var(--teal) 45%, var(--amber) 70%, var(--coral)); }
/* The links sit at the right on every page. This was in deck.css under .lab-deck, so the homepage pushed them
   right and the other ten let space-between centre them, and the nav visibly jumped as you moved between them. */
.top-links { display: none; list-style: none; margin: 0 0 0 auto; padding: 0; align-items: center; gap: 0.25rem; }
.top-links > li { position: relative; }
/* full white, not 85%: on the Projects page's coral ground 85% measured 3.87:1 against a 4.5 floor. The nav sits
   on nine different grounds, so it takes the colour that clears all of them. */
.top-links a, .top-toggle {
  font-family: var(--font-display); font-weight: 600; font-size: 0.875rem; letter-spacing: 0.02em;
  color: #fff; padding: 0.75rem 0.75rem; min-height: 44px; border-radius: 999px; white-space: nowrap;
  background: none; border: 0; cursor: pointer; display: inline-flex; align-items: center; gap: 0.3rem;
  transition: background-color 0.2s, color 0.2s;
}
.top-links a:hover, .top-toggle:hover { color: #fff; background: rgba(255,255,255,0.1); }
.top:not(.is-scrolled) .top-links a, .top:not(.is-scrolled) .top-toggle, .top:not(.is-scrolled) .brand { text-shadow: 0 1px 2px rgba(0,0,0,0.7), 0 0 12px rgba(0,0,0,0.5); }
.top-toggle svg { width: 12px; height: 12px; }
.top-sub {
  position: absolute; top: calc(100% + 0.5rem); left: 0; min-width: 13rem;
  list-style: none; margin: 0; padding: 0.4rem;
  background: rgba(11, 13, 18, 0.92); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(255,255,255,0.1); border-radius: 14px;
  box-shadow: 0 20px 40px -16px rgba(0,0,0,0.7);
  display: none;
}
/* THE GAP BETWEEN THE BUTTON AND THE PANEL IS PART OF THE PANEL. The panel is half a rem below the thing that
   opens it, and hover lives on the <li>, so a pointer travelling from the button down to the first item crossed
   half a rem of nothing and the menu shut under it. There was a bridge for this, but it was an ::after on the li,
   which is only as wide as the button: the panel is at least 13rem and starts at the button's left edge, so aiming
   at the middle of an item walked the pointer straight out of the bridge. It was also scoped to the homepage,
   while the same nav is on all eleven pages. On the panel it is the panel's own width, and it is everywhere. */

/* ---------- The pause control in the nav ----------
   IT IS A NAV CONTROL, SO IT LIVES WITH THE NAV. It was styled in deck.css, which /projects/ and /404.html do not
   load, so on those two pages the button in the shared nav partial fell back to the browser's own button: 54px
   instead of 87, which slid every link in the menu 37px sideways. Same class of fault as the submenu bridge and
   the export ratios, and the same answer: a thing that appears on every page is described where every page can
   see it. */
.deck-pause {
  height: 44px; padding: 0 0.9rem 0 0.75rem; border-radius: 999px; border: 1px solid rgba(255,255,255,0.22);
  background: rgba(255,255,255,0.06); color: #fff; cursor: pointer; display: inline-flex; align-items: center; gap: 0.4rem;
  font-family: var(--font-display); font-weight: 700; font-size: 0.82rem; letter-spacing: 0.01em; line-height: 1;
  margin-left: 0.25rem; flex: 0 0 auto;
  opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.3s, visibility 0s 0.3s;   /* hidden means out of the tab order too */
}
.deck-pause.is-visible { opacity: 1; visibility: visible; pointer-events: auto; transition-delay: 0s; }
.deck-pause.is-hidden { opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.3s, visibility 0s 0.3s; }
.deck-pause:hover { background: rgba(255,255,255,0.14); }
@media (max-width: 63.99rem) { .deck-pause { margin-left: auto; } }   /* with the links folded into the menu, the pill sits beside the menu button instead of mid-nav */
.deck-pause svg { width: 16px; height: 16px; }
.deck-pause .ico-play { display: none; }
.deck-pause[aria-pressed="true"] .ico-play { display: block; }
.deck-pause[aria-pressed="true"] .ico-pause { display: none; }
/* THE WORD IS WRITTEN, NOT DRAWN. It used to be a ::after adding a "d", so the button read "Paused" while its
   accessible name stayed "Pause page motion": "click Paused" fails for anyone driving by voice (WCAG 2.5.3), and
   the state word could be neither selected nor translated. js/site.js sets both together now. */
.top-sub::before { content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 0.5rem; }
.top-sub a { display: flex; align-items: center; min-height: 44px; padding: 0.6rem 0.8rem; border-radius: 10px; font-weight: 500; }
.top-sub .soon { display: flex; justify-content: space-between; gap: 0.75rem; padding: 0.55rem 0.8rem; color: rgba(255,255,255,0.45); font-family: var(--font-display); font-size: 0.875rem; }
.soon-badge { font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase; border: 1px solid rgba(255,255,255,0.3); border-radius: 999px; padding: 0.1rem 0.45rem; align-self: center; }
/* FOCUS ALONE DOES NOT OPEN A SUBMENU. It used to, through :focus-within, and the three panels' eight links were
   then in the tab order behind their buttons: seventeen stops between the top of the page and the Download button.
   The markup is already a disclosure (a button with aria-expanded and aria-controls), so it now behaves like one.
   A mouse hovers, a finger or a keyboard presses the button, and Tab walks the five top-level items. */
.top-links > li:hover > .top-sub, .top-links > li.open > .top-sub { display: block; }
.top-coffee svg { width: 18px; height: 18px; }
.top-links .top-coffee { min-width: 44px; justify-content: center; }
.menu-btn { background: none; border: 0; color: #fff; cursor: pointer; padding: 0.5rem; min-width: 44px; min-height: 44px; display: grid; place-items: center; }
.menu-btn svg { width: 26px; height: 26px; }
.menu-btn .ico-close { display: none; }
.menu-btn[aria-expanded="true"] .ico-open { display: none; }
.menu-btn[aria-expanded="true"] .ico-close { display: block; }
.mobile-menu {
  display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 39;
  background: #0b0d12; padding: 5rem 1.5rem 2rem; overflow: auto;
}
/* A SHADOW AT THE EDGE WHERE THERE IS MORE TO SEE. The menu is 868px of content. On a 667px phone it ended in
   whitespace just after PhotoDesc with Utilities, Projects and the Ko-fi link 201px below the fold and nothing to
   suggest they existed. Two gradients painted on the panel itself with background-attachment: local scroll away
   with the content, so the shadow is there exactly while something is still hidden and gone when it is not. No
   script, no measuring, and it is right at every phone height. */
.mobile-menu {
  background-image:
    linear-gradient(#0b0d12 40%, rgba(11,13,18,0)),
    linear-gradient(rgba(11,13,18,0), #0b0d12 60%),
    radial-gradient(farthest-side at 50% 0, rgba(255,255,255,0.22), rgba(255,255,255,0)),
    radial-gradient(farthest-side at 50% 100%, rgba(255,255,255,0.22), rgba(255,255,255,0));
  background-repeat: no-repeat;
  background-position: 0 0, 0 100%, 0 0, 0 100%;
  background-attachment: local, local, scroll, scroll;
  background-size: 100% 48px, 100% 48px, 100% 16px, 100% 16px;
}
.mobile-menu.open { display: block; }
.mobile-menu ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.mobile-menu a, .mobile-menu .mobile-soon { font-family: var(--font-display); font-size: 1.35rem; font-weight: 600; color: #fff; display: block; padding: 0.55rem 0; }
.mobile-menu .mobile-group { font-family: var(--font-display); font-size: 0.75rem; letter-spacing: 0.16em; text-transform: uppercase; color: rgba(255,255,255,0.45); margin-top: 1rem; }
.mobile-menu .mobile-sub { padding-left: 0.75rem; }
.mobile-menu .mobile-soon { padding-left: 0.75rem; color: rgba(255,255,255,0.45); display: flex; gap: 0.6rem; align-items: center; }
.mobile-menu .mobile-divider { border-top: 1px solid rgba(255,255,255,0.12); margin: 0.75rem 0; }
@media (min-width: 64rem) { .top-links { display: flex; } .menu-btn { display: none; } }
/* .top only gets its ground from .is-scrolled, which js/site.js adds on scroll. With scripts off it never arrived,
   so the bar stayed transparent and the cover paragraph scrolled straight through the wordmark. No script, no
   scroll listener, so the bar simply keeps its ground the whole way down. */
.no-js .top { background: rgba(11,13,18,0.92); backdrop-filter: blur(8px); }
/* The duplicate half that hides the loop's seam is added by the script, so with scripts off the marquee slid a
   single copy and left 441px of empty strip before snapping back. Standing still, it is a row of apps, which is
   what it is for; and it matches what reduced motion already does. */
.no-js .strip-track { animation: none !important; }
/* A row that already fills the window has nothing to scroll past, so it holds still and shows one copy. Same
   reasoning as the no-script rule above it: no loop, no motion. */
.strip-track.is-still { animation: none !important; }
/* and nothing fades off the right-hand edge, because nothing is coming */
.strip-viewport:has(.strip-track.is-still) { mask-image: linear-gradient(to right, transparent, #000 1rem); -webkit-mask-image: linear-gradient(to right, transparent, #000 1rem); }
.no-js .lab-deck .strip-viewport, .no-js .strip-viewport { overflow-x: auto; mask-image: none; -webkit-mask-image: none; }
/* THE SAME REASONING, APPLIED TO EVERY CONTROL AND EVERY LOOP RATHER THAN THE ONE I HAPPENED TO BE LOOKING AT.
   Pausing is done by a class the script toggles, so with no script both pause buttons are controls that cannot
   act, and 45 infinite animations had no way to be stopped: WCAG 2.2.2 failed in the path with no script, on a
   page that built two pause controls for the path with one. Nothing loops here, so nothing needs pausing, and the
   controls that could not have worked are gone. This matches what reduced motion already does. */
.no-js .menu-btn, .no-js .strip-pause, .no-js .deck-pause { display: none; }
/* ...and the menu the hidden hamburger would have opened. Without a script a phone was left with a nav bar holding
   one link, the brand, and no way to reach nine app pages except the filmstrip and the footer. The panel is already
   in the markup, so it needs no script to show: a plain link puts its id in the URL and :target opens it, and a
   second link inside puts the nav's id there instead and closes it again. Both links exist only on this path; with
   a script the button does the job and these are not rendered. */
/* THE MARKER, AND A RULE THAT CANNOT BE OUTRANKED. These two links exist only for the path where no script runs.
   Hiding them with a bare class was not enough: .mobile-menu a sets display:block and carries one more element in
   its selector, so the Close link rendered for everybody, first item in the open menu. Scoping the hiding rule to
   "the script has run" says exactly what is meant, out-specifies anything that styles the link itself, and will go
   on being right for the next fallback somebody adds. */
html:not(.no-js) .menu-link-nojs, html:not(.no-js) .mobile-close-nojs { display: none !important; }
.no-js .menu-link-nojs { display: grid; }
.no-js .mobile-menu:target { display: block; }
/* THE WAY OUT WAS UNDERNEATH THE NAV BAR. The panel is z-index 39 and the fixed nav is 40, and with no script the
   nav paints an opaque ground the whole way down the page, so the Close link at top: 1.1rem was drawn beneath it:
   invisible, and 36 of its 64px were under the link that opens the menu, which is what took the tap and did
   nothing because its target was already current. Back, or leaving the site, were the only exits. An open
   full-screen menu outranks the bar it was opened from. */
.no-js .mobile-menu:target { z-index: 41; }
.no-js .mobile-close-nojs { display: inline-block; position: absolute; top: 1.1rem; right: 1.5rem; z-index: 1;
  font-family: var(--font-display); font-weight: 600; font-size: 1rem; color: #fff;
  min-height: 44px; min-width: 44px; padding: 0.6rem 0.75rem; }
@media (min-width: 64rem) { .no-js .menu-link-nojs { display: none; } }   /* the full menu is on the bar at this width */
.no-js .strip-track, .no-js .bands span, .no-js .aurora span,
.no-js .demo *, .no-js .demo *::before, .no-js .demo *::after,
.no-js .mini *, .no-js .mini *::before { animation: none !important; }
/* Only one thing on this site genuinely cannot exist without a script: the address, which is kept out of the
   markup on purpose so it cannot be scraped. Everything else that used to hide here - the version chips, the
   download buttons - now ships with the real value in it, written at build time from the app's own manifest, so
   there is nothing left to apologise for. If a selector ever needs adding back here, ask first whether the value
   could simply be in the markup instead. */
.no-js .email-slot { display: none; }
/* The Say hello card used to be a link only because the script gave it one, so without a script it was a 325x377
   focusable box with no href that promised "I read every message" and then stopped. It now ships pointing at the
   Ko-fi page, which takes messages and needs no script, and js/site.js upgrades it to the address. Nothing here has
   to make it inert any more, because it is never inert. */

/* ---------- Hero ---------- */
.hero {
  position: relative; min-height: 100svh;
  display: grid; grid-template-rows: 1fr auto;
  overflow: hidden; isolation: isolate;
  padding-top: 4rem;
}
.aurora { position: absolute; inset: -20%; z-index: -1; filter: blur(60px) saturate(120%); opacity: 0.85; }
.aurora span { position: absolute; border-radius: 50%; mix-blend-mode: screen; will-change: transform; }
.aurora .a1 { width: 60vw; height: 60vw; left: -10vw; top: -5vh; background: radial-gradient(circle at 40% 40%, var(--slate), transparent 65%); animation: drift1 26s ease-in-out infinite alternate; }
.aurora .a2 { width: 55vw; height: 55vw; left: 30vw; top: 48vh; opacity: 0.75; background: radial-gradient(circle at 50% 50%, var(--teal), transparent 65%); animation: drift2 31s ease-in-out infinite alternate; }
.aurora .a3 { width: 58vw; height: 58vw; right: -8vw; top: -10vh; background: radial-gradient(circle at 50% 50%, var(--amber), transparent 65%); animation: drift3 29s ease-in-out infinite alternate; }
.aurora .a4 { width: 50vw; height: 50vw; right: 5vw; bottom: -15vh; background: radial-gradient(circle at 50% 50%, var(--coral), transparent 65%); animation: drift1 34s ease-in-out infinite alternate-reverse; }
@keyframes drift1 { from { transform: translate(0, 0) scale(1); } to { transform: translate(12vw, 10vh) scale(1.15); } }
@keyframes drift2 { from { transform: translate(0, 0) scale(1.05); } to { transform: translate(-14vw, -12vh) scale(0.9); } }
@keyframes drift3 { from { transform: translate(0, 0) scale(0.95); } to { transform: translate(-10vw, 14vh) scale(1.2); } }
.hero-inner {
  align-self: center;
  padding: clamp(2rem, 6vh, 5rem) clamp(1rem, 4vw, 3rem) 2rem;
  max-width: 96rem; width: 100%; margin: 0 auto;
}
.wordmark {
  font-family: var(--font-display);
  font-weight: 800;
  text-transform: uppercase;
  line-height: 0.86;
  letter-spacing: -0.03em;
  font-size: clamp(3.6rem, 15.5vw, 15rem);
  display: grid;
}
.wordmark .row { display: flex; white-space: nowrap; }
.wordmark .l { display: inline-block; font-variation-settings: "wght" 800; }
.wordmark .w-sounds .l:nth-child(1) { color: #7398c1; }
.wordmark .w-sounds .l:nth-child(2) { color: #6794b7; }
.wordmark .w-sounds .l:nth-child(3) { color: #5b93ac; }
.wordmark .w-sounds .l:nth-child(4) { color: #509ca1; }
.wordmark .w-sounds .l:nth-child(5) { color: #46a795; }
.wordmark .w-sounds .l:nth-child(6) { color: #3eb48f; }
.wordmark .w-amp { color: var(--gray); font-weight: 200; font-variation-settings: "wght" 200; margin-right: 0.08em; }
.wordmark .w-colors .l:nth-child(1) { color: #c8871e; }
.wordmark .w-colors .l:nth-child(2) { color: #cc801c; }
.wordmark .w-colors .l:nth-child(3) { color: #d0781a; }
.wordmark .w-colors .l:nth-child(4) { color: #d46b1f; }
.wordmark .w-colors .l:nth-child(5) { color: #d75d25; }
.wordmark .w-colors .l:nth-child(6) { color: #d94f2a; }
.hero-copy {
  margin-top: clamp(1.5rem, 4vh, 3rem);
  display: grid; gap: 1.1rem;
  max-width: 44rem;
}
.hero-count { display: block; font-family: var(--font-display); font-size: 1rem; letter-spacing: 0.01em; color: rgba(255,255,255,0.7); }
.hero-count b { color: #fff; font-weight: 700; }
@media (min-width: 64rem) {
  .hero-inner { display: grid; grid-template-columns: max-content minmax(min(16rem, 100%), 1fr); align-items: center; column-gap: clamp(2rem, 4vw, 4rem); }   /* a floor that cannot give way pushes the page sideways: see .feature-grid in app.css */
  .wordmark { font-size: clamp(3.6rem, 11.5vw, 13rem); }
  .hero-copy { margin-top: 0; max-width: 28rem; }
  .hero-tag { font-size: clamp(1.4rem, 2.1vw, 1.75rem); }
}
.hero-tag {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.5rem, 2.8vw, 2.1rem); line-height: 1.15; letter-spacing: -0.01em;
  text-wrap: balance;
}
.hero-sub { color: rgba(255,255,255,0.74); font-size: 1rem; max-width: 38rem; }
@media (max-width: 47.99rem) {
  .hero-inner { align-self: center; padding-top: 3rem; padding-bottom: 3rem; }   /* centred between the nav and the filmstrip; pinned to the top it left a 190px quiet band under the buttons */
}
.cta-row { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 0.25rem; }
.btn {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-family: var(--font-display); font-weight: 700; font-size: 1rem;
  padding: 0.85rem 1.5rem; border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.42);
  color: #fff; background: rgba(255,255,255,0.06);
  transition: transform 0.25s var(--ease), background-color 0.25s, border-color 0.25s, color 0.25s;
}
.btn:hover { transform: translateY(-2px); background: rgba(255,255,255,0.14); border-color: rgba(255,255,255,0.5); }
.btn-solid { background: #fff; color: var(--ink); border-color: #fff; }
.btn-solid:hover { background: #fff; color: var(--ink); }
.btn-accent { background: var(--accent, #fff); color: var(--bg, var(--ink)); border-color: var(--accent, #fff); }
.btn-accent:hover { background: var(--accent, #fff); color: var(--bg, var(--ink)); filter: brightness(1.08); }

/* Filmstrip of icons along the hero's bottom edge */
.strip {
  position: relative; z-index: 1;
  padding: 1.25rem 0 1.5rem;
  border-top: 1px solid rgba(255,255,255,0.08);
  overflow: hidden;
}
.strip-viewport {
  overflow: hidden;
  margin-left: calc(clamp(1rem, 3vw, 2rem) + 44px + 0.75rem);
  mask-image: linear-gradient(to right, transparent, #000 1.5rem, #000 92%, transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 1.5rem, #000 92%, transparent);
}
.strip-track { display: flex; gap: 1.25rem; width: max-content; animation: marquee 48s linear infinite; }
.strip-track > a { margin-right: 0; }
.strip:hover .strip-track, .strip-track.is-paused, .strip-track.is-prewarm, .strip-viewport:focus-within .strip-track { animation-play-state: paused; }
.strip-viewport:focus-within { mask-image: none; -webkit-mask-image: none; }   /* nothing fades over a focused pill */
.strip-track.is-held { animation: none !important; }   /* held still while keyboard focus walks the pills */
.strip-pause {
  position: absolute; left: clamp(1rem, 3vw, 2rem); top: 50%; transform: translateY(-50%); z-index: 2;
  width: 44px; height: 44px; border-radius: 50%; border: 1px solid rgba(255,255,255,0.2);
  background: rgba(11,13,18,0.7); color: #fff; cursor: pointer; display: grid; place-items: center;
}
.strip-pause svg { width: 16px; height: 16px; }
.strip-pause .ico-play { display: none; }
.strip-pause[aria-pressed="true"] .ico-play { display: block; }
.strip-pause[aria-pressed="true"] .ico-pause { display: none; }
@keyframes marquee { from { transform: translateX(0); } to { transform: translateX(calc(-50% - 0.625rem)); } }
.strip a {
  display: flex; align-items: center; gap: 0.8rem;
  padding: 0.5rem 1rem 0.5rem 0.5rem; border-radius: 999px;
  background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08);
  font-family: var(--font-display); font-weight: 600; font-size: 0.875rem; white-space: nowrap;
  transition: background-color 0.2s, transform 0.25s var(--ease);
}
.strip a:hover { background: rgba(255,255,255,0.12); transform: translateY(-2px); }
.strip img { width: 40px; height: 40px; border-radius: 10px; }
.strip .soon-pill { color: rgba(255,255,255,0.55); }

/* ---------- Intro ---------- */
.intro { padding: clamp(4rem, 8vh, 6rem) clamp(1rem, 4vw, 3rem); }
.intro-inner { max-width: 68rem; margin: 0 auto; display: grid; gap: 2rem; }
.intro-lead {
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(1.6rem, 3.6vw, 3rem); line-height: 1.15; letter-spacing: -0.02em;
  text-wrap: balance;
}
.intro-grid { display: grid; gap: 1.25rem; color: rgba(255,255,255,0.78); max-width: 62ch; }
.intro-grid a { color: #fff; text-decoration: underline; text-decoration-color: var(--amber); text-underline-offset: 4px; }
.intro-grid a:hover { text-decoration-color: #fff; }
.intro-strong { font-family: var(--font-display); font-weight: 700; color: #fff; font-size: 1.35rem; }
@media (min-width: 64rem) {
  .intro-inner { grid-template-columns: 1.2fr 1fr; column-gap: 5rem; align-items: end; }
}

/* ---------- Spectrum bar (fixed legend + progress) ---------- */
.spectrum {
  position: fixed; right: 0.75rem; top: 50%; transform: translateY(-50%) translateX(140%);
  z-index: 41;
  display: none; flex-direction: column; align-items: center; gap: 2px; padding: 8px 4px;
  background: rgba(11, 13, 18, 0.72); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255,255,255,0.12); border-radius: 999px;
  transition: transform 0.5s var(--ease), opacity 0.4s;
  opacity: 0;
}
.spectrum.is-visible { transform: translateY(-50%) translateX(0); opacity: 1; }
@media (min-width: 64rem) { .spectrum { display: flex; } }
.spectrum-label {
  position: absolute; right: calc(100% + 10px); top: 50%; transform: translateY(-50%);
  font-family: var(--font-display); font-weight: 600; font-size: 0.875rem; color: #fff; white-space: nowrap;
  background: rgba(11,13,18,0.85); border: 1px solid rgba(255,255,255,0.12); border-radius: 6px; padding: 0.3rem 0.6rem;
  opacity: 0; transition: opacity 0.3s;
}
.spectrum:hover .spectrum-label, .spectrum:focus-within .spectrum-label { opacity: 1; }
.spectrum a { position: relative; display: grid; place-items: center; width: 36px; height: 36px; }
.spectrum a i { display: block; width: 12px; height: 12px; border-radius: 999px; background: var(--c); opacity: 0.55; transition: opacity 0.3s, transform 0.3s var(--ease); }
.spectrum a:hover i, .spectrum a:focus-visible i { opacity: 0.9; }
.spectrum a.is-active i { opacity: 1; transform: scale(1.5); box-shadow: 0 0 0 3px rgba(255,255,255,0.15); }
.spectrum a::after {
  content: attr(data-label);
  position: absolute; right: calc(100% + 10px); top: 50%; transform: translateY(-50%) translateX(4px);
  white-space: nowrap; font-family: var(--font-display); font-weight: 600; font-size: 0.75rem;
  color: #fff; background: rgba(11,13,18,0.92); border: 1px solid rgba(255,255,255,0.14);
  padding: 0.3rem 0.6rem; border-radius: 6px; opacity: 0; pointer-events: none;
  transition: opacity 0.2s, transform 0.2s var(--ease);
}
.spectrum a:hover::after, .spectrum a:focus-visible::after { opacity: 1; transform: translateY(-50%) translateX(0); }
.spectrum:hover .spectrum-label { opacity: 0; }

/* ---------- App panels: the spectrum ---------- */
.apps-head {
  padding: clamp(4rem, 9vh, 6rem) clamp(1rem, 4vw, 3rem) 0;
  margin-bottom: 0;
  max-width: 96rem; margin: 0 auto;
}
.apps-head h2 {
  font-family: var(--font-display); font-weight: 800; letter-spacing: -0.03em;
  font-size: clamp(2.2rem, 5vw, 4rem); line-height: 0.95;
}
.apps-head p { color: rgba(255,255,255,0.7); margin-top: 1rem; max-width: 46rem; }
.panel {
  --bg: #1e2d40; --accent: #fff;
  position: relative;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  gap: 2rem;
  padding: clamp(4rem, 9vh, 6.5rem) clamp(1rem, 4vw, 3rem);
  max-width: 96rem; margin: 0 auto;
}
.panel-family { min-height: 70svh; }
.panel-copy { display: grid; gap: 1.35rem; align-content: center; }
.panel-copy > * { min-width: 0; }
.panel-name { overflow-wrap: anywhere; }
.panel-compact { min-height: 0; padding-top: clamp(3rem, 7vh, 5rem); padding-bottom: clamp(3rem, 7vh, 5rem); }
.panel-compact .panel-name { font-size: clamp(2.4rem, 5.5vw, 4.75rem); }
.panel-compact .panel-icon { width: min(50vw, 300px); }
.panel-name .amp { font-weight: 300; color: rgba(255,255,255,0.55); }
a.panel-icon { display: block; }
a.panel-icon:focus-visible { outline: 3px solid #fff; outline-offset: 6px; }
.panel-kicker {
  display: inline-flex; align-items: center; gap: 0.5rem; width: fit-content;
  font-family: var(--font-display); font-weight: 600; font-size: 0.75rem;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--accent);
  padding: 0;
}
.panel-name {
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(2.75rem, 6.5vw, 6rem); line-height: 0.9; letter-spacing: -0.035em;
  color: var(--accent);
  text-wrap: balance;
}
.panel-desc { font-size: clamp(1.15rem, 1.6vw, 1.4rem); color: rgba(255,255,255,0.86); max-width: 34rem; line-height: 1.45; }
.panel-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem 1rem;
  margin-top: 0.5rem;
  font-family: var(--font-display); font-size: 0.875rem; font-weight: 600; color: rgba(255,255,255,0.6);
}
.chip {
  display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.35rem 0.7rem; border-radius: 999px;
  background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.85);
  font-variant-numeric: tabular-nums;
}
.chip.is-pending { color: rgba(255,255,255,0.6); }
.panel-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 0.75rem; }
.panel-art { display: grid; place-items: center; }
.panel-icon {
  width: min(64vw, 300px); aspect-ratio: 1;
  border-radius: 22.5%;
  overflow: hidden;
  box-shadow:
    0 50px 90px -30px rgba(0, 0, 0, 0.7),
    0 0 0 1px rgba(255,255,255,0.08),
    0 30px 120px -20px color-mix(in srgb, var(--accent) 55%, transparent);
  transition: transform 0.5s var(--ease);
}
.panel-icon img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.4s var(--ease); will-change: transform; }
.panel-art.is-pair { gap: 1.5rem; grid-auto-flow: row; }
.panel-art.is-pair .panel-icon { width: min(44vw, 200px); }
.panel-art.is-pair .panel-icon:nth-child(2) { transform: rotate(6deg); }
.panel-art.is-pair .panel-icon:nth-child(1) { transform: rotate(-6deg); }
.panel-icon.is-dim img { filter: saturate(0.6) brightness(0.8); }
.panel-tint {
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(60% 70% at 78% 50%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%);
  opacity: 0.9;
}
@media (min-width: 56rem) {
  .panel { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(3rem, 6vw, 6rem); }
  .panel-icon { width: min(36vw, 440px); }
  .panel-art.is-pair { grid-auto-flow: column; }
  .panel-art.is-pair .panel-icon { width: min(20vw, 240px); }
  .panel:nth-child(even) .panel-art { order: -1; }
  .panel:nth-child(even) .panel-tint { background: radial-gradient(60% 70% at 22% 50%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%); }
}

/* Scroll-driven motion where supported; static everywhere else */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .panel-icon {
      animation: float linear both;
      animation-timeline: view();
      animation-range: entry 0% exit 100%;
    }
    .panel-art.is-pair .panel-icon { animation: none; }
    @keyframes float {
      from { transform: translateY(9vh) rotate(-5deg) scale(0.94); }
      to   { transform: translateY(-9vh) rotate(5deg) scale(1.02); }
    }
    .panel-copy > .panel-head {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 8% entry 42%;
    }
    @keyframes rise { from { opacity: 0; transform: translateY(28px); } to { opacity: 1; transform: none; } }
    .intro-inner > * { animation: rise linear both; animation-timeline: view(); animation-range: entry 5% entry 35%; }
  }
}

/* ---------- Bento ---------- */
.bento { padding: clamp(4rem, 10vh, 8rem) clamp(1rem, 4vw, 3rem); max-width: 96rem; margin: 0 auto; }
.bento-head { margin-bottom: 2rem; }
.bento-head h2 { font-family: var(--font-display); font-weight: 800; letter-spacing: -0.03em; font-size: clamp(2.2rem, 5vw, 4rem); line-height: 0.95; }
.bento-grid { display: grid; gap: 1.5rem; grid-template-columns: 1fr; }
.cell {
  position: relative; display: flex; flex-direction: column; justify-content: space-between; gap: 2rem;
  padding: clamp(1.5rem, 3vw, 2.5rem);
  border-radius: 24px;
  background: var(--cell, var(--navy));
  color: var(--cell-ink, #fff);
  min-height: 16rem;
  overflow: hidden;
  transition: transform 0.35s var(--ease), box-shadow 0.35s;
  box-shadow: 0 1px 0 rgba(255,255,255,0.06) inset;
}
a.cell:hover { transform: translateY(-4px); box-shadow: 0 30px 60px -30px rgba(0,0,0,0.7); }
.cell h3 { font-family: var(--font-display); font-weight: 800; font-size: clamp(1.75rem, 3vw, 2.75rem); line-height: 0.95; letter-spacing: -0.025em; }
.cell p { max-width: 42ch; font-size: 1.1rem; margin-top: 0.75rem; }
.cell-cta { font-family: var(--font-display); font-weight: 700; font-size: 1rem; display: inline-flex; align-items: center; gap: 0.4rem; }
.cell-cta::after { content: "\2192"; transition: transform 0.25s var(--ease); }
a.cell:hover .cell-cta::after { transform: translateX(4px); }
.cell-projects { --cell: #c4471f; }
.cell-news { --cell: #2a7f6f; }
.cell-yt { --cell: #c8871e; --cell-ink: #1e2d40; }
.cell-yt .cell-mark, .cell-news .cell-mark { font-size: clamp(6rem, 12vw, 10rem); right: 6%; bottom: -10%; }
.cell-hello { --cell: #3d5a80; }
.cell-hello .email { font-family: var(--font-display); font-weight: 700; font-size: clamp(1.25rem, 2.4vw, 1.75rem); word-break: break-all; text-decoration: underline; text-decoration-color: rgba(255,255,255,0.4); text-underline-offset: 5px; }
.cell-hello .email:hover { text-decoration-color: #fff; }
.cell-mark {
  position: absolute; right: -2%; bottom: -6%;
  font-family: var(--font-display); font-weight: 900; font-size: clamp(8rem, 18vw, 16rem); line-height: 1;
  opacity: 0.2; pointer-events: none; user-select: none; letter-spacing: -0.05em;
}
@media (min-width: 48rem) {
  .bento-grid { grid-template-columns: repeat(6, 1fr); }
  .cell-projects { grid-column: span 4; min-height: 17rem; }
  .cell-news { grid-column: span 2; }
  .cell-yt { grid-column: span 2; }
  .cell-hello { grid-column: span 4; }
}

/* ---------- Footer ---------- */
.footer { border-top: 1px solid rgba(255,255,255,0.1); padding: 3rem clamp(1rem, 4vw, 3rem) 2.5rem; color: rgba(255,255,255,0.7); }
.footer-inner { max-width: 96rem; margin: 0 auto; display: flex; flex-direction: column; gap: 1.5rem; align-items: center; }
.footer-nav { display: flex; flex-wrap: wrap; justify-content: center; gap: 1.25rem; font-size: 0.875rem; }
.footer-nav a { padding: 0.75rem 0.5rem; display: inline-block; }   /* 44px wide even for "Email" */
.footer-nav a:hover { color: #fff; }
.footer-copy { font-size: 0.875rem; color: rgba(255,255,255,0.45); }
@media (min-width: 48rem) { .footer-inner { flex-direction: row; justify-content: space-between; } }

@media (prefers-reduced-motion: reduce) {
  /* Nothing loops here, so nothing needs pausing: document.getAnimations() returns 0. Two buttons offering to stop
     motion that is already stopped is the same fault the .no-js path already fixed by removing them. */
  .strip-pause, .deck-pause { display: none; }

  .aurora span, .strip-track { animation: none !important; }
  /* The marquee IS the nav labelled "All apps". Frozen, it is a window onto a 3511px row: at 390 wide only TWO of
     the ten apps could ever be seen or tapped, and there was no way to reach the rest. So it becomes a scroller,
     and the duplicate half (which exists only to hide the loop's seam, and is already aria-hidden) steps out, so
     the row is each app exactly once. */
  .lab-deck .strip-viewport, .strip-viewport { overflow-x: auto; mask-image: none; -webkit-mask-image: none; scrollbar-width: thin; }   /* .lab-deck wins on specificity in deck.css, so the plain selector alone left the last pill fading out */
  .strip-track > a[aria-hidden="true"] { display: none; }
}

/* ---------- Demos: what the app does, in HTML ---------- */
.demo {
  position: relative;
  width: min(100%, 34rem);
  background: rgba(11, 13, 18, 0.55);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 14px;
  padding: 1.25rem 1.25rem 1.5rem;
  box-shadow: 0 40px 80px -30px rgba(0,0,0,0.7), 0 30px 120px -40px color-mix(in srgb, var(--accent) 45%, transparent);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  font-family: var(--font-body);
}
.demo-tag {
  position: absolute; top: -0.85rem; right: 1rem;
  font-family: var(--font-display); font-weight: 700; font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase;
  background: var(--accent); color: var(--bg); padding: 0.4rem 0.75rem; border-radius: 6px;
  box-shadow: 0 8px 20px -10px rgba(0,0,0,0.6);
}
.demo-bar { display: flex; align-items: center; gap: 6px; margin-bottom: 1rem; }
.demo-bar span { width: 10px; height: 10px; border-radius: 50%; background: rgba(255,255,255,0.18); }
.demo-bar em { margin-left: auto; font-style: normal; font-size: 0.75rem; color: rgba(255,255,255,0.55); font-variant-numeric: tabular-nums; }
/* B-Roller Studio: highlight words, get soundbites */
.demo-transcript { font-size: 1rem; line-height: 1.65; color: rgba(255,255,255,0.85); }
.demo-transcript mark {
  color: #fff; background: linear-gradient(var(--accent), var(--accent)) no-repeat 0 0 / 100% 100%;
  border-radius: 4px; padding: 0.05em 0.15em; margin: 0 -0.1em; color: var(--bg); font-weight: 600;   /* the padding is paid back by the margin, so the words around a mark never shift */
}
.demo-clips { display: grid; gap: 0.5rem; margin-top: 1rem; }
.clip { display: flex; align-items: center; gap: 0.75rem; padding: 0.55rem 0.75rem; border-radius: 12px; background: rgba(255,255,255,0.07); font-size: 0.875rem; font-family: var(--font-display); font-weight: 600; }
.clip i { width: 5.5rem; height: 18px; border-radius: 4px; background: repeating-linear-gradient(90deg, var(--accent) 0 2px, transparent 2px 4px); opacity: 0.9; mask-image: linear-gradient(90deg, transparent, #000 15%, #000 85%, transparent); -webkit-mask-image: linear-gradient(90deg, transparent, #000 15%, #000 85%, transparent); }
/* Bubble Reel: a conversation plays out on a phone */
.demo-chat { width: min(100%, 19.5rem); padding: 0.9rem; border-radius: 36px; margin: 0 auto; }
.phone { border-radius: 26px; background: #0b0d12; padding: 0.9rem 0.8rem 1rem; height: 23rem; display: flex; flex-direction: column; overflow: hidden; }
/* Do NOT give this a size container. It was tried: container-type: size means the box ignores its own contents, so
   with height:auto the phone collapsed to its padding, 37px, and clipped everything inside it. The aspect ratio
   lives on .msgs in deck.css instead, which needs no containment. */
.phone-top { display: flex; align-items: center; gap: 0.5rem; font-family: var(--font-display); font-weight: 600; font-size: 0.875rem; padding-bottom: 0.7rem; border-bottom: 1px solid rgba(255,255,255,0.1); margin-bottom: 0.8rem; }
.avatar { width: 26px; height: 26px; border-radius: 50%; background: var(--accent); color: var(--bg); display: grid; place-items: center; font-size: 0.75rem; }
.msgs { display: flex; flex-direction: column; gap: 0.45rem; margin-top: auto; }
.msg { max-width: 78%; padding: 0.5rem 0.8rem; border-radius: 18px; font-size: 1rem; line-height: 1.3; }
.msg.in { align-self: flex-start; background: #2a2d36; color: #fff; border-bottom-left-radius: 6px; }
.msg.out { align-self: flex-end; background: var(--accent); color: var(--bg); font-weight: 600; border-bottom-right-radius: 6px; }
.msg.typing { display: flex; gap: 4px; padding: 0.7rem 0.8rem; }
.msg.typing span { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,0.7); animation: dot 1.2s ease-in-out infinite; }
.msg.typing span:nth-child(2) { animation-delay: 0.2s; } .msg.typing span:nth-child(3) { animation-delay: 0.4s; }
@keyframes dot { 0%, 60%, 100% { transform: translateY(0); opacity: 0.5; } 30% { transform: translateY(-4px); opacity: 1; } }
.receipt { align-self: flex-end; font-size: 0.75rem; color: rgba(255,255,255,0.5); padding-right: 0.3rem; }
/* Ruff Cutter: transcript lines become a cut */
.lines { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.4rem; font-size: 0.875rem; color: rgba(255,255,255,0.65); }
.lines li { padding: 0.35rem 0.6rem; border-radius: 8px; border-left: 2px solid transparent; }
.lines li b { font-family: var(--font-display); font-weight: 600; font-variant-numeric: tabular-nums; color: rgba(255,255,255,0.5); margin-right: 0.5rem; font-size: 0.875rem; }
.lines li.pick { background: color-mix(in srgb, var(--accent) 16%, transparent); color: #fff; border-left-color: var(--accent); }
.lines li.pick b { color: var(--accent); }
.timeline { display: flex; gap: 4px; margin-top: 1rem; height: 26px; padding: 4px; border-radius: 8px; background: rgba(255,255,255,0.06); }
.timeline i { display: block; height: 100%; border-radius: 4px; background: var(--accent); }
.timeline .t1 { width: 34%; } .timeline .t2 { width: 24%; opacity: 0.8; } .timeline .t3 { width: 30%; opacity: 0.65; }
/* B-Roller keycaps and Kinora beats: small props beside the icon */
.keycaps { display: flex; gap: 0.5rem; margin-top: 1.5rem; justify-content: center; }
.keycaps kbd {
  font-family: var(--font-display); font-weight: 700; font-size: 1rem;
  width: 44px; height: 44px; border-radius: 10px; display: grid; place-items: center;
  background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 3px 0 rgba(0,0,0,0.45); color: #fff;
}
.keycaps kbd.wide { color: var(--accent); }
.beats { display: flex; gap: 5px; align-items: flex-end; height: 44px; margin-top: 1.5rem; justify-content: center; }
.beats i { width: 8px; border-radius: 4px 4px 2px 2px; background: var(--accent); height: 30%; }
.beats i:nth-child(3n) { height: 100%; } .beats i:nth-child(3n+1) { height: 55%; } .beats i:nth-child(4n) { height: 75%; }
/* Demo choreography: base styles are the finished state; animation plays from empty */
@media (prefers-reduced-motion: no-preference) {
  .demo *, .demo *::before { animation-play-state: paused; }
  .demo.is-playing *, .demo.is-playing *::before { animation-play-state: running; }
  .motion-paused .demo *, .motion-paused .demo *::before, .motion-paused .aurora span, .motion-paused .bands span { animation-play-state: paused !important; }
  .demo-bites .m2 { animation: fill2 9s linear infinite; }
  .demo-bites .c2 { animation: pop2 9s var(--ease) infinite; }
  /* THE FILL SWEEPS, THE INK JUMPS. Both used to be tweened together over the whole sweep, so for a second or so
     of every nine-second loop the word was mid-grey on pink: sampled at 1.8:1 and 2.2:1, on the demo that exists
     to show you words being highlighted. The colour now changes in a tenth of the sweep, just as the fill covers
     the word, so the legible white state hands straight over to the legible dark one. */
  @keyframes fill  { 0%, 6% { background-size: 0 100%; color: #fff; } 19% { color: #fff; } 21%, 100% { background-size: 100% 100%; color: var(--bg); } }
  @keyframes fill2 { 0%, 42% { background-size: 0 100%; color: #fff; } 55% { color: #fff; } 57%, 100% { background-size: 100% 100%; color: var(--bg); } }
  @keyframes pop  { 0%, 24% { opacity: 0; transform: translateY(8px); } 30%, 96% { opacity: 1; transform: none; } 100% { opacity: 0; } }
  @keyframes pop2 { 0%, 60% { opacity: 0; transform: translateY(8px); } 66%, 96% { opacity: 1; transform: none; } 100% { opacity: 0; } }
  .demo-chat .msg:not(.b1):not(.b2), .demo-chat .receipt { animation: bubble 11s var(--ease) infinite; }
  .demo-chat .b1 { --at: 4%; } .demo-chat .b2 { --at: 16%; } .demo-chat .b3 { --at: 28%; } .demo-chat .b4 { --at: 42%; } .demo-chat .b6 { --at: 76%; } .demo-chat .b7 { --at: 84%; }
  .demo-chat .b3 { animation-name: b3; } .demo-chat .b4 { animation-name: b4; } .demo-chat .b5 { animation-name: b5; } .demo-chat .b6 { animation-name: b6; } .demo-chat .b7 { animation-name: b7; }
  @keyframes b1 { 0%, 2% { opacity: 0; transform: translateY(10px) scale(0.96); } 5%, 97% { opacity: 1; transform: none; } 100% { opacity: 0; } }
  @keyframes b2 { 0%, 16% { opacity: 0; transform: translateY(10px) scale(0.96); } 20%, 96% { opacity: 1; transform: none; } 100% { opacity: 0; } }
  /* the bubbles take no room until they arrive, so the thread grows up out of the compose bar instead of hanging at the top of an empty phone */
  @keyframes b3 { 0%, 28% { opacity: 0; transform: translateY(10px) scale(0.96); max-height: 0; padding-top: 0; padding-bottom: 0; margin-top: -0.45rem; overflow: hidden; } 32%, 96% { opacity: 1; transform: none; max-height: 6rem; } 100% { opacity: 0; max-height: 6rem; } }
  @keyframes b4 { 0%, 42% { opacity: 0; transform: translateY(10px) scale(0.96); max-height: 0; padding-top: 0; padding-bottom: 0; margin-top: -0.45rem; overflow: hidden; } 46%, 96% { opacity: 1; transform: none; max-height: 6rem; } 100% { opacity: 0; max-height: 6rem; } }
  @keyframes b5 { 0%, 54% { opacity: 0; height: 0; padding-top: 0; padding-bottom: 0; margin-top: -0.45rem; } 58%, 72% { opacity: 1; height: auto; padding-top: 0.7rem; padding-bottom: 0.7rem; margin-top: 0; } 74%, 100% { opacity: 0; height: 0; padding-top: 0; padding-bottom: 0; margin-top: -0.45rem; } }
  @keyframes b6 { 0%, 76% { opacity: 0; transform: translateY(10px) scale(0.96); max-height: 0; padding-top: 0; padding-bottom: 0; margin-top: -0.45rem; overflow: hidden; } 80%, 96% { opacity: 1; transform: none; max-height: 6rem; } 100% { opacity: 0; max-height: 6rem; } }
  @keyframes b7 { 0%, 86% { opacity: 0; max-height: 0; margin-top: -0.3rem; overflow: hidden; } 90%, 96% { opacity: 1; max-height: 3rem; margin-top: 0; } 100% { opacity: 0; max-height: 3rem; } }
  .demo-cut .l2 { animation: pick 10s linear infinite; } .demo-cut .l4 { animation: pick2 10s linear infinite; } .demo-cut .l5 { animation: pick3 10s linear infinite; }
  .demo-cut .t1 { animation: grow 10s var(--ease) infinite; } .demo-cut .t2 { animation: grow2 10s var(--ease) infinite; } .demo-cut .t3 { animation: grow3 10s var(--ease) infinite; }
  @keyframes pick  { 0%, 12% { background: transparent; color: rgba(255,255,255,0.65); border-left-color: transparent; } 16%, 100% { background: color-mix(in srgb, var(--accent) 8%, transparent); color: rgba(255,255,255,0.86); border-left-color: color-mix(in srgb, var(--accent) 50%, transparent); } }
  @keyframes pick2 { 0%, 36% { background: transparent; color: rgba(255,255,255,0.65); border-left-color: transparent; } 40%, 100% { background: color-mix(in srgb, var(--accent) 8%, transparent); color: rgba(255,255,255,0.86); border-left-color: color-mix(in srgb, var(--accent) 50%, transparent); } }
  @keyframes pick3 { 0%, 60% { background: transparent; color: rgba(255,255,255,0.65); border-left-color: transparent; } 64%, 100% { background: color-mix(in srgb, var(--accent) 8%, transparent); color: rgba(255,255,255,0.86); border-left-color: color-mix(in srgb, var(--accent) 50%, transparent); } }
  @keyframes grow  { 0%, 18% { width: 0; } 26%, 100% { width: 34%; } }
  @keyframes grow2 { 0%, 42% { width: 0; } 50%, 100% { width: 24%; } }
  @keyframes grow3 { 0%, 66% { width: 0; } 74%, 100% { width: 30%; } }
}
@media (prefers-reduced-motion: reduce) { .demo-chat .typing { display: none; } }
/* Bento additions */
.cell-soon { --cell: #101015; flex-direction: row; align-items: center; gap: 1.25rem; min-height: 0; border: 1px solid rgba(255,255,255,0.1); }
.cell-soon .cell-soon-icon { width: 56px; height: 56px; border-radius: 14px; flex: 0 0 auto; filter: saturate(0.7); }
.cell-soon h3 { font-size: 1.35rem; }
.cell-soon p { font-size: 1rem; margin-top: 0.25rem; }
.cell-soon .chip { flex: 0 0 auto; margin-left: auto; }


/* B-Roller: cull a card from the keyboard */
.cull-row { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; }
.cull-row + .cull-row { margin-top: 6px; }
.cull-row.is-next i { opacity: 0.55; }
.cull-scrub { margin-top: 0.9rem; height: 6px; border-radius: 3px; background: rgba(255,255,255,0.1); position: relative; }
.cull-scrub::after { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 42%; border-radius: 3px; background: var(--accent); }
.cull-row i { position: relative; aspect-ratio: 16 / 9; border-radius: 6px; outline: 2px solid transparent; outline-offset: 2px; }
.cull-row .k1 { background: linear-gradient(135deg,#1f6f8b,#2a3d5c); }
.cull-row .k2 { background: linear-gradient(135deg,#8b5a2b,#3b2a1a); }
.cull-row .k3 { background: linear-gradient(135deg,#2a7f6f,#12303a); }
.cull-row .k4 { background: linear-gradient(135deg,#6d4c8f,#2a1538); }
.cull-row .k5 { background: linear-gradient(135deg,#a8552f,#3a1f14); }
.cull-row .k6 { background: linear-gradient(135deg,#3d5a80,#1e2d40); }
.cull-row i::after { content: ""; position: absolute; right: 4px; bottom: 4px; width: 16px; height: 16px; border-radius: 50%; font: 700 10px/16px var(--font-display); text-align: center; }
.cull-row .keep::after { content: "\2713"; background: var(--accent); color: var(--bg); }
.cull-row .drop::after { content: "\2715"; background: rgba(255,255,255,0.28); color: #fff; }
.cull-row .drop { filter: brightness(0.45) saturate(0.5); }
.cull-row .k6 { outline-color: var(--accent); }
.cull-foot { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-top: 1rem; }
.cull-foot .keycaps { margin-top: 0; justify-content: flex-start; gap: 0.35rem; }
.cull-foot .keycaps kbd { width: 34px; height: 34px; font-size: 0.875rem; border-radius: 8px; }
.cull-foot .keycaps kbd.lit { background: rgba(255,255,255,0.18); }
.cull-status { font-family: var(--font-display); font-weight: 600; font-size: 0.875rem; color: rgba(255,255,255,0.7); }
.cull-status .count { color: var(--accent); font-size: 1.1rem; }
@media (prefers-reduced-motion: no-preference) {
  .cull-row .k1 { animation: cull1 9s linear infinite; }
  .cull-row .k1::after { animation: badge1 9s linear infinite; }
  @keyframes cull1 { 0%, 2% { outline-color: transparent; } 3%, 14% { outline-color: var(--accent); } 15%, 100% { outline-color: transparent; } }
  @keyframes badge1 { 0%, 14% { opacity: 0; transform: scale(0.4); } 17%, 100% { opacity: 1; transform: none; } }
  .cull-row .k2 { animation: cull2 9s linear infinite; }
  .cull-row .k2::after { animation: badge2 9s linear infinite; }
  @keyframes cull2 { 0%, 16% { outline-color: transparent; filter: none; } 17%, 28% { outline-color: var(--accent); filter: none; } 29%, 100% { outline-color: transparent; filter: brightness(0.45) saturate(0.5); } }
  @keyframes badge2 { 0%, 28% { opacity: 0; transform: scale(0.4); } 31%, 100% { opacity: 1; transform: none; } }
  .cull-row .k3 { animation: cull3 9s linear infinite; }
  .cull-row .k3::after { animation: badge3 9s linear infinite; }
  @keyframes cull3 { 0%, 30% { outline-color: transparent; } 31%, 42% { outline-color: var(--accent); } 43%, 100% { outline-color: transparent; } }
  @keyframes badge3 { 0%, 42% { opacity: 0; transform: scale(0.4); } 45%, 100% { opacity: 1; transform: none; } }
  .cull-row .k4 { animation: cull4 9s linear infinite; }
  .cull-row .k4::after { animation: badge4 9s linear infinite; }
  @keyframes cull4 { 0%, 44% { outline-color: transparent; } 45%, 56% { outline-color: var(--accent); } 57%, 100% { outline-color: transparent; } }
  @keyframes badge4 { 0%, 56% { opacity: 0; transform: scale(0.4); } 59%, 100% { opacity: 1; transform: none; } }
  .cull-row .k5 { animation: cull5 9s linear infinite; }
  .cull-row .k5::after { animation: badge5 9s linear infinite; }
  @keyframes cull5 { 0%, 58% { outline-color: transparent; filter: none; } 59%, 70% { outline-color: var(--accent); filter: none; } 71%, 100% { outline-color: transparent; filter: brightness(0.45) saturate(0.5); } }
  @keyframes badge5 { 0%, 70% { opacity: 0; transform: scale(0.4); } 73%, 100% { opacity: 1; transform: none; } }
  .cull-row .k6 { animation: cull6 9s linear infinite; }
  @keyframes cull6 { 0%, 72% { outline-color: transparent; } 74%, 100% { outline-color: var(--accent); } }
}

/* Small icon beside the kicker on demo panels, so the mascots stay in the room */
.panel-demo .panel-kicker { display: inline-flex; }
.panel-head { display: flex; align-items: center; gap: 1.1rem; }
.panel-head > div { display: grid; gap: 0.5rem; min-width: 0; }
.panel-head .panel-name { font-size: clamp(2.6rem, 4.6vw, 4.5rem); }
.panel-app-icons { display: flex; gap: 0.5rem; flex: 0 0 auto; }
.panel-app-icon { display: block; flex: 0 0 auto; border-radius: 24%; overflow: hidden; width: 84px; height: 84px; box-shadow: 0 18px 40px -16px rgba(0,0,0,0.8), 0 0 0 1px rgba(255,255,255,0.1), 0 20px 60px -20px color-mix(in srgb, var(--accent) 55%, transparent); transition: transform 0.3s var(--ease), box-shadow 0.3s; }
.panel-app-icon img { width: 100%; height: 100%; display: block; }
.panel-app-icon:hover { transform: translateY(-3px) scale(1.03); }
.panel-app-icons .panel-app-icon { width: 72px; height: 72px; }
@media (min-width: 56rem) { .panel-app-icon { width: 104px; height: 104px; } .panel-app-icons .panel-app-icon { width: 84px; height: 84px; } }
.ladder li > a { display: block; border-radius: 14px; overflow: hidden; }
.ladder li > a:hover img { transform: scale(1.06); }
.ladder img { transition: transform 0.3s var(--ease); }
.bar-icon { width: 18px; height: 18px; border-radius: 5px; margin-right: 0.35rem; }
.demo-bar b { font-family: var(--font-display); font-weight: 600; font-size: 0.875rem; color: rgba(255,255,255,0.8); }
.panel-head { display: flex; align-items: center; gap: 0.9rem; }

/* ---------- Shelf: the rest of the apps on one screen ---------- */
.shelf { padding: clamp(3rem, 8vh, 6rem) clamp(1rem, 4vw, 3rem); max-width: 96rem; margin: 0 auto; }
.shelf-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem 1.5rem; margin-bottom: 1rem; }
.shelf-head h2 { font-family: var(--font-display); font-weight: 800; letter-spacing: -0.03em; font-size: clamp(2.2rem, 5vw, 4rem); line-height: 0.95; margin: 0; }
.shelf-head p { color: rgba(255,255,255,0.65); }
.shelf-grid { display: grid; gap: 1.5rem; grid-template-columns: 1fr; }
.shelf-card {
  --bg: #1e2d40; --accent: #fff;
  position: relative; display: flex; flex-direction: column; gap: 0.9rem;
  padding: clamp(1.5rem, 2.5vw, 2.25rem);
  border-radius: 24px; background: var(--bg);
  box-shadow: 0 1px 0 rgba(255,255,255,0.06) inset;
  overflow: hidden;
}
.shelf-card::before { content: ""; position: absolute; inset: 0; background: radial-gradient(70% 50% at 80% 0%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%); pointer-events: none; }
.shelf-card > * { position: relative; }
.shelf-icons { display: flex; gap: 0.75rem; margin-bottom: 0.25rem; }
.shelf-icons .panel-icon { width: 96px; box-shadow: 0 20px 40px -20px rgba(0,0,0,0.8), 0 0 0 1px rgba(255,255,255,0.08); animation: none; }
.shelf-icons .panel-icon:nth-child(2) { transform: rotate(5deg); }
.shelf-name { font-family: var(--font-display); font-weight: 800; letter-spacing: -0.025em; font-size: clamp(1.6rem, 2.2vw, 2.1rem); line-height: 1; color: var(--accent); margin: 0; }
.shelf-name .nw { white-space: nowrap; }
@media (min-width: 40rem) { .panel-name .nw { white-space: nowrap; } }
@media (max-width: 39.99rem) { .panel-head { flex-direction: column; align-items: flex-start; gap: 0.9rem; } .panel-head > div { width: 100%; } }
.shelf-name .amp { font-weight: 300; color: rgba(255,255,255,0.55); }
.shelf-desc { color: rgba(255,255,255,0.86); font-size: 1rem; line-height: 1.45; }
.shelf-card .panel-actions { margin-top: auto; }
@media (min-width: 64rem) { .shelf-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: stretch; } .shelf-card { height: 100%; } }   /* minmax(0,...) so no card's min-content widens its column */
.shelf .cell-soon { margin-top: 1rem; }
.shelf .cell-soon h3 { font-family: var(--font-display); font-weight: 800; font-size: 1.35rem; margin: 0; }

/* Hero variant: the four brand colors as skewed bands, no blur */
.hero.is-bands .aurora { display: none; }
.hero-inner { position: relative; }
.hero.is-bands .hero-inner::before { content: ""; position: absolute; inset: -10% -5%; z-index: -1; background: radial-gradient(55% 60% at 30% 50%, rgba(11,13,18,0.6), transparent 72%); pointer-events: none; }
.bands { position: absolute; inset: 0; z-index: -1; overflow: hidden; display: none; }
.hero.is-bands .bands { display: block; }
.bands span {
  position: absolute; top: -20%; bottom: -20%; width: 26vw;
  transform: skewX(-18deg);
  opacity: 0.46;
  filter: blur(4px);
}
.bands span:nth-child(1) { left: -8vw; background: linear-gradient(180deg, var(--coral), transparent 88%); animation: band 30s ease-in-out infinite alternate; }
.bands span:nth-child(2) { left: 20vw; background: linear-gradient(180deg, var(--amber), transparent 88%); animation: band 36s ease-in-out infinite alternate-reverse; }
.bands span:nth-child(3) { left: 58vw; background: linear-gradient(180deg, var(--teal), transparent 88%); animation: band 33s ease-in-out infinite alternate; }
.bands span:nth-child(4) { left: 78vw; background: linear-gradient(180deg, var(--slate), transparent 88%); animation: band 40s ease-in-out infinite alternate-reverse; }

@keyframes band { from { transform: skewX(-18deg) translateX(-3vw); } to { transform: skewX(-18deg) translateX(3vw); } }
@media (prefers-reduced-motion: reduce) { .bands span { animation: none !important; } }
.cull-row .k7 { background: linear-gradient(135deg,#4a6b3a,#1f2d1a); }
.cull-row .k8 { background: linear-gradient(135deg,#2b4a6b,#141f2d); }
.cull-row .k9 { background: linear-gradient(135deg,#7a3b3b,#2d1414); }
.cull-row .k10 { background: linear-gradient(135deg,#5a5a2b,#2d2d14); }
.cull-row .k11 { background: linear-gradient(135deg,#2b6b5a,#142d26); }
.cull-row .k12 { background: linear-gradient(135deg,#6b4a2b,#2d1f14); }

/* Family ladder (B-Roller) */
.ladder { list-style: none; margin: 0.75rem 0 0; padding: 0; display: grid; gap: 0.75rem; }
.ladder li { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 0.9rem; padding: 0.75rem 0.75rem 0.75rem 1rem; border-radius: 16px; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08); }
.ladder img { width: 56px; height: 56px; border-radius: 14px; }
.ladder b { display: block; font-family: var(--font-display); font-weight: 700; font-size: 1rem; }
.ladder span { display: block; font-size: 0.875rem; color: rgba(255,255,255,0.75); line-height: 1.4; }
.ladder .chip { display: inline-flex; font-size: 0.75rem; padding: 0.15rem 0.5rem; margin-left: 0.25rem; vertical-align: middle; }
.btn-sm { padding: 0.7rem 1.1rem; font-size: 0.875rem; white-space: nowrap; min-height: 44px; }
@media (max-width: 30rem) { .ladder li { grid-template-columns: minmax(0, 1fr); } .ladder li .btn { justify-self: start; } }
.chip-tier { font-size: 0.75rem; padding: 0.1rem 0.5rem; margin-left: 0.35rem; vertical-align: middle; color: #fff; background: color-mix(in srgb, var(--accent) 28%, transparent); }
/* Two demos stacked for the family */
.demo-column { display: grid; gap: 2rem; width: min(100%, 34rem); }
.demo-column .demo { width: 100%; }
/* AudioDesc: reads the words the speaker skipped */
.desc-frame { position: relative; border-radius: 10px; overflow: hidden; aspect-ratio: 16 / 9; background: radial-gradient(80% 60% at 50% 30%, #2a2f3a, #14171e 80%); }
.slide { position: absolute; left: 8%; right: 8%; top: 6%; height: 55%; background: #f6f3ec; color: #1e2d40; border-radius: 6px; padding: 0.7rem 0.9rem; box-shadow: 0 10px 30px -10px rgba(0,0,0,0.8); }
/* The visitor types into this slide, and it is a fixed box inside a frame that also holds the presenter: letting it
   grow covered her by up to 23px, and letting it clip cost the last bullet 7px at 1440x900. So the box stays put and
   the TITLE yields, taking whatever height is left once the bullets have theirs. No line count is chosen, because
   the right number differs at every size: a title fits on one line in a 175px slide and on three in a tall one.
   (A fixed -webkit-line-clamp was tried and was inert anyway, since common.css sets display:block on this element
   with higher specificity. Flex shrink needs no cooperation from the display value.) The description under the
   slide still reads every word of a long title, which is the thing this demo exists to show. */
.slide { display: flex; flex-direction: column; }
.slide b { flex: 0 1 auto; min-height: 0; overflow: hidden;
           font-family: var(--font-display); font-weight: 700; font-size: 1rem; line-height: 1.1; margin-bottom: 0.35rem; }
.slide ul { flex: 0 0 auto; margin: 0; padding-left: 1rem; font-size: 0.75rem; line-height: 1.35; color: #3d4a5c; }   /* the bullets keep their room; the title is what gives */
/* the room under the screen: a presenter standing in the light, so the frame reads as a recording of a talk
   rather than a slide floating in a gradient. Everything here is decoration behind the caption. */
.desc-room { display: none; }
@media (min-width: 56rem) and (hover: hover) { .desc-room { display: block; } }   /* only where the frame keeps its 16:9 shape; on phones and touch the slide and caption stack */
.desc-room { position: absolute; left: 0; right: 0; top: 62%; bottom: 0; overflow: hidden; pointer-events: none; }
.desc-room::before { content: ""; position: absolute; left: 5%; right: 5%; top: 14%; height: 1px; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.12) 20%, rgba(255,255,255,0.12) 80%, transparent); }
.desc-pool { position: absolute; left: 2%; bottom: -46%; width: 40%; aspect-ratio: 2 / 1; border-radius: 50%; background: radial-gradient(closest-side, rgba(255,255,255,0.11), transparent 70%); }
/* the presenter: head and shoulders rising from the bottom edge, the caption crossing at chest height */
.desc-figure { position: absolute; left: 7%; bottom: 0; width: 19%; height: 96%; }
.desc-figure::before { content: ""; position: absolute; left: 61%; top: 2%; width: 40%; aspect-ratio: 1; transform: translateX(-50%); border-radius: 50%; background: #3a4358; }   /* turned toward the screen, so it is a person and not a profile icon */
.desc-figure::after { content: ""; position: absolute; left: 0; right: 0; top: 34%; bottom: 0; border-radius: 38% 56% 0 0 / 30% 46% 0 0; background: #3a4358; }
.desc-caption { position: absolute; left: 0.6rem; right: 0.6rem; bottom: 0.55rem; display: grid; }
.cap { grid-area: 1 / 1; display: flex; gap: 0.45rem; align-items: flex-start; font-size: 0.875rem; line-height: 1.3; color: #fff; padding: 0.4rem 0.5rem; border-radius: 6px; background: rgba(11,13,18,0.75); }
.cap b { flex: 0 0 auto; font-family: var(--font-display); font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; padding: 0.15rem 0.35rem; border-radius: 4px; background: rgba(255,255,255,0.25); }
.cap-ad b { background: var(--accent); color: var(--bg); }
.cap-sp { visibility: hidden; }
/* The two captions are stacked and revealed in turn. With no script every animation is killed, which left the
   speaker line visible at full opacity underneath the description instead of taking its turn: two chips and two
   sentences printed on top of each other. The resting state is the description, so that is what stands still. */
.no-js .demo-desc .cap-sp { visibility: hidden !important; }
.no-js .demo-desc .cap-ad { opacity: 1 !important; }
.desc-track { display: flex; gap: 4px; margin-top: 0.9rem; height: 22px; padding: 4px; border-radius: 8px; background: rgba(255,255,255,0.06); }
.desc-track i { display: block; height: 100%; border-radius: 4px; }
.desc-track .dg { background: rgba(255,255,255,0.22); flex: 3; }
.desc-track .ad { background: var(--accent); flex: 4; }
.desc-legend { display: flex; gap: 1rem; align-items: center; margin-top: 0.6rem; font-size: 0.75rem; color: rgba(255,255,255,0.65); }
.desc-legend span { display: inline-flex; align-items: center; gap: 0.35rem; }
.desc-legend i { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
.desc-legend .sw-d { background: rgba(255,255,255,0.3); } .desc-legend .sw-a { background: var(--accent); }
.desc-legend em { margin-left: auto; font-style: normal; }
@media (prefers-reduced-motion: no-preference) {
  .demo-desc .cap-sp { visibility: visible; animation: capsp 12s linear infinite; }
  .demo-desc .cap-ad { animation: capad 12s linear infinite; }
  .demo-desc .ad1 { animation: adgrow 12s linear infinite; }
  .demo-desc .dg2 { animation: dg2 12s linear infinite; }
  @keyframes capsp { 0%, 4% { opacity: 0; } 8%, 34% { opacity: 1; } 38%, 100% { opacity: 0; } }
  @keyframes capad { 0%, 38% { opacity: 0; } 42%, 94% { opacity: 1; } 98%, 100% { opacity: 0; } }
  @keyframes adgrow { 0%, 38% { flex: 0.01; } 60%, 100% { flex: 4; } }
  @keyframes dg2 { 0%, 60% { flex: 0.01; } 70%, 100% { flex: 3; } }
}
/* Shelf: bigger icons, a dimmed coming-soon card */
.shelf-icons .panel-icon { width: 120px; }
.shelf-card-soon { border: 1px solid rgba(245,197,24,0.18); }
.shelf-card-soon .panel-icon.is-dim img { filter: saturate(0.6) brightness(0.8); }
.apps { padding-bottom: 2rem; }
.shelf { padding-top: clamp(4rem, 9vh, 6.5rem); padding-bottom: clamp(3rem, 6vh, 4rem); }
.bento { padding-top: clamp(4rem, 9vh, 6.5rem); }

/* Ladder tier marks */
.tier { display: grid; place-items: center; width: 56px; height: 56px; border-radius: 14px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.12); font-family: var(--font-display); font-weight: 700; font-size: 0.75rem; letter-spacing: 0.06em; text-transform: uppercase; color: rgba(255,255,255,0.75); }
.tier-top { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
@media (max-width: 30rem) { .tier { width: 48px; height: 48px; } }
/* Shelf minis */
.mini { border-radius: 12px; background: rgba(11,13,18,0.45); border: 1px solid rgba(255,255,255,0.1); padding: 0.9rem; }
.mini-kinora .photos { display: flex; gap: 6px; margin-bottom: 0.7rem; }
.mini-kinora .photos i { flex: 1 1 0; min-width: 0; aspect-ratio: 4 / 3; border-radius: 5px; border: 3px solid #f6f3ec; box-shadow: 0 6px 14px -6px rgba(0,0,0,0.8); }
.mini-kinora .photos i:nth-child(1) { background: linear-gradient(160deg, #6b8cae, #2f4b6e); transform: rotate(-3deg); }
.mini-kinora .photos i:nth-child(2) { background: linear-gradient(160deg, #d9a066, #7a4a2b); transform: rotate(2deg); }
.mini-kinora .photos i:nth-child(3) { background: linear-gradient(160deg, #7fb59a, #2f5d4a); transform: rotate(-1deg); }
.mini-kinora .photos i:nth-child(4) { background: linear-gradient(160deg, #c97b8d, #5a2f45); transform: rotate(3deg); }
.mini-kinora .beats { margin-top: 0; height: 28px; justify-content: space-between; }
.mini-kinora .beats i { width: 5px; }
.mini-songs { display: grid; gap: 0.45rem; }
.song { display: grid; grid-template-columns: 5.5rem 1fr 18px; align-items: center; gap: 0.6rem; font-size: 0.75rem; color: rgba(255,255,255,0.7); }
.song b { font-family: var(--font-display); font-weight: 600; font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.song i { display: block; height: 18px; border-radius: 3px; opacity: 0.55; background: repeating-linear-gradient(90deg, currentColor 0 2px, transparent 2px 4px); mask-image: linear-gradient(0deg, transparent 10%, #000 50%, transparent 90%); -webkit-mask-image: linear-gradient(0deg, transparent 10%, #000 50%, transparent 90%); color: #fff; }
.song.is-keeper i { color: var(--accent); opacity: 1; }
.song em { font-style: normal; width: 18px; height: 18px; border-radius: 50%; display: grid; place-items: center; }
.song.is-keeper em { background: var(--accent); }
.song.is-keeper em::after { content: ""; box-sizing: border-box; width: 30%; height: 54%; border: solid var(--bg); border-width: 0 2px 2px 0; transform: translateY(-12%) rotate(45deg); }   /* a drawn tick: no font on the page has the glyph, and the fallback's was synthetic-bold */
.song.is-keeper b { color: #fff; }
@media (prefers-reduced-motion: no-preference) {
  .mini-kinora .beats i { animation: beat 1.8s ease-in-out infinite; transform-origin: bottom; }
  .mini-kinora .beats i:nth-child(2n) { animation-delay: 0.3s; } .mini-kinora .beats i:nth-child(3n) { animation-delay: 0.6s; } .mini-kinora .beats i:nth-child(5n) { animation-delay: 0.9s; }
  @keyframes beat { 0%, 100% { transform: scaleY(0.55); } 50% { transform: scaleY(1); } }
  .motion-paused .mini-kinora .beats i { animation-play-state: paused; }
}

/* Phone: the family panel in one screen */
@media (max-width: 56rem) {
  .ladder li { background: none; border: 0; border-top: 1px solid rgba(255,255,255,0.1); border-radius: 0; padding: 0.85rem 0; }
  .demo-column .demo-cull { display: none; }
}
/* Lower Key: a lower third builds in */
.mini-cg { padding: 0.6rem; }
.cg-frame { position: relative; aspect-ratio: 16 / 9; border-radius: 8px; overflow: hidden; background: radial-gradient(70% 60% at 60% 30%, #3a4a5c, #171b22 80%); }
.cg-bug { position: absolute; top: 8%; right: 6%; font: 700 0.75rem/1 var(--font-display); letter-spacing: 0.12em; color: #fff; background: #b8391c; padding: 0.25rem 0.45rem; border-radius: 3px; font-style: normal; }
.cg-third { position: absolute; left: 6%; bottom: 10%; right: 30%; border-left: 4px solid var(--accent); padding: 0.4rem 0.6rem; background: linear-gradient(90deg, rgba(11,13,18,0.92), rgba(11,13,18,0.55) 80%, transparent); }
.cg-third b { display: block; font-family: var(--font-display); font-weight: 700; font-size: 0.95rem; color: #fff; line-height: 1.1; }
.cg-third span { display: block; font-size: 0.75rem; color: var(--accent); margin-top: 0.15rem; }
@media (prefers-reduced-motion: no-preference) {
  .mini-cg .cg-third { animation: thirdin 8s var(--ease) infinite; }
  @keyframes thirdin { 0%, 8% { transform: translateX(-110%); opacity: 0; } 16%, 84% { transform: none; opacity: 1; } 92%, 100% { transform: translateX(-110%); opacity: 0; } }
  .motion-paused .mini-cg .cg-third { animation-play-state: paused; }
}

/* ---------- Figures: the app subjects, out of their squares ---------- */
.fig { display: block; }
.fig img { width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 22px 28px rgba(0,0,0,0.55)) drop-shadow(0 0 36px color-mix(in srgb, var(--accent) 38%, transparent)); transition: transform 0.45s var(--ease); }
.fig:hover img { transform: translateY(-6px) rotate(-4deg) scale(1.04); }
.fig:focus-visible { outline-offset: 10px; border-radius: 30%; }
/* the lockup figure: the subject leads the name */
.panel-head { display: flex; align-items: center; gap: clamp(1rem, 2.5vw, 2rem); }
.fig-head { flex: 0 0 auto; width: clamp(6.5rem, 9vw, 8.5rem); aspect-ratio: 1; }
.fig-head.fig-wide { width: clamp(7.5rem, 11vw, 10.5rem); aspect-ratio: 4 / 3; }
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .fig-head img { animation: figdrift linear both; animation-timeline: view(); animation-range: entry 0% exit 100%; }
    @keyframes figdrift { from { translate: 0 1.25rem; rotate: -3deg; } to { translate: 0 -1.25rem; rotate: 3deg; } }
  }
}
@media (max-width: 39.99rem) {
  .fig-head, .fig-head.fig-wide { width: 7rem; }
}
/* shelf figures: perched on the card's top-right corner */
.shelf-card { overflow: visible; }
.shelf-card::before { border-radius: 24px; overflow: hidden; }
.fig-card { position: absolute; top: -1.75rem; right: 0.5rem; width: clamp(6rem, 8vw, 8rem); aspect-ratio: 1; transform: rotate(8deg); }
.fig-card.is-dim img { filter: saturate(0.6) brightness(0.8) drop-shadow(0 18px 24px rgba(0,0,0,0.6)); }
.shelf-card > .panel-kicker { margin-top: 2.5rem; }
.shelf-grid { padding-top: 3rem; }

/* the current page in the menus */
.top-sub a[aria-current], .mobile-menu a[aria-current] { text-decoration: underline; text-decoration-color: var(--amber); text-decoration-thickness: 2px; text-underline-offset: 5px; }
.top-toggle.active { box-shadow: inset 0 -2px 0 var(--amber); border-radius: 999px 999px 0 0; }

/* At 320 the wordmark broke onto a second line and grew the nav from 73 to 80px. A slightly smaller letter and a
   tighter gutter keep "Sounds & Colors" on one line; every control still clears 44px. */
@media (max-width: 20.5rem) {
  .top { padding-left: 0.75rem; padding-right: 0.75rem; gap: 0.3rem; }
  .brand { font-size: 0.95rem; white-space: nowrap; }
  .brand .brand-dot { margin-right: -0.15rem; }
}

/* ---------- Print ----------
   DERIVED, NOT ENUMERATED. The first version of this listed the selectors that needed a light background removed
   and a dark colour applied, and the things that were not on the list printed invisibly: the whole footer as a
   black slab, every system requirement line white on white, and the ampersand joining AudioDesc and PhotoDesc.
   Listing is the wrong shape for this job, because the list can only ever contain what somebody happened to look
   at. So: everything loses its ground and takes ink, and the few things that need to differ say so afterwards.
   Nothing can print invisibly, because nothing keeps a light colour. */
@media print {
  *, *::before, *::after {
    background: none !important; background-color: transparent !important;
    color: #222 !important; box-shadow: none !important; filter: none !important;
    text-shadow: none !important; border-color: #bbb !important; opacity: 1 !important;
  }
  html, body { background: #fff !important; }
  h1, h2, h3, h4, b, strong, .shelf-name, .sec-name, .frame h4 { color: #111 !important; }
  a { color: #111 !important; text-decoration: underline !important; }
  /* A card is a single link wrapped round a heading and a paragraph, so underlining "every link" underlined whole
     paragraphs of body copy. A link that contains block content is a card, not a phrase, and cards are not
     underlined on paper any more than they are on screen. */
  a:has(p), a:has(h2), a:has(h3), a:has(h4), a:has(ul) { text-decoration: none !important; }
  /* chrome, motion and the playable mock-ups: none of it survives the trip to paper */
  .top, .strip, .deck-jump, .deck-pause, .strip-pause, .aurora, .bands, .demo, .mini, .obj,
  .demo-badge, .demo-go, .demo-done, .demo-hint, .sheet-tab, .skip-link, .cell-mark, .frame-fig, .fig, .sec-fig { display: none !important; }
  .sheet::after, .sheet::before, .shelf-card::before, .hero-inner::before, .wordmark::before { content: none !important; }
  /* the stack was six pinned layers; on paper it is one list */
  .sheet { position: static !important; height: auto !important; min-height: 0 !important; overflow: visible !important;
           clip-path: none !important; transform: none !important; break-inside: avoid; margin-bottom: 1.5rem; }
  .sheet-inner, .sheet-inner-wide { display: block !important; height: auto !important; padding: 0 !important; transform: none !important; }
  .hero { min-height: 0 !important; }   /* a whole sheet of paper for a wordmark and four lines is a waste of it */
  .shelf-grid, .frames { display: block !important; }
  /* !important, because a media block adds no specificity: .lab-deck .shelf-card-soon { border: 0 } outranks a bare
     .shelf-card and the Lower Key card printed without the outline its five siblings got. Same for the padding,
     which .lab-deck .sheet .shelf-card sets. */
  .shelf-card, .frames li { break-inside: avoid; margin-bottom: 1rem !important; border: 1px solid #ccc !important; padding: 0.75rem !important; }
  /* a printed link is useless without its address, inside and outside the site alike */
  a[href^="/"]::after { content: " (soundsandcolors.com" attr(href) ")"; font-size: 0.85em; color: #555 !important; font-weight: 400; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.85em; color: #555 !important; font-weight: 400; }
  a[href^="mailto:"]::after { content: none !important; }   /* the address is already the link's text; printing "(mailto:...)" after it says it twice, with a scheme nobody reads */
  /* ...unless it is not. The footer's link is the word "Email", and it printed with no address among twelve links
     that all carried theirs. js/site.js puts the bare address on any mail link whose text is not already one. */
  a[data-print]::after { content: " (" attr(data-print) ")" !important; font-size: 0.85em; color: #555 !important; font-weight: 400; }
  .top a::after { content: none !important; }   /* the nav does not print */
  h1 a::after, h2 a::after, h3 a::after, h4 a::after { content: none !important; }   /* an address inside a 40px heading swamps the name */
  .btn, .chip { border: 1px solid #999 !important; }
  @page { margin: 18mm 16mm; }
}

/* ---------- The tour ----------
   A third control in the hero, for the visitor who would rather watch than scroll. It is a link to the film on
   YouTube until the script turns it into a dialog, so it works either way and asks for nothing until it is
   clicked. The player inside the dialog is ours: see the long note over the tour IIFE in tools/build-home.mjs for
   why the YouTube embed had to go (its chrome cannot be retimed, restyled, or added to from outside the iframe). */
.btn-play { display: inline-flex; align-items: center; gap: 0.6rem; }
.play-glyph { width: 22px; height: 22px; border-radius: 50%; flex: 0 0 auto; position: relative;
              background: currentColor; opacity: 0.92; }
.play-glyph::after { content: ""; position: absolute; left: 8px; top: 6px; width: 0; height: 0;
                     border-left: 8px solid var(--bg, #0b0d12); border-top: 5px solid transparent;
                     border-bottom: 5px solid transparent; }
.btn-play:hover .play-glyph { opacity: 1; }
.tour-dialog { border: 0; padding: 0; background: transparent; max-width: min(92vw, 68rem); width: 100%; }
.tour-dialog::backdrop { background: rgba(6,7,10,0.88); backdrop-filter: blur(6px); }
.tour-frame { position: relative; aspect-ratio: 16 / 9; width: 100%; border-radius: 12px; overflow: hidden;
              background: #000; box-shadow: 0 40px 90px -30px rgba(0,0,0,0.95);
              max-height: calc(100svh - 7rem); }   /* a phone on its side: the bar and Close stay on screen, the picture letterboxes */
/* THE CAPTION TRACK, when it is turned on: the site's body face, sized to be read on a phone, on the same dark
   glass as the bar. The line sits above the bar's band so a paused film does not print words through its controls.
   AND THE FILM'S OWN CAPTION GETS COVERED WHILE IT DOES. These films carry their captions burned into the bottom
   of the picture, so switching the track on printed every sentence twice, stacked, the lower one cut off by the
   frame. The burned-in band is the bottom 13% of the picture and nothing else is down there, so it is painted out
   for as long as the track is doing the job instead. */
/* THE CAPTION LINE. tour.js paints the active cue here rather than letting the browser draw it, so it sits in
   the same place at every frame size. It stands in the band below, which covers the caption the film carries
   burned into its own picture: without that cover, switching the track on printed every sentence twice. */
.tour-dialog.is-cc .tour-frame::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 13%;
                                        background: #0b0d12; z-index: 1; }
.tour-cap {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 2.5%; z-index: 3;
  margin: 0; max-width: 92%; width: max-content; text-align: center; text-wrap: balance;
  font-family: var(--font-body); font-size: clamp(0.8rem, 1.5vw + 0.4rem, 1.3rem); line-height: 1.3;
  color: #fff; background: rgba(6,8,12,0.86); padding: 0.25em 0.6em; border-radius: 6px;
  white-space: pre-line;   /* the cue text is wrapped by hand, two short lines rather than one long one */
}
.tour-cap[hidden] { display: none; }
/* and the bar steps up out of the caption's way, as a player's bar does, so a film paused to be read is not
   paused under its own controls */
.tour-dialog.is-cc .tour-bar { bottom: 26%; }
.tour-cc[aria-pressed="true"] { background: rgba(255,255,255,0.22); box-shadow: inset 0 -2px 0 #fff; }
.tour-frame:fullscreen { border-radius: 0; aspect-ratio: auto; height: 100%; }
.tour-video { width: 100%; height: 100%; display: block; object-fit: contain; background: #000; cursor: pointer; }

/* THE CONTROL BAR. Play/pause first, at the left, which is where a hand goes for it; then the time bar, then the
   clock, then sound and full screen at the far end. Set in the site's own display face, on the site's own dark
   glass, so it belongs to this page rather than to a video host. */
.tour-bar {
  /* THE FILM CARRIES ITS OWN CAPTIONS, BURNED INTO THE BOTTOM BAND. There is no <track>, so those pixels are the
     only captions a deaf viewer gets, and a bar sitting flush at the bottom ran its seek track straight through
     them: on open, on every pause, and for a second after every pointer move, which are the moments a viewer is
     most likely to be reading. Lifted clear of that band. If the film is ever re-cut with the captions elsewhere,
     this is the number to revisit. */
  position: absolute; left: 0; right: 0; bottom: 8%; z-index: 2;
  display: flex; align-items: center; gap: 0.5rem; padding: 0.75rem 0.85rem 0.7rem;
  background: linear-gradient(to top, rgba(6,8,12,0.9), rgba(6,8,12,0.72) 58%, transparent);
  border-radius: 10px; margin-inline: 0.6rem;
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.tour-btn {
  flex: 0 0 auto; width: 44px; height: 44px; display: grid; place-items: center;
  border: 0; border-radius: 10px; background: transparent; color: #fff; cursor: pointer;
  transition: background 0.18s;
}
.tour-btn svg { width: 22px; height: 22px; fill: currentColor; pointer-events: none; }
.tour-btn:hover { background: rgba(255,255,255,0.16); }
.tour-btn:focus-visible { outline: 3px solid #fff; outline-offset: -3px; }
.tour-clock { flex: 0 0 auto; color: #fff; font-family: var(--font-display); font-weight: 600; font-size: 0.85rem;
              font-variant-numeric: tabular-nums; letter-spacing: 0.01em; opacity: 0.92; padding: 0 0.2rem; }
.tour-clock .tour-of { opacity: 0.68; }

/* The time bar is a real range input, so dragging, tapping, arrow keys, Home and End all work without a line of
   code, and a screen reader gets a slider that reads out as a time rather than as a number out of a thousand.
   --played is written by the script and paints the part already watched. */
.tour-seek {
  flex: 1 1 auto; min-width: 0; height: 44px; margin: 0; padding: 0; background: transparent;
  -webkit-appearance: none; appearance: none; cursor: pointer;
}
.tour-seek::-webkit-slider-runnable-track {
  height: 5px; border-radius: 999px;
  background: linear-gradient(to right, #fff 0 var(--played, 0%), rgba(255,255,255,0.3) var(--played, 0%) 100%);
}
.tour-seek::-moz-range-track { height: 5px; border-radius: 999px; background: rgba(255,255,255,0.3); }
.tour-seek::-moz-range-progress { height: 5px; border-radius: 999px; background: #fff; }
.tour-seek::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 14px; height: 14px; margin-top: -4.5px;
  border: 0; border-radius: 50%; background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,0.6);
}
.tour-seek::-moz-range-thumb { width: 14px; height: 14px; border: 0; border-radius: 50%; background: #fff;
                               box-shadow: 0 1px 4px rgba(0,0,0,0.6); }
.tour-seek:focus-visible { outline: 3px solid #fff; outline-offset: 4px; border-radius: 999px; }

.tour-frame.is-broken .tour-bar { display: none; }
.tour-sorry { position: absolute; inset: auto 1.5rem 45%; text-align: center; color: #fff;
              font-family: var(--font-display); font-size: 1.05rem; line-height: 1.5; }
.tour-sorry a { color: #fff; text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 0.2em; }
.tour-close { display: block; margin: 1rem auto 0; min-height: 44px; padding: 0.6rem 1.4rem; border-radius: 999px;
              border: 1px solid rgba(255,255,255,0.28); background: rgba(255,255,255,0.08); color: #fff;
              font-family: var(--font-display); font-weight: 600; font-size: 0.95rem; cursor: pointer;
              transition: opacity 0.25s ease, background 0.18s; }
.tour-close:hover { background: rgba(255,255,255,0.16); }
.tour-close:focus-visible { outline: 3px solid #fff; outline-offset: 3px; }

/* The bar gets out of the way while the film plays. The Close button goes with it only while the pointer is over
   the picture (is-over), because that is the only time it is in anybody's way; off the film it stays, since that
   is where a hand reaching for it will be. */
.tour-dialog.is-idle .tour-bar { opacity: 0; transform: translateY(6px); pointer-events: none; }
.tour-dialog.is-idle.is-over .tour-close { opacity: 0; pointer-events: none; }
.tour-dialog.is-idle.is-over .tour-video { cursor: none; }
@media (prefers-reduced-motion: reduce) {
  .tour-bar, .tour-close { transition: none; }
  .tour-dialog.is-idle .tour-bar { transform: none; }
}
/* a phone in landscape has no room for five controls and a clock, so the clock goes and the bar tightens */
@media (max-width: 30rem) {
  .tour-bar { gap: 0.25rem; padding: 0.5rem 0.4rem 0.45rem; }
  .tour-clock { display: none; }
}

/* ---------- Forced colours (Windows High Contrast) ----------
   The transcript demo paints its highlight with a gradient and reverses the type out of it. In forced colours the
   pair collapses, so "the whole town showed up", "shot it in one afternoon" and "Somebody's kid ran the slate"
   came out as blank gaps in the middle of the paragraph: the demo whose whole point is highlighting words showed
   an unhighlighted sentence with holes in it. The system's own highlight pair is exactly the right answer here. */
@media (forced-colors: active) {
  /* !important, because the highlight's keyframes animate colour and an animation outranks a normal declaration:
     the first attempt at this rule shipped and two of the three phrases still drew white on yellow at 1.07:1 for
     most of every nine-second cycle. !important is one of the few things that beats an animation. */
  .demo-transcript mark, .demo-transcript .mk {
    forced-color-adjust: none !important; background: Mark !important; color: MarkText !important;
  }
  .spine, .spine b, .spine-name { forced-color-adjust: none; }
  /* Naming a button here is not the same as giving it an edge. .demo-go paints its own background and has no
     border, so in forced colours it lost both and "Export the rough cut" became bold text floating in space. */
  .demo-go, .demo-try { border: 1px solid currentColor !important; }
}
