/* Shared chrome: site-header, site-footer, project-row, case-header.
 * Loaded on every page that uses these components — never modify a copy
 * per page, edit here.
 */

/* --- Case header (case study pages — back link + About, no wordmark/full
 * nav) --- */

.case-header {
  display: flex;
  align-items: center;
  justify-content: space-between; /* NEW — now two links (← Work, About);
   * space them to opposite ends rather than letting them bunch together
   * with no gap between them. */
  padding: var(--space-24) var(--padding-section-x);
  background: var(--bg-page);
}

.case-header__back,
.case-header__about {
  color: var(--text-secondary);
  text-decoration: none;
  letter-spacing: var(--letter-spacing-none); /* was wide-sm (inherited from
   * .type-label-secondary) — only reads correctly on short all-caps text;
   * "Work" and "About" are Title Case, same pairing already fixed on
   * .case-title-block__eyebrow and others. Missed in that same fix since
   * this text lives in the shared case-header.js component, not a page's
   * own <style> block. */
}

/* --- Case footer nav (Back to top / Next case study — end of every case
 * study's content, before the shared site-footer). Same dashed-divider
 * and label treatment as .section-label on the homepage, for visual
 * consistency between the two wayfinding patterns. --- */

.case-footer-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-card);
  padding: var(--space-24) var(--padding-section-x);
  border-top: 1px dashed var(--border-divider);
  background: var(--bg-page);
}

.case-footer-nav__link {
  color: var(--text-secondary);
  text-decoration: none;
  letter-spacing: var(--letter-spacing-none); /* same wide-tracking fix as
   * .case-header__back/__about — this is sentence case, not short
   * all-caps. */
}

/* --- Case study section (The Problem, Why This Matters, ...) — repeats
 * identically across every section on a case study page, confirmed against
 * Figma node 44:2. */

.case-section {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--gap-section);
  padding: var(--padding-section-x);
  background: var(--bg-page);
}

.case-section--alt {
  background: var(--bg-canvas);
}

.case-section__heading {
  color: var(--text-primary);
}

.case-section__body {
  color: var(--text-primary);
  max-width: 56ch; /* was a fixed 640px (before that, 760px) — that px value
   * was tuned once against a specific type scale and drifted back to
   * ~92-95 chars/line once the scale changed (Impeccable audit, 2026-09-15).
   * ch ties the width to the current font's actual character width instead
   * of a static px guess, so it can't drift the same way again.
   *
   * 56ch, not a nominal ~72-75ch, because Barlow's real prose character
   * width runs narrower than its own ch unit (the '0' glyph the unit is
   * defined by) here — 72ch measured out to ~90-96 real characters/line
   * in this component, not ~72-75. Verified directly via
   * getClientRects()-based per-character line measurement (not estimated):
   * 56ch = max 78 chars/line on this page, max 79 on Linking Documents &
   * Prescriptions, the other page sharing this class. Re-verify the same
   * way if this component's font or type scale changes again — don't
   * trust the nominal ch count at face value for this font. */
}

/* --- Case study bulleted rules list (e.g. Reasoning & Governance's
 * "standing rules" list) — a real <ul>, not literal bullet characters in
 * <p> tags like Figma's own authoring, for semantics. --- */

.case-rules-list {
  display: flex;
  flex-direction: column;
  gap: var(--gap-section);
  width: 736px;
  max-width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

.case-rules-list__item {
  position: relative;
  padding-left: var(--gap-stack);
  color: var(--text-primary);
}

.case-rules-list__item::before {
  content: "•";
  position: absolute;
  left: 0;
}

/* --- Case study inline visual (bg-placeholder wrapper, no radius on the
 * wrapper itself; inner image gets radius + object-fit, no shadow) — reused
 * across every real-screenshot block on a case study page. Each usage sets
 * its own aspect-ratio via a modifier, since that varies per image. --- */

.case-visual {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 760px;
  max-width: 100%;
  padding: var(--space-24);
  background: var(--bg-placeholder);
}

.case-visual__image {
  display: block;
  width: 100%;
  max-width: 712px;
  height: auto;
  object-fit: cover;
  border-radius: var(--radius-md);
}

/* .case-visual's --bg-placeholder card fill is meant for a placeholder-
 * style box behind screenshots that need one. Some captures already
 * carry their own light background (fully transparent, or flat white/
 * near-white) — stacking --bg-placeholder's darker greenwhite tone
 * behind those just adds a visible colored border with no content
 * reason for it. Opt-in per image, not a change to .case-visual's
 * default, since most instances (full-app screenshots, video clips)
 * still want the card frame. Used on both Linking Documents &
 * Prescriptions and OnePulse Connect — shared here rather than
 * duplicated per page. */

.case-visual--transparent {
  background: transparent;
}

/* --- Case study dashed placeholder (flagged visual gap not built yet —
 * a genuinely missing asset, not a stand-in for something already shot).
 * Re-added for Linking Documents & Prescriptions' Hero Visual; was removed
 * as dead code once OnePulse Connect's own gaps (sidebar clip, hero) got
 * real footage. Each usage sets its own size via a modifier. --- */

.case-dashed-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-24);
  background: transparent;
  border: 2px dashed var(--border-muted);
  border-radius: var(--radius-md);
}

.case-dashed-placeholder__label {
  color: var(--text-primary);
  text-align: center;
  text-transform: none; /* .type-meta already sets none at the token level;
   * this is now redundant but left explicit — these labels run to full
   * sentences describing what's missing, not short tags, so all-caps would
   * hurt readability here regardless of the base value. */
  letter-spacing: var(--letter-spacing-none); /* wide tracking only reads
   * correctly on all-caps text — same pairing as .stat-tile__label. */
}

/* --- Case study vertical image stack (a group of .case-visual/
 * .case-dashed-placeholder blocks stacked with a consistent 20px gap —
 * Data Table Screenshots, Production Screenshots) --- */

.case-image-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--gap-section);
  width: 760px;
  max-width: 100%;
}

/* --- Stat Grid (case study "At a Glance") --- */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gap-card);
  width: 100%;
}

.stat-tile {
  display: flex;
  flex-direction: column;
  gap: var(--gap-inline);
  padding: var(--gap-inline);
  background: var(--bg-surface);
}

.stat-tile__value {
  color: var(--text-primary);
  font-family: var(--font-family-serif); /* override — .type-case-title (sans)
   * is shared with .project-row__title on the homepage, so this stays
   * scoped to the stat-tile value only. Size/weight/color unchanged. */
}

.stat-tile__label {
  color: var(--text-label);
  text-transform: none; /* .type-meta already sets none at the token level;
   * this is now redundant but left explicit — these are full sentences (up
   * to 64 chars), not short labels, so all-caps would hurt readability here
   * regardless of the base value. */
  letter-spacing: var(--letter-spacing-none); /* wide tracking only reads
   * correctly on all-caps text — leaving it in place after removing
   * uppercase left oddly wide-spaced sentence case. */
}

/* --- Problem Diagram (scattered files -> shared token system illustration,
 * case study "The Problem" section only — bespoke, not a reusable pattern) --- */

.problem-diagram {
  display: flex;
  align-items: center;
  gap: var(--space-32);
  padding: var(--gap-stack);
  background: var(--bg-surface);
}

.problem-diagram__group {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-shrink: 0;
  gap: var(--gap-stack);
  padding: var(--gap-stack);
}

.problem-diagram__thumbs {
  position: relative;
  width: 270px;
  height: 220px;
}

.problem-diagram__thumb-wrap {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
}

.problem-diagram__thumb-wrap--a {
  left: 0;
  top: 61px;
  width: 160px;
  height: 108px;
  transform: rotate(7deg);
}

.problem-diagram__thumb-wrap--b {
  left: 112px;
  top: 0;
  width: 157px;
  height: 103px;
  transform: rotate(-5deg);
}

.problem-diagram__thumb-wrap--c {
  left: 67px;
  top: 119px;
  width: 156px;
  height: 100px;
  transform: rotate(4deg);
}

.problem-diagram__card {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 150px;
  height: 90px;
  padding: var(--space-32) var(--gap-stack);
  background: var(--bg-canvas);
  border: 1px solid var(--border-muted);
  border-radius: var(--space-4);
  color: var(--text-muted);
  text-align: center;
  letter-spacing: var(--letter-spacing-none); /* was wide-sm (inherited from
   * .type-label-secondary) — only read correctly while the text was
   * hardcoded "FILE A/B/C"; now that it's "File A/B/C", wide tracking only
   * reads correctly on all-caps text, same pairing already fixed on
   * .stat-tile__label and others. */
}

.problem-diagram__caption {
  width: 220px;
  text-align: center;
  text-transform: none; /* .type-label-secondary already sets none at the
   * token level; this is now redundant but left explicit — these run up to
   * 46 chars, long enough that all-caps would hurt readability here
   * regardless of the base value. */
  letter-spacing: var(--letter-spacing-none); /* same reasoning — wide
   * tracking belongs to all-caps text, not the sentence case above. */
}

.problem-diagram__caption--primary {
  color: var(--text-primary);
}

.problem-diagram__caption--label {
  color: var(--text-label);
}

.problem-diagram__arrow {
  /* Matches .token-chain__arrow exactly — same SVG asset, size, and color,
   * for visual consistency between the two arrow usages on this page. */
  display: flex;
  flex-shrink: 0;
  width: 14px;
  height: 14.7279px;
  color: var(--accent-primary-dark);
}

.problem-diagram__arrow svg {
  display: block;
  width: 100%;
  height: 100%;
}

.problem-diagram__image-frame {
  position: relative;
  width: 280px;
  height: 220px;
}

.problem-diagram__image {
  position: absolute;
  left: 0;
  top: 22px;
  width: 280px;
  height: 175px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-resolved); /* the resolved/elevated cue now stands alone, no border */
}

/* --- Token Chain (case study "Token Architecture" live embed) — three
 * connected cards showing a primitive/alias/mapped-token chain resolving to
 * the same color; hovering any card highlights all three via :has(), no JS
 * interaction needed. Confirmed against Figma node 353:108. --- */

.token-chain-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 760px;
  max-width: 100%;
  padding: var(--space-24);
  background: var(--bg-placeholder);
}

.token-chain {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1px var(--gap-card);
  width: 100%;
}

.token-chain__card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex: 1 0 0;
  min-width: 0;
  min-height: 110px;
  gap: var(--gap-stack);
  padding: var(--space-16) var(--space-8);
  background: var(--bg-page);
  border: 1px solid var(--border-divider);
  border-radius: var(--radius-md);
}

@media (prefers-reduced-motion: no-preference) {
  .token-chain__card {
    transition: border-color 200ms ease-out;
  }
}

.token-chain:has(.token-chain__card:hover) .token-chain__card {
  border-color: var(--border-highlight-teal);
}

.token-chain__swatch {
  display: block;
  width: var(--space-24);
  height: var(--space-24);
  border-radius: var(--space-4);
}

/* Each card's swatch resolves through its own step of the chain rather than
 * all three sharing one variable — primitive, alias, and mapped token are
 * genuinely distinct custom properties here, even though (per tokens.css)
 * they resolve to the same value by design. */
.token-chain__swatch--primitive {
  background: var(--color-pulse-teal-700);
}

.token-chain__swatch--alias {
  background: var(--pulse-primary-dark);
}

.token-chain__swatch--mapped {
  background: var(--pulse-accent-primary-fg-fill);
}

.token-chain__text {
  display: flex;
  flex-direction: column;
  align-self: stretch;
  min-width: 0;
  gap: var(--gap-inline);
}

.token-chain__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--color-teal-label);
}

.token-chain__name {
  /* Wraps rather than truncates — unlike .token-chain__label (short, fixed
   * category names), a resolved token name's length isn't bounded, and
   * clipping it defeats the point of a widget whose job is showing the
   * real name. Accent.Primary.fg.fill (178px) was overflowing the card's
   * 146px width under nowrap+ellipsis; overflow-wrap lets it break even
   * with no spaces to wrap at. */
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--text-primary);
}

.token-chain__arrow {
  display: flex;
  flex-shrink: 0;
  width: 14px;
  height: 14.7279px;
  color: var(--accent-primary-dark);
}

.token-chain__arrow svg {
  display: block;
  width: 100%;
  height: 100%;
}

.token-chain__caption {
  width: 760px;
  max-width: 100%;
  color: var(--text-secondary);
}

@media (max-width: 600px) {
  .token-chain {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
  }

  .token-chain__arrow {
    align-self: center;
    transform: rotate(90deg);
  }
}

/* --- Site header --- */

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--gap-inline);
  height: 80px;
  padding: 0 var(--padding-section-x);
}

.site-header__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--gap-inline);
  color: var(--text-primary);
  text-decoration: none;
}

.site-header__brand-mark {
  display: block;
  width: var(--space-16);
  height: var(--space-16);
}

.site-header__brand-text-wrap {
  display: grid;
}

.site-header__brand-text {
  grid-area: 1 / 1;
  opacity: 0;
  filter: blur(6px);
  transition: opacity 900ms ease-in-out, filter 900ms ease-in-out;
}

.site-header__brand-text.is-active {
  opacity: 1;
  filter: blur(0);
}

.site-header__nav {
  display: flex;
  align-items: center;
  gap: var(--gap-inline);
}

.nav-pill {
  /* Reworked — border-radius back to 0 (square, reverses the earlier
   * rounded-pill decision), padding uniform 8px (was 8px/16px), border
   * moved off the shared base and onto the active .is-active state
   * only, confirmed against Figma node 378:160. */
  display: inline-flex;
  align-items: center;
  min-height: 44px; /* WCAG 2.5.5 minimum tap target — visible padding stays
   * at the Figma-confirmed 8px; this only grows the invisible hit area. */
  padding: var(--gap-inline);
  border: none;
  border-radius: 0;
  color: var(--text-primary);
  text-decoration: none;
  background: transparent;
}

.nav-pill.is-active {
  /* Active-tab treatment: bottom border only, 2px, cosmos-6 — reuses
   * --border-highlight (defined for the Token Architecture hover border,
   * same color). No fill, no inverse text — both removed per redesign.
   * Which pill gets this class is decided at render time in nav.js from
   * document.body.dataset.page, not hardcoded to a single data-nav value
   * — every page nav.js renders is a real, active-able destination now. */
  border-bottom: 2px solid var(--border-highlight);
}

@media (prefers-reduced-motion: no-preference) {
  .nav-pill {
    transition: transform 350ms cubic-bezier(0.165, 0.84, 0.44, 1); /* was an
     * overshoot/bounce curve (0.34, 1.15, 0.64, 1) — swapped for ease-out-quart,
     * a smooth deceleration instead of a springy snap-back. */
  }

  .nav-pill:hover {
    transform: scale(1.025);
  }

  .nav-pill:active {
    transform: scale(0.97);
    transition-duration: 120ms;
  }
}

/* --- Site footer --- */

.site-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--gap-stack);
  padding: var(--padding-section-y) var(--padding-section-x) var(--space-48);
}

.site-footer__copy {
  color: var(--text-label);
  text-transform: none; /* .type-meta already sets none at the token level;
   * this is now redundant but left explicit — confirmed against Figma node
   * 48:15 that the copyright line is mixed-case, not all-caps. */
  letter-spacing: var(--letter-spacing-none); /* was wide-md — missed when
   * text-transform was fixed above; the copyright line was always mixed
   * case, never all-caps, so wide tracking never read correctly here,
   * same pairing as .stat-tile__label and the other mono labels. */
}

.site-footer__links {
  display: flex;
  gap: var(--gap-footer-links);
}

.site-footer__links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px; /* WCAG 2.5.5 minimum tap target — text size/position
   * unchanged, this only grows the invisible hit area. */
  color: var(--text-label);
  text-decoration: none;
}

.site-footer__links a:hover {
  color: var(--text-primary);
}

/* --- Project row --- */

.project-row {
  display: flex;
  flex-direction: column;
  gap: var(--gap-card);
  padding: var(--padding-card-y) var(--padding-section-x);
  text-decoration: none;
  border-bottom: 1px dashed var(--border-divider);
}

.project-row:hover {
  background: var(--bg-inverse);
}

/* Non-clickable rows (no href yet — case study not published) never show
 * the interactive hover treatment; nothing happens on click either way. */
.project-row--upcoming:hover {
  background: none;
}

.project-row__body {
  display: flex;
  align-items: start;
  gap: var(--padding-section-x);
}

.project-row__image {
  /* width:50% (not a fixed px) so this scales down gracefully at desktop
   * widths just above the 720px stacking breakpoint — a flat px width big
   * enough to matter at 1440px would leave --project-row-description
   * almost no room right above 720px. flex-shrink stays enabled (0 1 auto,
   * not 0 0 auto) so it can actually give up that 50% if the row is ever
   * tighter than image+gap+description's combined min-content. */
  flex: 0 1 auto;
  width: 50%;
  min-width: 280px;
  max-width: var(--project-row-image-width);
  overflow: hidden;
  background: var(--bg-placeholder);
  padding: var(--padding-image);
}

.project-row__image-inner {
  display: block;
  width: 100%;
  height: auto;
  background: var(--bg-page);
  object-fit: cover;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
}

.project-row__image--siteoverview {
  /* The site-overview clip is a self-contained frame — its own background
   * fills its full bounds — so, unlike a bare screenshot, it doesn't need
   * to sit inside the shared placeholder tray above. Same reasoning as
   * .case-hero__placeholder--frameless on the case-study page itself. */
  background: none;
  padding: 0;
}

.project-row__image--infectionscreening {
  /* Requested directly: swap this row's placeholder tray from the shared
   * greenwhite tone to a warmer one, scoped to just this row so the other
   * two keep the default --bg-placeholder tray. */
  background: var(--bg-placeholder-warm);
}

.project-row__image-inner--siteoverview {
  /* Explicit aspect-ratio so the row doesn't flash/reflow before the
   * video's own intrinsic size is known, matching ds_siteoverview.mp4's
   * current 1600x1000 encode (previously 1600/986 for the older clip). */
  aspect-ratio: 1600 / 1000;
}

.project-row__image-inner--linkdocflow {
  /* Same reasoning as --siteoverview, matching link-document-flow-short.mp4's
   * real 1440x900 encode. project-row.js derives this class name from each
   * row's own video.aspectClass rather than hardcoding one modifier for
   * every video row. */
  aspect-ratio: 1440 / 900;
}

.project-row__image-inner--infectionscreening {
  /* Same reasoning as --siteoverview/--linkdocflow, matching
   * as_infectionscreening.mp4's real 2880x1800 encode (also the Pre-Infusion
   * case study page's own hero clip). */
  aspect-ratio: 2880 / 1800;
}

.project-row__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-card);
}

.project-row__title-group {
  display: flex;
  align-items: baseline;
  gap: var(--gap-inline);
}

.project-row__title-group--stacked {
  /* Used only where a row carries a subtitle (the product name underneath
   * a descriptive headline) — overrides the baseline row layout above so
   * the subtitle sits below the title instead of inline beside it. Scoped
   * to a modifier rather than changed on the base class so rows without a
   * subtitle keep their existing single-line layout. */
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
}

.project-row__subtitle {
  color: var(--text-label);
  margin: 0;
}

.project-row__index {
  color: var(--accent-primary-dark); /* accent-primary fails 4.5:1 as text on bg-page —
   * accent-primary-dark clears it at 6.6:1. */
}

.project-row__title {
  color: var(--text-primary);
  margin: 0;
  font-size: var(--font-size-24);
  letter-spacing: var(--letter-spacing-tight-xs2);
}

.project-row__meta {
  display: flex;
  align-items: center;
  gap: var(--gap-stack);
}

.project-row__year {
  color: var(--text-label);
}

.badge {
  display: inline-block;
  padding: var(--gap-tight) var(--gap-inline);
  border: 1px solid var(--border-muted);
  border-radius: var(--space-4);
  color: var(--text-tag);
}

.project-row__description {
  flex: 1 1 0;
  min-width: 0;
  color: var(--text-primary);
  margin: 0;
}

/* Scroll-triggered stagger reveal — .js-stagger is added by project-row.js
 * only when IntersectionObserver is available and the user hasn't asked
 * for reduced motion, so this never leaves content stuck invisible if JS
 * fails to run. .is-visible is added once per row, on first intersection,
 * and never removed — the reveal fires exactly once. */
@media (prefers-reduced-motion: no-preference) {
  .project-row.js-stagger .badge,
  .project-row.js-stagger .project-row__description {
    opacity: 0;
    transition: opacity 340ms ease-out;
  }

  .project-row.js-stagger .project-row__title,
  .project-row.js-stagger .project-row__image {
    opacity: 0;
    filter: blur(4px);
    transition: opacity 340ms ease-out, transform 340ms ease-out, filter 340ms ease-out;
  }

  .project-row.js-stagger .project-row__title {
    transform: translateY(6px);
  }

  .project-row.js-stagger .project-row__image {
    transform: scale(0.98);
  }

  .project-row.js-stagger.is-visible .badge,
  .project-row.js-stagger.is-visible .project-row__description {
    opacity: 1;
  }

  .project-row.js-stagger.is-visible .project-row__title,
  .project-row.js-stagger.is-visible .project-row__image {
    opacity: 1;
    transform: none;
    filter: blur(0);
  }

  .project-row.js-stagger.is-visible .project-row__title { transition-delay: 60ms; }
  .project-row.js-stagger.is-visible .badge { transition-delay: 120ms; }
  .project-row.js-stagger.is-visible .project-row__image { transition-delay: 180ms; }
  .project-row.js-stagger.is-visible .project-row__description { transition-delay: 240ms; }
}

@media (max-width: 720px) {
  .project-row__body {
    flex-direction: column;
  }

  .project-row__image {
    width: auto;
    min-width: 0;
    max-width: none;
  }

  .project-row__header {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--gap-stack);
  }

  .project-row__meta {
    align-items: flex-start;
  }

  .stat-grid {
    grid-template-columns: 1fr;
  }

  .problem-diagram {
    max-width: 100%;
    overflow-x: auto;
  }

  .case-visual {
    /* 24px fixed padding eats ~17% of a 279px mobile container width vs.
     * ~3% of the 712px desktop one, shrinking Tab Group, Data Table, and
     * Cell Type Variants down to an illegible size inside an
     * oversized empty box. 12px keeps a visible inset without the
     * disproportionate shrink. */
    padding: var(--space-12);
  }
}

/* --- Lightbox (shared — click any real case-study image for a larger
 * version). One instance injected per page by lightbox.js; any <img> with
 * a data-lightbox-src attribute is a trigger. --- */

[data-lightbox-src] {
  cursor: zoom-in;
}

[data-lightbox-src]:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}

body.lightbox-open {
  overflow: hidden;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-48);
  visibility: hidden;
  opacity: 0;
}

.lightbox.is-open {
  visibility: visible;
  opacity: 1;
}

@media (prefers-reduced-motion: no-preference) {
  .lightbox {
    transition: opacity 200ms ease-out, visibility 0s linear 200ms;
  }

  .lightbox.is-open {
    transition: opacity 200ms ease-out, visibility 0s linear 0s;
  }
}

.lightbox__backdrop {
  position: absolute;
  inset: 0;
  background: var(--bg-backdrop);
}

.lightbox__content {
  position: relative;
  display: flex;
  max-width: 100%;
  max-height: 100%;
  transform: scale(0.96);
}

.lightbox.is-open .lightbox__content {
  transform: scale(1);
}

@media (prefers-reduced-motion: no-preference) {
  .lightbox__content {
    transition: transform 200ms ease-out;
  }
}

.lightbox__image {
  display: block;
  max-width: 100%;
  max-height: calc(100vh - var(--space-96));
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--radius-md);
}

.lightbox__close {
  position: fixed;
  top: var(--space-24);
  right: var(--space-24);
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--space-48);
  height: var(--space-48);
  font-family: var(--font-family-sans);
  font-size: var(--font-size-32);
  line-height: 1;
  color: var(--color-white);
  background: transparent;
  border: none;
  border-radius: var(--radius-full);
  cursor: pointer;
}

.lightbox__close:hover,
.lightbox__close:focus-visible {
  background: color-mix(in srgb, var(--color-white) 15%, transparent);
}

@media (max-width: 600px) {
  .lightbox {
    padding: var(--space-24);
  }
}
