/* case-study.css: case study pages: header, slides, footnotes, Venn, stat count-up, pill cloud, App Store carousel, scroll cue, back link. Loaded on every page, because Swup swaps pages without reloading <head>. */

/* case study */
.cs-header h1 { font-size: var(--text-large-title); line-height: var(--leading-large-title); }
.cs-header .tags { margin-top: var(--space-4); }
/* inline icons in the brief: the Maureen app icon after "iOS app", and grey tool logos after each tool's name.
   Sized to the text (about cap height + a bit), sitting on the baseline; logos are muted so they don't compete. */
.inline-app-icon { display: inline-block; width: 1.2em; height: 1.2em; vertical-align: -0.25em; border-radius: 22.5%; }
.inline-logo { display: inline-block; width: .9em; height: .9em; vertical-align: -0.1em; color: var(--muted); }
.nowrap { white-space: nowrap; }   /* a name never splits from its logo */
.cs-brief { margin-top: var(--space-6); max-width: var(--measure); font-size: var(--text-title3); line-height: var(--leading-title2); }   /* the lead: a step up from body. 28px leading, not Title 3's 24, because it runs several lines */
@media (min-width: 768px) { .cs-header h1 { font-size: var(--text-display); line-height: var(--leading-display); } }
.cs-meta {   /* Role, Timeline, Status in a black card, matching the home page's details panel */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-6);
  margin: 0; padding: var(--pad-card);
  background: var(--stack); color: var(--on-stack); border-radius: var(--radius-card);
}
.cs-meta dd { margin: var(--space-1) 0 0; }
/* a fact with a link (Status → the App Store): the text, then a small white circle with a ↗ arrow (the home card's
   arrow button, smaller). On the black card it fills with Maureen's teal on hover, since the usual black would vanish. */
.meta-link { display: flex; align-items: center; gap: var(--space-3); }
.store-link { flex: none; width: var(--space-8); height: var(--space-8); }   /* 32px */
.store-link .icon { width: var(--space-4); height: var(--space-4); rotate: -45deg; }   /* → becomes ↗ */
.store-link:hover, .store-link:focus-visible { background: #37727e; color: var(--on-stack); }
/* header: the lead (title, pills, brief) and the facts card. Phones and tablets: stacked, the card's facts
   side by side. 900px+: the card condenses to a right column, top-aligned with the TITLE, so it sits beside
   the whole lead instead of hanging down past the brief into the page body. */
.cs-header { display: grid; gap: var(--space-8); }
.cs-meta { row-gap: var(--space-4); }
@media (min-width: 900px) {
  .cs-header { grid-template-columns: minmax(0, 1fr) var(--space-80); column-gap: var(--space-16); align-items: start; }
  .cs-header .cs-meta { grid-template-columns: 1fr; padding: var(--space-8); }
}
.cs-meta dt { font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--on-stack-muted); }
.case-study section h2 { margin-bottom: var(--space-4); }
/* Case studies use the full content width (--container), like the rest of the site. Running text keeps a
   comfortable measure; images, infographics and other media take the whole width. */
.case-study p, .case-study section > ul, .case-study section > ol, .slide-text > ul { max-width: var(--measure); }
.case-study p + p { margin-top: var(--space-4); }
/* lists in a section's body: under the measure, a little indented, 8px between items */
.case-study section > ul:not(.tags), .slide-text > ul { margin: var(--space-4) 0 0; padding-left: var(--space-5); }
.case-study section > ul:not(.tags) li + li, .slide-text > ul li + li { margin-top: var(--space-2); }
.slide-text > p + ul { margin-top: var(--space-2); }   /* a list introduced by a sentence sits close under it */
.slide-text > ul + p { margin-top: var(--space-4); }   /* text after a list: the same gap as between paragraphs */
/* bullets: the normal dot, in Maureen's teal */
.case-study section > ul:not(.tags) > li::marker, .slide-text > ul > li::marker { color: #37727e; }
@media (prefers-color-scheme: dark) { .case-study section > ul:not(.tags) > li::marker, .slide-text > ul > li::marker { color: #4f8894; } }
/* bold lead-ins in slide text (e.g. the two user needs on 02) take Maureen's teal, like the bullets */
.slide-text strong { color: #37727e; }
@media (prefers-color-scheme: dark) { .slide-text strong { color: #4f8894; } }
/* footnotes: a superscript number after the claim; the source sits with the visual it supports (e.g. under the
   big stat), in small muted type. */
.fn-ref { font-size: .65em; line-height: 0; vertical-align: super; margin-left: 1px; }
.fn-ref a { color: var(--muted); text-decoration: none; }
.cs-notes { margin-top: var(--space-6); }
.cs-note { font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--muted); }
.cs-note + .cs-note { margin-top: var(--space-2); }
.cs-note a { color: inherit; }
.fn-num { margin-right: var(--space-1); }
/* Venn (Identifying the ideal user): solid Maureen teals, the overlap its own mid teal (#45818e), white type */
.cs-venn svg { display: block; width: 100%; height: auto; overflow: visible; }
.venn-a { fill: #37727e; }
.venn-b { fill: #4f8894; }
.venn-ab { fill: #45818e; }
.venn-centre { fill: #fff; font: 600 var(--text-subhead) / 1 var(--font-body); }
.venn-label { fill: rgba(255,255,255,.85); font: 400 var(--text-sm) / 1 var(--font-body); }
/* Case study "slides": each section is at least one screen tall (below the fixed header), its content centred
   vertically, so the page reads as a story, one moment per screen, with no background changes. Normal scrolling
   (no snapping). 900px+: text and visual side by side, alternating sides (.flip puts the text on the right).
   .center: left-anchored text over a full-width visual (or alone). Phones: text, then visual. */
.case-study .slide {
  display: grid; align-content: center; gap: var(--space-10);
  min-height: calc(100svh - var(--header-h));
  padding-block: var(--space-16);
}
.case-study main > .slide + .slide, .case-study .slide + .slide { margin-top: 0; }   /* the slides' own height separates them */
.cs-header + .slide { margin-top: var(--space-8); }
.slide .slide-visual { margin: 0; min-width: 0; }   /* beats the cloud's and others' own top margins: the slide's gap spaces them */
.slide .shots { margin-top: 0; }
.slide-label {   /* a small number above each heading: 01, 02… */
  margin: 0 0 var(--space-2); font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--muted);
  font-variant-numeric: tabular-nums; letter-spacing: .04em;
}
/* .center (full-width slides): text above, left-anchored like the rest of the site; the visual spans the full width */
.slide.center { justify-items: start; }
.slide.center .slide-visual { justify-self: stretch; }
@media (min-width: 900px) {
  .slide:not(.center) { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--space-16); align-items: center; }
  .slide.flip .slide-text { order: 2; }
  /* full-width slides keep their text to the same width as a two-column slide's text column */
  .slide.center .slide-text { max-width: calc((100% - var(--space-16)) / 2); }
}
/* 01: the big stat */
.stat-num { margin: 0; font-size: clamp(var(--space-12), 7vw, 96px); line-height: 1; font-weight: 600; letter-spacing: -.02em; color: #37727e; font-variant-numeric: tabular-nums; }
.stat-cap { margin-top: var(--space-4); font-size: var(--text-title3); line-height: var(--leading-title2); max-width: 32ch; }
.stat .cs-notes { margin-top: var(--space-6); max-width: 40ch; }
/* 02: the Venn, capped so it doesn't overwhelm its half */
.slide .cs-venn { max-width: 480px; width: 100%; justify-self: center; }
/* Case study hero: the home page's landing hero reused (.hero-sky: same sky, glow, grain, top-rounded corners,
   eased fade into the page), shorter and without the plane. The phone is centred and rises from the bottom,
   dissolving with the same mask as the home card. data-tone tells nav-tone.js it's dark above the fade. */
.cs-hero {
  --sky-fade: var(--space-40);   /* 160px: a shorter card gets a shorter fade */
  /* Maureen's brand gradient, the same teal as the home card's hover tint (#37727e at the top to #4f8894),
     fed into the home hero's sky layers: base, horizon glow and fade */
  --sky-top: #37727e; --sky-mid: #4f8894; --sky-low: #8fb5bd; --sky-pale: #d9e7ea;
  position: relative; isolation: isolate;
  display: flex; justify-content: center; align-items: flex-end;
  margin: 0 var(--hero-inset);   /* same insets as the home hero: full width on phones, page gutter from 600px */
  height: clamp(var(--space-80), 56vw, 600px);   /* 320px on phones, up to 600px on wide screens */
  padding-top: var(--space-12);
}
.cs-hero img {
  display: block; height: 100%; width: auto;
  translate: var(--media-shift, 0) 0;   /* centre the PHONE, not the image (the hand enters from the left) */
  -webkit-mask-image: linear-gradient(to bottom, #000 64%, rgb(0 0 0 / .9) 70%, rgb(0 0 0 / .7) 76%, rgb(0 0 0 / .45) 82%, rgb(0 0 0 / .2) 88%, rgb(0 0 0 / .06) 94%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 64%, rgb(0 0 0 / .9) 70%, rgb(0 0 0 / .7) 76%, rgb(0 0 0 / .45) 82%, rgb(0 0 0 / .2) 88%, rgb(0 0 0 / .06) 94%, transparent 100%);
}
.cs-hero + main { padding-top: var(--space-6); }
/* Back link on the case study hero: chevron + text, no pill, white on the sky, on the content's left edge
   (inside the inset card: the content edge, never closer than --hero-pad to the card edge, like the home hero text). */
.back-link {
  position: absolute; z-index: 1; top: var(--space-6);
  left: max(var(--hero-pad), calc((100% - var(--container)) / 2 + var(--gutter)));   /* same rule as the home hero text */
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding-block: var(--space-2);   /* a taller tap target without a visible box */
  color: var(--on-sky); text-decoration: none;
  font-size: var(--text-body); line-height: var(--leading-body); font-weight: 500;
  transition: opacity 200ms;
}
.back-link .icon { width: var(--space-5); height: var(--space-5); margin-left: -4px; }   /* optical: the chevron's stroke sits inside its box */
.back-link:hover, .back-link:focus-visible { opacity: .75; }
/* a project card targeted by a back link lands below the fixed header, not under it */
.card-stack { scroll-margin-top: calc(var(--header-h) + var(--space-6)); }
/* Restraint pill cloud (assets/js/cloud.js): every prototyped feature as a white pill straight on the page (no card), under
   the section's text. Cuts strike through and pop out, the rest slide together and turn Maureen teal; 17 → 11. */
.cloud { margin: var(--space-8) 0 0; }
.cloud-card { position: relative; }   /* no card: the pills sit straight on the page */
.cloud-head { display: flex; align-items: baseline; gap: var(--space-3); margin: 0 0 var(--space-6); }
.cloud-count { font-size: clamp(var(--space-12), 5vw, 72px); line-height: 1; letter-spacing: -.02em; font-weight: 600; color: #37727e; font-variant-numeric: tabular-nums; }
.cloud-label { font-size: var(--text-title1); line-height: var(--leading-title1); font-weight: 200; color: var(--text); }
.cloud-list { display: flex; flex-wrap: wrap; align-content: flex-start; align-items: flex-start; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }   /* rows pack to the top: the locked height must never stretch the pills */
.cloud-list li {
  position: relative; padding: var(--space-2) var(--space-3); border-radius: 999px;
  background: transparent; box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--text) 20%, transparent);   /* outline only, so they read as labels, not buttons (an inset stroke doesn't change the pill's size) */
  font-size: var(--text-subhead); line-height: var(--leading-subhead);
  transition: transform 350ms cubic-bezier(.2, .8, .2, 1), opacity 200ms ease, background-color 300ms, color 300ms, box-shadow 300ms;
}
.cloud-list li::after {   /* the strike-through, drawn left to right */
  content: ""; position: absolute; left: var(--space-3); right: var(--space-3); top: 50%; height: 1.5px; background: currentColor;
  transform: scaleX(0); transform-origin: left; transition: transform 250ms ease;
}
.cloud-list li.is-struck { color: var(--muted); }
.cloud-list li.is-struck::after { transform: scaleX(1); }
.cloud-list li.is-popping { transform: scale(.6); opacity: 0; }
.cloud-list li.is-gone { display: none; }
.cloud-list li.is-shipped { color: #45818e; box-shadow: inset 0 0 0 1px #45818e; }   /* shipped: the outline and text turn Maureen teal (the Venn overlap), still no fill */
/* a two-word swap with the rotator's motion (drift + fade, no clipping): both words share one grid cell */
.swap { display: inline-grid; }
.swap-word { grid-area: 1 / 1; white-space: nowrap; opacity: 0; translate: 0 .3em; transition: none; }
.swap-word.is-on  { opacity: 1; translate: 0 0; transition: opacity 260ms ease-out 100ms, translate 260ms cubic-bezier(.2, .8, .2, 1) 100ms; }
.swap-word.is-off { opacity: 0; translate: 0 -.3em; transition: opacity 150ms ease-in, translate 150ms ease-in; }
@media (prefers-reduced-motion: reduce) { .cloud-card * { transition: none !important; } }
@property --fade-l { syntax: '<length>'; inherits: true; initial-value: 0px; }
@property --fade-r { syntax: '<length>'; inherits: true; initial-value: 0px; }
.shots-wrap { position: relative; grid-column: 1 / -1; }   /* spans both section columns on desktop */
.case-study section > .shots-wrap { grid-column: 1 / -1; }   /* beats the sections' column-1 rule */
.shots-btn { display: none; }   /* arrows are desktop only; touch screens swipe */
/* App Store screenshot carousel (What shipped): a horizontal row that scrolls / swipes and snaps to each shot.
   Phones: a tight carousel that bleeds to the screen edges, about 1.5 shots visible so it's clearly swipeable.
   600px+: about 2.5. 900px+: four across the full content width, the rest a scroll away. */
.shots {
  margin: var(--space-8) calc(var(--gutter) * -1) 0;   /* bleed to the screen edges on small screens… */
  padding-inline: var(--gutter);                        /* …while the first shot still lines up with the text */
  scroll-padding-inline: var(--gutter);
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scrollbar-width: none;
}
.shots::-webkit-scrollbar { display: none; }
.shots:focus-visible { outline: 2px solid var(--text); outline-offset: 4px; }
.shots ul { display: flex; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
.shots li { flex: 0 0 62%; scroll-snap-align: start; }
.shots img { display: block; width: 100%; height: auto; border-radius: var(--space-5); }
@media (min-width: 600px) {
  .shots li { flex-basis: 38%; }
}
@media (min-width: 900px) {
  .shots { margin-inline: 0; padding-inline: 0; scroll-padding-inline: 0;
    /* fade the edges that have more to see (the row doesn't bleed off the screen here, so it would otherwise cut hard) */
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--fade-l), #000 calc(100% - var(--fade-r)), transparent 100%);
            mask-image: linear-gradient(to right, transparent 0, #000 var(--fade-l), #000 calc(100% - var(--fade-r)), transparent 100%);
    transition: --fade-l 250ms ease, --fade-r 250ms ease;
  }
  .shots-wrap { --fade-l: var(--space-16); --fade-r: var(--space-16); }
  .shots-wrap.at-start { --fade-l: 0px; }
  .shots-wrap.at-end { --fade-r: 0px; }
  /* arrows: the nav's white circle, centred on each edge over the fade; hidden when there's nowhere to go */
  .shots-btn { display: grid; position: absolute; top: calc(50% + var(--space-4)); translate: 0 -50%;   /* +16px: half the carousel's 32px top margin, which sits inside the wrapper */ z-index: 1; border: 0; cursor: pointer;
    width: var(--space-12); height: var(--space-12); transition: background-color 200ms, color 200ms, opacity 200ms; }
  .shots-prev { left: calc(var(--space-6) * -1); }
  .shots-next { right: calc(var(--space-6) * -1); }
  .shots-wrap.at-start .shots-prev, .shots-wrap.at-end .shots-next { opacity: 0; pointer-events: none; }
  .shots li { flex-basis: calc((100% - 3 * var(--space-2)) / 4); }   /* four across, 8px gaps */
}
.cs-media { margin: var(--space-8) 0 0; }   /* a figure: an image, video or infographic, full content width */
.cs-media img, .cs-media video, .cs-media svg { display: block; width: 100%; height: auto; border-radius: var(--radius-card); }
.cs-media figcaption { margin-top: var(--space-3); max-width: var(--measure); font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--muted); }
/* two figures side by side from 768px (e.g. before / after, two screens) */
.cs-media-pair { display: grid; gap: var(--space-4); margin: var(--space-8) 0 0; }
@media (min-width: 768px) { .cs-media-pair { grid-template-columns: 1fr 1fr; } }
.cs-media-pair .cs-media { margin: 0; }

/* Scroll cue (case study), fixed at the bottom centre. Above the first slide: a chevron bobbing gently, a nudge to
   scroll. From slide 01 the progress ("03 / 06", scroll-cue.js) replaces it, crossfading in the same spot. The
   whole cue fades out once "More from this project" is in view (.is-done). Decorative only. */
.scroll-cue {
  position: fixed; left: 50%; bottom: var(--space-6); z-index: 5; translate: -50% 0;
  display: grid; place-items: center;
  color: var(--muted); pointer-events: none;
  transition: opacity 400ms ease;
}
.scroll-cue > * { grid-area: 1 / 1; transition: opacity 300ms ease; }
.scroll-cue svg { display: block; width: var(--space-6); height: var(--space-6); animation: cue-bob 2.4s ease-in-out infinite; }
.cue-count { opacity: 0; font-size: var(--text-sm); line-height: var(--leading-sm); font-variant-numeric: tabular-nums; letter-spacing: .04em; white-space: nowrap; }
.scroll-cue.has-count svg { opacity: 0; animation-play-state: paused; }
.scroll-cue.has-count .cue-count { opacity: 1; }
.scroll-cue.is-done { opacity: 0; }
.scroll-cue.is-done svg { animation-play-state: paused; }   /* nothing animates while hidden */
@keyframes cue-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(6px); } }
@media (prefers-reduced-motion: reduce) { .scroll-cue svg { animation: none; } }
/* count-up (count-up.js): the decimal is its own box so it can ease shut at the end */
.count-dec { display: inline-block; overflow: hidden; vertical-align: bottom; white-space: nowrap; }
.count-dec.is-closing { max-width: 0 !important; opacity: 0; transition: max-width 450ms cubic-bezier(.2, .8, .2, 1), opacity 250ms ease-out; }
/* Venn animation (venn.js): .is-apart slides the two circles (with their labels) 44 units out each way so they
   no longer touch, and hides the overlap and "Maureen". Removing it eases them together; the overlap and name
   fade in once they've met. Transform and opacity only, so it stays on the compositor. */
.venn-side { transition: transform 900ms cubic-bezier(.65, 0, .35, 1); }
.venn-join { transition: opacity 400ms ease-out; }
.cs-venn.is-apart .venn-left  { transform: translateX(-44px); }
.cs-venn.is-apart .venn-right { transform: translateX(44px); }
/* labels: centred in their own circle while apart (joined x 88 / 272 vs circle centres 128 / 232, so 40 units in),
   easing out to the sides as the circles merge, leaving the middle for "Maureen" */
.venn-side .venn-label { transition: transform 900ms cubic-bezier(.65, 0, .35, 1); }
.cs-venn.is-apart .venn-left .venn-label  { transform: translateX(40px); }
.cs-venn.is-apart .venn-right .venn-label { transform: translateX(-40px); }
.cs-venn.is-apart .venn-join, .cs-venn.is-joining .venn-join { opacity: 0; transition-duration: 250ms; }
/* Phone screen recordings (video.js): no bezel, just the iPhone screen's own rounded corners (about 55pt on a
   402pt-wide screen: 13.7% of the width, which is 6.3% of the height at this aspect) and a hairline edge, so it
   reads as a phone and matches the App Store shots. Capped so a portrait video doesn't tower over its half. */
.phone-video { justify-self: center; width: min(100%, 320px); margin: 0 auto; }   /* auto sides centre it in any parent: the panel is a plain block, where Safari ignores justify-self (and a <figure>'s default 40px sides overflowed it) */
.phone-video video {
  display: block; width: 100%; height: auto; aspect-ratio: 590 / 1282;
  border-radius: 13.7% / 6.3%; background: var(--chip);
  box-shadow: 0 0 0 1px var(--line), 0 var(--space-6) var(--space-12) rgb(0 0 0 / .06);
}
/* Tab bar clips (05, video.js): short, wide crops of just the bar, stacked in order (custom → native) with a
   small label above each. No phone frame: rounded like the site's cards, a hairline edge, the clip's own
   light background. */
.bar-clips { display: grid; gap: var(--space-6); align-content: center; margin: 0 var(--space-10); }   /* 40px in from each side */
.bar-clip { margin: 0; }
.bar-clip figcaption { margin-bottom: var(--space-2); font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--muted); }
.bar-clip video {
  display: block; width: 100%; height: auto; aspect-ratio: 900 / 186;
  border-radius: var(--space-6); background: var(--chip);
  box-shadow: 0 0 0 1px var(--line);
}
/* Examples switcher (examples.js): small pills above a visual, like an iOS segmented control. The active pill
   is Maureen teal. Panels sit in ONE grid cell, so the visual keeps the height of the tallest example and the
   slide never jumps; the inactive ones fade out and can't be clicked. Pills hide while there's one example. */
.examples { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-6); align-content: center; }   /* one column, exactly the frame's width */
.ex-pills { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.ex-pills:has(> :only-child) { display: none; }
.ex-pills [role="tab"] {
  height: calc(var(--space-8) + var(--space-1));   /* 36px */ padding: 0 var(--space-4); border: 0; border-radius: 999px;
  background: var(--surface); color: var(--text);   /* white, like the cloud pills */
  font: 500 var(--text-sm) / 1 var(--font-body); cursor: pointer;
  transition: background-color 200ms ease, color 200ms ease;
}
.ex-pills [role="tab"]:hover { background: var(--chip); }
.ex-pills [role="tab"][aria-selected="true"] { background: #37727e; color: #fff; }
.ex-pills [role="tab"]:focus-visible { outline: 2px solid #37727e; outline-offset: 2px; }
.ex-panels { display: grid; grid-template-columns: minmax(0, 1fr); }   /* one column exactly the card's width: a video's own (590px) size can't widen it, which pushed the phone right in iOS Safari */
.ex-panel { grid-area: 1 / 1; align-self: start;   /* every example starts right under the pills */ opacity: 0; visibility: hidden; transition: opacity 250ms ease, visibility 0s linear 250ms; }
.ex-panel.is-active { opacity: 1; visibility: visible; transition: opacity 250ms ease 100ms, visibility 0s; }
@media (prefers-reduced-motion: reduce) { .ex-panel, .ex-panel.is-active { transition: none; } }
/* Slide skies (sky.js): a fixed layer behind the page, one gradient per slide in the Maureen family. Plain
   page colour for the top third of the screen (where headings and text sit), the tint gathering towards the
   bottom edge, so it's clearly visible without sitting behind the copy. Each fades in over the last (700ms); the layer is clear
   outside the slides. Add a .sky-N here when a slide is added; a slide without one just keeps the previous tint.
   Height 100lvh (the screen with Safari's bars hidden), so on iPhone the gradient carries on behind the bottom
   bar instead of stopping above it; sky.js also tints the page colour to match (--page-tint on body). */
.slide-sky { position: fixed; inset: 0 0 auto; height: 100vh; height: 100lvh; z-index: -1; pointer-events: none; opacity: 0; transition: opacity 700ms ease; }
.slide-sky.is-on { opacity: 1; }
.sky-layer { position: absolute; inset: 0; opacity: 0; transition: opacity 700ms ease; }
.sky-layer.is-on { opacity: 1; }
.sky-1 { background: linear-gradient(to bottom, #f7f7f7 0%, #f7f7f7 35%, #daeaee 100%); }   /* 01 the market: teal mist */
.sky-2 { background: linear-gradient(to bottom, #f7f7f7 0%, #f7f7f7 35%, #d8ebe7 100%); }   /* 02 the ideal user: sea glass */
.sky-3 { background: linear-gradient(to bottom, #f7f7f7 0%, #f7f7f7 35%, #d6ebf0 100%); }   /* 03 the core interactions: aqua */
.sky-4 { background: linear-gradient(to bottom, #f7f7f7 0%, #f7f7f7 35%, #dfe6ee 100%); }   /* 04 the design library: blue-grey */
.sky-5 { background: linear-gradient(to bottom, #f7f7f7 0%, #f7f7f7 35%, #d8e7ee 100%); }   /* 05 every screen: harbour blue */
.sky-6 { background: linear-gradient(to bottom, #f7f7f7 0%, #f7f7f7 35%, #d9ecee 100%); }   /* 06 restraint: pale teal */
.sky-7 { background: linear-gradient(to bottom, #f7f7f7 0%, #f7f7f7 35%, #d7e8ec 100%); }   /* 07 onboarding: Maureen teal */
.sky-8 { background: linear-gradient(to bottom, #f7f7f7 0%, #f7f7f7 35%, #dce6f3 100%); }   /* 08 native: pale iOS blue */
.sky-9 { background: linear-gradient(to bottom, #f7f7f7 0%, #f7f7f7 35%, #d9e8f2 100%); }   /* 09 the listing: sky blue */
.sky-10 { background: linear-gradient(to bottom, #f7f7f7 0%, #f7f7f7 35%, #e6ecec 100%); }   /* 10 outcomes: settling back to the page */
@media (prefers-color-scheme: dark) {
  .sky-1 { background: linear-gradient(to bottom, #111 0%, #111 35%, #152c2a 100%); }
  .sky-2 { background: linear-gradient(to bottom, #111 0%, #111 35%, #13291f 100%); }
  .sky-3 { background: linear-gradient(to bottom, #111 0%, #111 35%, #0f282d 100%); }
  .sky-4 { background: linear-gradient(to bottom, #111 0%, #111 35%, #1a2129 100%); }
  .sky-5 { background: linear-gradient(to bottom, #111 0%, #111 35%, #12262e 100%); }
  .sky-6 { background: linear-gradient(to bottom, #111 0%, #111 35%, #122a2c 100%); }
  .sky-7 { background: linear-gradient(to bottom, #111 0%, #111 35%, #0f2c30 100%); }
  .sky-8 { background: linear-gradient(to bottom, #111 0%, #111 35%, #162130 100%); }
  .sky-9 { background: linear-gradient(to bottom, #111 0%, #111 35%, #13233a 100%); }
  .sky-10 { background: linear-gradient(to bottom, #111 0%, #111 35%, #181e1e 100%); }
}
@media (prefers-reduced-motion: reduce) { .slide-sky, .sky-layer { transition: none; } }
/* Form sheets example (05): phone screens side by side (custom → native), each with the iPhone screen's corners
   like the onboarding video, a label above. Images and videos share .phone-screen. */
.sheet-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-6); align-items: start; }
.sheet-shot { margin: 0; }
.sheet-shot figcaption { margin-bottom: var(--space-2); font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--muted); }
.phone-screen {
  display: block; width: 100%; height: auto; aspect-ratio: 590 / 1283;
  border-radius: 13.7% / 6.3%; background: var(--chip);
  box-shadow: 0 0 0 1px var(--line), 0 var(--space-6) var(--space-12) rgb(0 0 0 / .06);
}
/* Progressive blur example (05): crops of the top of the screen, custom → native stacked, rounded like the site's cards */
.blur-pair { display: grid; gap: var(--space-6); margin: 0 var(--space-10); }   /* stacked, the same width as the tab bar clips */
.blur-clip { margin: 0; }
.blur-clip figcaption { margin-bottom: var(--space-2); font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--muted); }
.blur-clip video { display: block; width: 100%; height: auto; aspect-ratio: 900 / 538; border-radius: var(--space-6); background: var(--chip); box-shadow: 0 0 0 1px var(--line); }
/* label above a phone recording (the tool's name), styled like the tab bar clip labels; each tool's panel has
   its own, so it changes with the switcher */
.phone-video .screen-label { margin-bottom: var(--space-2); font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--muted); }
/* The pills stay on one row at every size and scroll sideways when they don't fit (like iOS filter chips),
   rather than wrapping onto a second line. No scrollbar. Wider screens: each edge fades out while there's
   more to scroll that way, as a hint. Phones: the row runs to the screen edges and fades out there the same way. */
.ex-pills { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
.ex-pills::-webkit-scrollbar { display: none; }
.ex-pills [role="tab"] { flex: none; }
/* each edge fades only while there's more to scroll that way: --fl (left) and --fr (right) are 0 or 48px */
.ex-pills.is-overflowing {
  --fl: var(--space-12); --fr: var(--space-12);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--fl), #000 calc(100% - var(--fr)), transparent);
          mask-image: linear-gradient(to right, transparent, #000 var(--fl), #000 calc(100% - var(--fr)), transparent);
}
.ex-pills.is-overflowing.is-at-start { --fl: 0px; }
.ex-pills.is-overflowing.is-at-end { --fr: 0px; }
@media (max-width: 599px) { .ex-pills.is-overflowing { --fl: var(--space-8); --fr: var(--space-8); } }   /* a shorter 32px fade on a narrow row */
@media (max-width: 599px) {
  .ex-pills { margin-inline: calc(var(--gutter) * -1); padding-inline: var(--gutter); scroll-padding-inline: var(--gutter); }
}
/* Auto-cycling switcher (examples.js, data-autoplay): the active pill shows how far its video has played,
   a light sweep across the teal from left to right. Hidden once the viewer takes over. */
@property --p { syntax: '<percentage>'; inherits: false; initial-value: 0%; }
.examples.is-auto .ex-pills [role="tab"][aria-selected="true"] {
  background: linear-gradient(to right, rgb(255 255 255 / .22) var(--p), transparent var(--p)), #37727e;
  transition: --p 300ms linear;
}
/* .ex-card (slide 03): the pills and the phone share one rounded frame, so the pills read as part of the demo
   rather than separate buttons. Pills centred over the phone. A faint 24px grid (a design-canvas feel) that fades out
   at the edges, no outline and no fill, so the slide tint shows through. */
.examples.ex-card {
  justify-self: center; width: min(100%, 480px);
  padding: var(--space-6) var(--space-6) var(--space-8);
  border-radius: var(--space-10);
  position: relative; isolation: isolate;
}
/* the grid lives on its own layer so it can fade out softly at every edge (no outline); the mask is static,
   painted once, so it costs nothing while scrolling */
.examples.ex-card::before, .grid-card::before {
  content: ""; position: absolute; inset: calc(var(--space-12) * -1); z-index: -1; pointer-events: none;   /* 48px past the frame on every side; absolute, so it adds no spacing */
  border-radius: var(--space-16);   /* 64px: the grid area reads as a rounded card; the background is clipped to it */
  --grid: color-mix(in oklab, var(--text) 7%, transparent);
  background:
    linear-gradient(to right, var(--grid) 1px, transparent 1px) 0 0 / var(--space-6) var(--space-6),
    linear-gradient(to bottom, var(--grid) 1px, transparent 1px) 0 0 / var(--space-6) var(--space-6);
  /* fade only in the 48px band outside the frame, on all four sides: two straight fades, intersected
     (not an oval, so the grid stays full behind the pills at the top) */
  --edge: var(--space-6);   /* a softer, narrower fade so the rounded shape still reads */
  -webkit-mask-image:
    linear-gradient(to right, transparent, #000 var(--edge), #000 calc(100% - var(--edge)), transparent),
    linear-gradient(to bottom, transparent, #000 var(--edge), #000 calc(100% - var(--edge)), transparent);
  -webkit-mask-composite: source-in;
          mask-image:
    linear-gradient(to right, transparent, #000 var(--edge), #000 calc(100% - var(--edge)), transparent),
    linear-gradient(to bottom, transparent, #000 var(--edge), #000 calc(100% - var(--edge)), transparent);
          mask-composite: intersect;
}
.examples.ex-card .ex-pills { justify-content: center; }
.examples.ex-card .ex-pills.is-overflowing { justify-content: flex-start; }   /* centring would hide the first pills when the row scrolls */
.examples.ex-card .screen-label { text-align: center; }
@media (max-width: 599px) {
  .examples.ex-card { padding: var(--space-4) var(--space-4) var(--space-6); border-radius: var(--space-8); }
  .examples.ex-card .ex-pills { margin-inline: calc(var(--space-4) * -1); padding-inline: var(--space-4); }
  /* phones: the grid stops at the screen edge (the 16px gutter) and fades out inside it, rather than running
     48px past and getting cut off by the screen, which read as bleeding off the edge */
  .examples.ex-card::before, .grid-card::before { inset-inline: calc(var(--gutter) * -1); }
}
/* the wide grid frame (07 native examples): fills the column; the clips drop their own side insets, since
   the frame's padding now does that job */
.examples.ex-card.ex-card-wide { width: 100%; }
.ex-card .bar-clips, .ex-card .blur-pair { margin-inline: 0; }
.ex-card .bar-clip figcaption, .ex-card .blur-clip figcaption, .ex-card .sheet-shot figcaption { text-align: center; }

/* Design library stage (slide 04, lib-stage.js): Maureen's real components, rebuilt from the app's values
   (portfolio/_source/maureen-design-library), shown a step at a time on the faded grid. Each component is a
   "target" with its own Figma-style selection frame: outline, square handles, a name/size tag. Between steps
   every frame shrinks to a point, clipping its component away, then grows out around the next. A step can
   hold one big component or two small ones side by side. --fw / --fh (a target's frame content size) are
   animated custom properties; the component clips to them while they move. --sel is the outline colour:
   Figma's selection blue, or its component purple (data-color="purple"). */
.grid-card { position: relative; isolation: isolate; }
@property --fw { syntax: '<length>'; inherits: true; initial-value: 0px; }
@property --fh { syntax: '<length>'; inherits: true; initial-value: 0px; }
.lib-stage { justify-self: center; width: 100%; padding: var(--space-6); margin: 0; }
.lib-canvas { display: grid; grid-template: minmax(0, 1fr) / minmax(0, 1fr); place-items: center; place-items: unsafe center; height: var(--space-80); }   /* one fixed cell: a wide hidden step can't widen the track and push the others off centre */
.lib-item { grid-area: 1 / 1; display: flex; align-items: center; justify-content: center; gap: var(--space-32); visibility: hidden;
  justify-self: start; position: relative; left: 50%; translate: -50% 0; }   /* centred by offset, not grid alignment, so it stays centred in Safari even if it's ever wider than the canvas */   /* 128px: room for each frame's tag, which can be wider than a small component */
.lib-item.is-on { visibility: visible; }
.lib-item[data-stack="column"] { flex-direction: column; gap: var(--space-16); }   /* stacked steps: 64px between frames, room for the lower tag */
.lib-target {
  position: relative; display: grid; place-items: center;   /* lib-stage.js gives the part negative margins, so its layout box is exactly the scaled size: centred, nothing overflowing */
  --pad: var(--space-2); --sel: #0d99ff;   /* frame sits 8px outside the component */
  transition: --fw 380ms cubic-bezier(.65, 0, .35, 1), --fh 380ms cubic-bezier(.65, 0, .35, 1);
}
.lib-target[data-color="purple"] { --sel: #9747ff; }
.lib-part { transform: scale(var(--s, 1));
  clip-path: inset(calc((100% - var(--fh) / var(--s, 1)) / 2) calc((100% - var(--fw) / var(--s, 1)) / 2)); }   /* clips exactly to the component's edge, so at 0 nothing is left (no padded sliver); shadows return once settled */
.lib-canvas.is-settled .lib-part { clip-path: none; }   /* at rest nothing is clipped, so shadows show in full */
.lib-frame {
  position: absolute; left: 50%; top: 50%; pointer-events: none;
  width: calc(var(--fw) + var(--pad) * 2); height: calc(var(--fh) + var(--pad) * 2); translate: -50% -50%;
  box-shadow: inset 0 0 0 1px var(--sel);
}
.lib-frame i {   /* 8 resize handles: corners and edge midpoints */
  position: absolute; width: 7px; height: 7px; margin: -3.5px; background: #fff; box-shadow: inset 0 0 0 1px var(--sel);
  transition: opacity 150ms ease;
}
.lib-frame i:nth-child(1) { left: 0; top: 0; }    .lib-frame i:nth-child(2) { left: 50%; top: 0; }
.lib-frame i:nth-child(3) { left: 100%; top: 0; } .lib-frame i:nth-child(4) { left: 100%; top: 50%; }
.lib-frame i:nth-child(5) { left: 100%; top: 100%; } .lib-frame i:nth-child(6) { left: 50%; top: 100%; }
.lib-frame i:nth-child(7) { left: 0; top: 100%; } .lib-frame i:nth-child(8) { left: 0; top: 50%; }
.lib-tag {
  position: absolute; left: -1px; bottom: calc(100% + var(--space-2)); white-space: nowrap;
  padding: 2px 6px; border-radius: 3px; background: var(--sel); color: #fff;
  font: 500 11px / 16px var(--font-body); font-variant-numeric: tabular-nums;
  transition: opacity 150ms ease;
}
.lib-tag .lib-size { opacity: .75; margin-left: 4px; }
/* deselect, swap, select: while the frames shrink and regrow, the whole selection (outline, handles, tag) is
   hidden, so only the clip wipes the component; it fades back in once the next component is fully revealed */
.lib-frame { transition: opacity 120ms ease; }
.lib-canvas:not(.is-settled) .lib-frame { opacity: 0; }
.lib-canvas.is-settled .lib-frame { transition: opacity 200ms ease 60ms; }

/* The components, scoped to the stage. Values from the app via the exported recreations. */
.lib-stage { --m-page: #F4F4F4; --m-label: #F4F4F4; --m-rest: rgb(255 255 255 / .80); --m-on: #E3ECEE; --m-on-border: #45818E; --m-pill-label: #2C2C2A; }
@media (prefers-color-scheme: dark) {
  .lib-stage { --m-page: #1C1A18; --m-rest: rgb(255 255 255 / .09); --m-on: rgb(255 255 255 / .09); --m-pill-label: #F0EDE8; }
}
.lib-stage .m-cta {
  position: relative; display: block; width: 346px; padding: 16px 0; overflow: hidden;
  border: 0; border-radius: 100px; cursor: default; background: #45818E;
  font-family: -apple-system, system-ui, "SF Pro Text", sans-serif; font-size: 16px; line-height: 20px; font-weight: 500; letter-spacing: .2px; color: var(--m-label); text-align: center;
  box-shadow: 0 3px 20px rgb(26 49 54 / .18);
  transition: transform 320ms cubic-bezier(.22, 1, .36, 1), opacity 200ms cubic-bezier(.45, 0, .55, 1);
}
.lib-stage .m-cta::before {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(rgb(255 255 255 / .30), rgb(255 255 255 / 0)) top / 100% 50% no-repeat,
    linear-gradient(rgb(0 0 0 / 0), rgb(0 0 0 / .16)) bottom / 100% 34% no-repeat,
    linear-gradient(rgb(91 144 156), rgb(61 114 125));
}
.lib-stage .m-cta::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1px rgb(255 255 255 / .2); }
.lib-stage .m-cta span { position: relative; }
.lib-stage .m-cta.is-pressing { transform: scale(.97); opacity: .4; transition: transform 100ms cubic-bezier(.45, 0, .55, 1), opacity 100ms cubic-bezier(.45, 0, .55, 1); }
.lib-stage .m-pills { display: flex; gap: 6px; }
.lib-stage .m-pill {
  padding: 8px 14px; border: 1.5px solid transparent; border-radius: 999px;
  background: var(--m-rest); color: var(--m-pill-label);
  font-family: -apple-system, system-ui, "SF Pro Text", sans-serif; font-size: 14px; line-height: 18px; font-weight: 500;
  transition: transform 320ms cubic-bezier(.22, 1, .36, 1);
}
.lib-stage .m-pill.is-pressing { transform: scale(.96); transition: transform 120ms cubic-bezier(.45, 0, .55, 1); }
.lib-stage .m-pill.is-on { background: var(--m-on); border-color: var(--m-on-border); }
.lib-stage { --m-soft: #8A8A84; --m-text: #2C2C2A; --m-danger: #D8564A; --m-divider: rgb(0 0 0 / .07); --m-highlight: rgb(0 0 0 / .055); }
@media (prefers-color-scheme: dark) { .lib-stage { --m-soft: #6A6660; --m-text: #F0EDE8; --m-danger: #F2857A; --m-divider: rgb(255 255 255 / .10); --m-highlight: rgb(255 255 255 / .08); } }
/* Carousel dots (CarouselIndicator.tsx): at most 5 pills, the active one stretched to 18 holding the centre,
   edge dots shrunk to 4; positions and lengths spring (lib-stage.js) */
.lib-stage .m-carousel { position: relative; width: 78px; height: 6px; }
.lib-stage .m-carousel span { position: absolute; top: 50%; left: 50%; height: 6px; border-radius: 3px; background: var(--m-soft); }
/* Lens switch (LensSwitch.tsx, the Pacer's title): the active word in the left slot; choosing the other swaps
   them, both sliding to each other's slot (460ms) and dipping to nothing as they cross. System font here;
   the app uses Merriweather. lib-stage.js drives position, opacity and colour. */
.lib-stage { --m-active: #2C2C2A; }
@media (prefers-color-scheme: dark) { .lib-stage { --m-active: #F0EDE8; } }
.lib-stage .m-lens { position: relative; height: 34px; }
.lib-stage .m-lens span { position: absolute; left: 0; top: 50%; white-space: nowrap; font: 300 26px / 34px -apple-system, system-ui, "SF Pro Display", sans-serif; }
.lib-stage .m-lens span.is-active { font-weight: 400; }
/* Notice (Notice.tsx): the shared empty / permission-off state, in iOS's own text styles (17 semibold
   Headline, 15 Subheadline); the outlined action fills while held */
.lib-stage { --m-heading: #383838; --m-border: rgb(138 158 140 / .2); --m-pressed: rgb(0 0 0 / .06); }
@media (prefers-color-scheme: dark) { .lib-stage { --m-heading: #C8C6BC; --m-border: rgb(255 255 255 / .07); --m-pressed: rgb(255 255 255 / .12); } }
.lib-stage .m-notice { display: flex; flex-direction: column; align-items: center; gap: 8px; width: 402px; box-sizing: border-box; padding: 0 40px; text-align: center; font-family: -apple-system, system-ui, "SF Pro Text", sans-serif; }
.lib-stage .m-notice-icon { width: 32px; height: 32px; margin-bottom: 8px; color: var(--m-soft); }
.lib-stage .m-notice-title { font-size: 17px; line-height: 22px; font-weight: 600; color: var(--m-heading); }
.lib-stage .m-notice-body { margin: 0 0 4px; font-size: 15px; line-height: 20px; color: var(--m-soft); }
.lib-stage .m-notice-action { padding: 10px 20px; border: 1px solid var(--m-border); border-radius: 20px; font-size: 15px; line-height: 20px; font-weight: 600; color: var(--m-heading); }
.lib-stage .m-notice-action.is-pressed { background: var(--m-pressed); }
/* Sheet row (SheetRow.tsx, shown in the Meal Plan folder menu): iOS's own press, no scale, a grey fill laid
   down instantly on touch and faded out over 260ms; the fill sits behind the content */
.lib-stage .m-sheet { width: 362px; padding: 20px 0 12px; font-family: -apple-system, system-ui, "SF Pro Text", sans-serif; }
.lib-stage .m-sheet-title { margin: 0 0 8px; text-align: center; font-size: 15px; line-height: 20px; font-weight: 600; color: var(--m-text); }
.lib-stage .m-divider { height: 1px; transform: scaleY(.5); background: var(--m-divider); }
.lib-stage .m-row {
  position: relative; display: flex; align-items: center; gap: 12px; width: calc(100% + 20px); margin: 0 -10px;
  padding: 15px 10px; font-size: 15px; line-height: 20px; color: var(--m-text);
}
.lib-stage .m-row::before {
  content: ""; position: absolute; inset: 0; border-radius: 12px; background: var(--m-highlight);
  opacity: 0; transition: opacity 260ms cubic-bezier(.33, 1, .68, 1);
}
.lib-stage .m-row.is-pressed::before { opacity: 1; transition: none; }
.lib-stage .m-row > * { position: relative; }
.lib-stage .m-row-icon { flex: none; width: 20px; height: 20px; color: var(--m-soft); }
.lib-stage .m-row.is-danger, .lib-stage .m-row.is-danger .m-row-icon { color: var(--m-danger); }
.lib-stage .m-dot svg, .lib-stage .m-check { display: block; }
@media (prefers-reduced-motion: reduce) { .lib-target { transition: none; } .lib-stage .m-cta, .lib-stage .m-pill { transition: none; } }

/* Screen wall (slide 05, screen-wall.js): real screens in straight rows, alternate rows drifting in
   opposite directions, edges fading out. Each row holds its screens twice and slides by exactly one set, so
   the loop is seamless. Transform-only CSS animation; paused off screen (.is-paused) and for reduced motion.
   Two rows everywhere: light screens on top, dark below. Desktop up to 170px screens; phones up to 110px. */
.screen-wall { position: relative; justify-self: stretch; overflow: hidden; overflow: clip;   /* clip: never scrollable, not even by a swipe */
  pointer-events: none; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;   /* decorative: touches go straight to the page, no image drag or long-press preview */
  /* the screens are sized so both rows fit whole: about 64% of the screen height, never above 170px wide
     (phone shape 1 : 2.17); the wall is exactly the rows plus a little room for the fades */
  --wall-gap: var(--space-4); --wall-w: clamp(100px, calc((64svh - var(--wall-gap)) / 4.34), 170px);
  height: calc(var(--wall-w) * 2.17 * 2 + var(--wall-gap) + var(--space-12));
  -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent), linear-gradient(to bottom, transparent, #000 var(--space-6), #000 calc(100% - var(--space-6)), transparent);
  -webkit-mask-composite: source-in;
          mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent), linear-gradient(to bottom, transparent, #000 var(--space-6), #000 calc(100% - var(--space-6)), transparent);
          mask-composite: intersect; }
.wall-tilt { display: grid; gap: var(--wall-gap); width: max-content; position: relative; top: 50%; translate: 0 -50%; }   /* rows start at the wall's left edge: centring them (they're different lengths) left the shorter dark row off screen for its first stretch */
.wall-row { display: flex; gap: var(--wall-gap); width: max-content; animation: wall-drift var(--wall-dur, 60s) linear infinite; will-change: transform; }   /* will-change: each row keeps its own layer, so iOS Safari doesn't drop and repaint it mid-scroll */
.wall-row:nth-child(even) { animation-direction: reverse; }
.wall-row img { display: block; width: var(--wall-w); height: auto; aspect-ratio: 590 / 1282; border-radius: 13.7% / 6.3%; box-shadow: 0 0 0 1px var(--line); background: var(--chip); }
@keyframes wall-drift { from { transform: translateX(0); } to { transform: translateX(calc(-50% - var(--wall-gap) / 2)); } }
.screen-wall.is-paused .wall-row { animation-play-state: paused; }
@media (max-width: 599px) {
  .screen-wall { --wall-gap: var(--space-3); --wall-w: clamp(80px, calc((55svh - var(--wall-gap)) / 4.34), 110px); }
}
@media (prefers-reduced-motion: reduce) { .wall-row { animation: none; } }
/* 10: where people find Maureen. The Webflow site (a maureen.care mockup, the whole image links to it, with the
   case study's ↗ circle in its top-right corner) above the iOS app, in its own white card: icon, name and the same
   ↗ circle; the whole card links to the App Store. No outlines or shadows. */
.app-card { display: grid; gap: var(--space-6); width: min(100%, 560px); justify-self: center; }
.app-card > .screen-label { margin: 0 0 calc(var(--space-6) * -1 + var(--space-2)); font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--muted); }   /* each label sits 8px above what it names */
.app-card > .app-card-shot + .screen-label { margin-top: var(--space-4); }   /* a little extra room between the two groups */
.app-card-shot { position: relative; display: block; border-radius: var(--space-6); overflow: hidden; }
.app-card-shot img { display: block; width: 100%; height: auto; transition: scale 400ms ease; }
.app-card-shot:hover img, .app-card-shot:focus-visible img { scale: 1.02; }
.shot-arrow { position: absolute; top: var(--space-4); right: var(--space-4); width: var(--space-10); height: var(--space-10); }   /* 40px over the image */
.app-card-shot:hover .shot-arrow, .app-card-shot:focus-visible .shot-arrow { background: var(--nav); color: var(--surface); }
.app-card-row { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-4); border-radius: var(--space-6); background: var(--surface); color: inherit; text-decoration: none; }   /* its own card: the whole card links to the App Store */
.app-card-icon { width: var(--space-16); height: auto; }   /* the icon file already has the squircle cut out: no extra rounding or outline */
.app-card-text { display: grid; gap: var(--space-1); margin-right: auto; }
.app-card-name { font-size: var(--text-title3); line-height: var(--leading-title3); font-weight: 600; }
.app-card-sub { font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--muted); }
.app-card-row .store-link { width: var(--space-10); height: var(--space-10); }
.app-card-row .store-link { background: var(--line); }   /* a grey circle on the white card */
.app-card-row:hover .store-link, .app-card-row:focus-visible .store-link { background: var(--nav); color: var(--surface); }
@media (prefers-reduced-motion: reduce) { .app-card-shot img { transition: none; } }
