/* Section Specific Layout Styles — The Café */

/* ==========================================================================
   1. Hero Section — Entering the Café
   One cohesive illustrated scene along the base, not floating characters.
   ========================================================================== */
.hero-section {
  /* Was calc(--space-16 + 2.5rem) = 104px; --space-16 alone (64px) is a
     ~38% cut, inside the requested 30-40% range, while still leaving
     comfortable room below the 72px header. */
  padding-top: var(--space-16);
  padding-bottom: 0;
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--color-bg-primary) 0%, var(--color-bg-elevated) 100%);
}

.hero-container {
  position: relative;
  z-index: 1;
  padding-bottom: var(--space-10);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-10);
}

/* Page-load entrance — header, headline, paragraph, CTAs, and the
   illustration (further down, alongside its existing idle-float rule)
   each fade + rise into place on a short stagger, all using the same
   soft ease, finishing in ~950ms. Scoped to .hero-page-header (added
   only to index.html's <header>) rather than the shared .site-header
   class, so case-study pages' headers are unaffected. */
.hero-page-header {
  opacity: 0;
  animation: heroHeaderIn 450ms cubic-bezier(0.22, 1, 0.36, 1) 0ms forwards;
}

.hero-headline {
  opacity: 0;
  animation: heroHeadlineIn 600ms cubic-bezier(0.22, 1, 0.36, 1) 150ms forwards;
}

.hero-subhead {
  opacity: 0;
  animation: heroSubheadIn 600ms cubic-bezier(0.22, 1, 0.36, 1) 300ms forwards;
}

.hero-ctas {
  opacity: 0;
  animation: heroCtasIn 500ms cubic-bezier(0.22, 1, 0.36, 1) 450ms forwards;
}

@keyframes heroHeaderIn {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes heroHeadlineIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes heroSubheadIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes heroCtasIn {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .hero-page-header,
  .hero-headline,
  .hero-subhead,
  .hero-ctas {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

.hero-content {
  max-width: 640px;
  width: 100%;
}

/* Illustration — sits beside the text on tablet/desktop, stacks below the
   CTAs on mobile. The dashed blob behind it echoes the stamp-label motif
   used elsewhere on the page (see .stamp-label in components.css). */
/* Entrance + idle float live on the wrap itself, not the <img>, so the
   heart-icon child (positioned separately below) rides along with the same
   motion via normal transform composition — its own hover pulse then just
   adds a scale on top with no conflict. */
.hero-illustration-wrap {
  position: relative;
  flex-shrink: 0;
  width: 260px;
  max-width: 70%;
  opacity: 0;
  animation:
    heroIllustrationEnter 700ms cubic-bezier(0.22, 1, 0.36, 1) 250ms forwards,
    heroIllustrationFloat 4000ms ease-in-out 950ms infinite;
}

.hero-illustration-blob {
  position: absolute;
  inset: 6% 8%;
  border-radius: 50%;
  background-color: var(--color-clay-deep);
  opacity: 0.1;
  border: 2px dashed var(--color-clay-deep);
  z-index: -1;
  animation: heroBlobPulse 16s ease-in-out infinite;
}

.hero-illustration-img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 20px 32px rgba(180, 120, 60, 0.15));
}

.hero-heart-icon {
  position: absolute;
  left: 21.2%;
  top: 22.8%;
  width: 10.4%;
  z-index: 2;
}

.hero-illustration-wrap:hover .hero-heart-icon,
.hero-illustration-wrap:focus-within .hero-heart-icon {
  animation: heroHeartBeat 500ms ease-in-out;
}

/* Entrance runs once (250ms after load, alongside the headline/paragraph
   stagger above), then heroIllustrationFloat takes over transform for the
   idle "breathing" loop — its 0% state matches exactly where the entrance
   leaves off (translateY(0) scale(1)), so the handoff is seamless. */
@keyframes heroIllustrationEnter {
  from { opacity: 0; transform: translateY(9px) scale(0.985); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes heroIllustrationFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-8px); }
}

@keyframes heroBlobPulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.03); }
}

@keyframes heroHeartBeat {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.2); }
}

@media (prefers-reduced-motion: reduce) {
  .hero-illustration-wrap {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .hero-illustration-blob {
    animation: none !important;
  }
  .hero-illustration-wrap:hover .hero-heart-icon,
  .hero-illustration-wrap:focus-within .hero-heart-icon {
    animation: none !important;
  }
}

@media (min-width: 768px) {
  .hero-container {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-8);
  }

  .hero-content {
    width: auto;
  }

  .hero-illustration-wrap {
    width: clamp(260px, 28vw, 430px);
    max-width: none;
  }
}

.hero-headline {
  font-size: var(--text-3xl);
  line-height: var(--line-height-tight);
  color: var(--color-text-primary);
  margin-bottom: var(--space-6);
  letter-spacing: -0.03em;
  font-weight: var(--font-weight-bold);
}

.hero-headline .highlight-accent {
  color: var(--color-clay-deep);
  font-family: var(--font-family-serif);
  font-weight: var(--font-weight-medium);
  font-style: italic;
  letter-spacing: -0.01em;
}

.hero-subhead {
  font-size: var(--text-lg);
  color: var(--color-text-secondary);
  line-height: var(--line-height-relaxed);
  margin-bottom: 2.75rem;
  max-width: 780px;
}

.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}

/* Kept for the interior Charlie in Meet the Barista — unrelated to the hero,
   which no longer has any illustrated scene. */
@keyframes tailWag {
  0% { transform: rotate(0deg); }
  30% { transform: rotate(-5deg); }
  70% { transform: rotate(7deg); }
  100% { transform: rotate(0deg); }
}

/* ==========================================================================
   2. Today's Menu — an editorial café menu, not a card grid
   ========================================================================== */
.menu-masthead {
  max-width: 640px;
  margin-bottom: var(--space-10);
}

.menu-masthead-title {
  font-size: var(--text-3xl);
  color: var(--color-text-primary);
  margin: var(--space-3) 0 var(--space-4) 0;
}

.menu-masthead-intro {
  font-size: var(--text-lg);
  color: var(--color-text-secondary);
  line-height: var(--line-height-relaxed);
  max-width: 620px;
}

.menu-board {
  position: relative;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding: var(--space-2) var(--space-6);
  transform: rotate(-0.3deg);
}

@media (min-width: 640px) {
  .menu-board { padding: var(--space-2) var(--space-10); }
}

.menu-item {
  position: relative;
  padding: var(--space-8) 0 var(--space-8) var(--space-4);
  border-bottom: 2px dashed var(--color-border);
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 550ms ease-out, transform 550ms ease-out, border-bottom-color 300ms ease;
}

.menu-item.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

.menu-item:last-child {
  border-bottom: none;
}

/* Row hover — the dashed divider beneath the row solidifies (dashed ->
   solid, color darkening to that card's own accent) instead of a
   separate left-edge accent line. border-style itself can't transition
   smoothly (a native CSS limitation — it snaps), but the color change
   still eases, so the overall effect reads as a soft darken/solidify
   rather than an abrupt flip. Event Overview Template has no
   [data-project-id] override below, so it falls through to the shared
   rust default here. */
.menu-item:hover,
.menu-item:focus-within {
  border-bottom-style: solid;
  border-bottom-color: var(--color-clay-deep);
}

.menu-item-row {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  align-items: flex-start;
}

/* flex-grow: 0 (not 1) is deliberate — with grow enabled, this box would
   stretch to consume all leftover row width even though its text content
   doesn't need it, which pushed the illustration far off to the right on
   wide viewports with only a little text. Capping the basis at 620px
   (matching .work-card-challenge's own max-width) means this column
   always hugs its actual content, so the illustration sits right after it
   with exactly `gap` in between — the same fixed distance on every card,
   regardless of how much text a given case study has. */
.menu-item-main {
  min-width: 280px;
  flex: 0 1 620px;
}

/* Eyebrow — reuses the hand-script treatment that used to label the
   coffee-menu item name, now labeling the project category instead. */
.menu-item-category {
  display: inline-block;
  font-family: var(--font-family-hand);
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--color-berry-text);
  line-height: 1;
  margin-bottom: var(--space-2);
}

.work-card-title {
  font-size: 1.5rem;
  font-weight: var(--font-weight-bold);
  line-height: 1.25;
  color: var(--color-text-primary);
  margin-bottom: var(--space-2);
  letter-spacing: -0.01em;
}

.work-card-challenge {
  font-family: var(--font-family-serif);
  font-style: italic;
  font-size: var(--text-base);
  color: var(--color-text-secondary);
  line-height: var(--line-height-base);
  margin-bottom: var(--space-3);
  max-width: 620px;
}

.menu-item-year {
  display: block;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-weight: var(--font-weight-medium);
  margin-bottom: var(--space-4);
}

/* Always-visible skill tags — replaces the old click-to-reveal recipe list
   on these cards. */
.skill-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  list-style: none;
  padding: 0;
  margin: 0;
}

/* Compact, quiet metadata — smaller than before and with a neutral
   (not accent-tinted) border, so these read as secondary to the CTA
   button below rather than competing with it visually. */
.skill-tag {
  font-size: 0.75rem;
  font-weight: var(--font-weight-medium);
  color: var(--color-text-accent);
  background-color: var(--color-accent-subtle);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-full);
  padding: 0.375rem 0.75rem;
  opacity: 0;
  transform: translateY(9px);
  transition: opacity 380ms ease-out, transform 380ms ease-out;
}

.menu-item.is-revealed .skill-tag {
  opacity: 1;
  transform: translateY(0);
}

.skill-tag:nth-child(1) { transition-delay: 0ms; }
.skill-tag:nth-child(2) { transition-delay: 70ms; }
.skill-tag:nth-child(3) { transition-delay: 140ms; }
.skill-tag:nth-child(4) { transition-delay: 210ms; }

/* Single, deliberate 32px gap to the tags above (was two compounding
   values — the tags' own margin-bottom plus this padding — which read as
   more empty space than intended). */
.work-card-action-row {
  display: flex;
  align-items: center;
  padding-top: 2rem;
}

/* Real button, not a text link — reuses .btn/.btn-secondary's pill shape
   as-is (the same base classes "Meet the Barista" uses in the hero) and
   only recolors it: transparent at rest with the site's primary rust
   accent as the border/text color, filling solid with that same accent
   and switching text to a light color on hover. Uniform across all five
   cards regardless of category color — unlike the eyebrow/illustration,
   this button is intentionally NOT themed per-card. */
.menu-item .btn-card-action {
  background-color: transparent;
  border-color: #355B4A;
  color: #355B4A;
}

.menu-item .btn-card-action:hover {
  background-color: #355B4A;
  border-color: #355B4A;
  color: var(--color-text-inverse);
}

.menu-item .btn-card-action svg {
  color: currentColor;
}

/* Non-interactive placeholder for unfinished case studies — sits where
   the CTA button would be, styled quietly (same weight/color as the
   year/client line above) so it stays secondary to the title and
   description rather than competing with the active CTA elsewhere. */
.work-card-coming-soon {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-weight: var(--font-weight-medium);
}

/* Custom illustration, not a screenshot — sized/capped so it can never
   push the card taller than the left column already makes it, and
   vertically centered within that height via align-self (the row itself
   keeps align-items: flex-start for its other children). No more photo
   mat/frame chrome around it since the illustration is self-contained
   artwork, not a crop that needs one.
   364x280 is 260x200 scaled by exactly 1.4x (40%, within the 35-50%
   target) — keeping the same 1.3:1 ratio as every illustration's own
   viewBox="0 0 260 200" means each one just scales up cleanly with no
   distortion and no dead space inside the box. */
.menu-item-preview {
  position: relative;
  flex: 0 0 364px;
  width: 364px;
  max-width: 100%;
  height: 280px;
  max-height: 300px;
  margin-left: var(--space-10);
  align-self: center;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: visible;
}

/* Kiosk + mobile illustration is sized on its own (still the same 1.3:1
   ratio as the shared viewBox="0 0 260 200", so no distortion); the other
   four illustrations are unaffected. 320x246 is ~6.5% up from the
   previous 300x231, inside the requested 5-8% bump. */
.work-card-media-map-gallery {
  flex-basis: 320px;
  width: 320px;
  height: 246px;
}

.work-card-illustration {
  display: block;
  width: 100%;
  height: 100%;
  /* SVG root elements clip to their viewBox by default (a UA-stylesheet
     overflow: hidden), independent of .menu-item-preview's own
     overflow: visible — that HTML-side rule never controlled the SVG's
     internal clipping. This default was cutting off the kiosk's top/right
     edge whenever the hover fan-out's rotate/translate pushed it past the
     viewBox boundary; none of this illustration's ancestors set
     overflow: hidden, so this is the only place that needed the fix. */
  overflow: visible;
}

/* Muted/grayed variant for the Carmichael HART "before" frame — pushed
   further toward fully desaturated (a real grayscale filter, not just
   muted-toned custom properties) so the before/after contrast reads at a
   glance next to the warmer "after" frame below. */
.work-card-frame-muted {
  --wc-frame-fill: #E9E9E7;
  --wc-frame-stroke: #8C8C8C;
  --wc-chrome-line: #A8A8A4;
  --wc-content: #D2D2CE;
  filter: grayscale(1);
}

/* "After" frame on the same illustration — deliberately warmer/stronger
   than the shared default (both the accent stroke/nav and the content
   fill) to sharpen that before/after contrast further. */
.work-card-frame-carmichael-after {
  --wc-frame-stroke: #A8333B;
  --wc-chrome-line: #C9737A;
  --wc-nav: #A8333B;
  --wc-content: #F3D9D2;
}

/* Nav line goes full accent instead of the default muted tan, for the
   cross-product navigation illustration's connected-nav concept — both
   this and the frame outline itself use the category's plum accent. */
.work-card-frame-nav-highlight {
  --wc-frame-stroke: #8A5A78;
  --wc-chrome-line: #B08CA1;
  --wc-nav: #8A5A78;
}


/* Category accent colors — one distinct color per card, applied to that
   card's eyebrow, its illustration's outlines/highlighted elements, and
   (on hover) the row's own divider solidifying into that same color.
   Event Overview Template keeps the shared rust/tan default (untouched
   here); tag pills and body text stay neutral everywhere regardless of
   card. The "See more" CTA is intentionally NOT themed per-category — it
   uses one uniform accent site-wide (see .menu-item .btn-card-action
   above) so it isn't part of this per-card system. Illustrations built
   from the shared <use> symbols pick up their color via the --wc-*
   custom properties (inherited into the symbol's cloned content); the
   hand-drawn kiosk shapes and dotted connector/dot markers are colored
   directly in their SVG markup instead, since they don't go through the
   symbol/custom-property system. */
[data-project-id="map-gallery"] .menu-item-category { color: #4F8A80; }
[data-project-id="map-gallery"] use { --wc-frame-stroke: #4F8A80; --wc-chrome-line: #7BA59D; }
[data-project-id="map-gallery"]:hover,
[data-project-id="map-gallery"]:focus-within { border-bottom-color: #4F8A80; }

/* Event Overview Template has its own project accent (rust/orange) even
   though its eyebrow/illustration colors live outside this per-card
   system — pin its divider-hover color explicitly so it doesn't inherit
   the global brand accent's border-bottom-color from the shared default. */
[data-project-id="event-template"]:hover,
[data-project-id="event-template"]:focus-within { border-bottom-color: #A96C3F; }

[data-project-id="sponsorship"] .menu-item-category { color: #5C7A5F; }
[data-project-id="sponsorship"] use { --wc-frame-stroke: #5C7A5F; --wc-chrome-line: #8BA58D; --wc-nav: #5C7A5F; }
[data-project-id="sponsorship"]:hover,
[data-project-id="sponsorship"]:focus-within { border-bottom-color: #5C7A5F; }

[data-project-id="carmichael-hart"] .menu-item-category { color: #A8333B; }
[data-project-id="carmichael-hart"]:hover,
[data-project-id="carmichael-hart"]:focus-within { border-bottom-color: #A8333B; }

[data-project-id="cross-navigation"] .menu-item-category { color: #8A5A78; }
[data-project-id="cross-navigation"]:hover,
[data-project-id="cross-navigation"]:focus-within { border-bottom-color: #8A5A78; }

/* Map Gallery — the phone overlaps the kiosk screen's bottom-right
   corner, so it gets a soft drop shadow to read as resting on top of the
   kiosk rather than just clipped into it. Always on (not hover-only). */
.wc-phone-overlap-shadow {
  filter: drop-shadow(0 3px 5px rgba(43, 30, 26, 0.3));
}

/* Map Gallery hover — phone slides a few px further from the kiosk and a
   dotted "syncing" line fades in to fill the gap. Resting values (line
   invisible, phone untranslated) are declared unconditionally so the
   illustration looks correct even before any motion-preference media
   query is evaluated; the transitions/animation and the reduced-motion
   fallback are split out below. */
.wc-sync-line {
  opacity: 0;
}
.wc-phone-sync {
  translate: 0 0;
}

/* Kiosk fan-out companion to the phone slide above — same 350ms ease
   used by the browser-stack illustrations' fan-out (see .wc-stack-frame
   further down), so this illustration's hover motion feels like part of
   the same system. rotate/translate are standalone properties, so they
   compose with the kiosk group's own translate/scale attribute instead
   of fighting it; the CTA pill lives inside this group, so it rides along
   naturally rather than needing its own hover rule. Rotating counter-
   clockwise lifts the kiosk's bottom-right corner (where the phone
   overlaps it) up and away — opposite the phone's own down-right slide —
   so the two read as fanning open from that shared corner. */
.wc-kiosk-fan {
  rotate: 0deg;
  translate: 0 0;
}

@media (prefers-reduced-motion: no-preference) {
  .wc-sync-line {
    transition: opacity 350ms ease;
  }
  .wc-phone-sync {
    transition: translate 350ms ease;
  }
  .wc-kiosk-fan {
    transition: rotate 350ms ease, translate 350ms ease;
  }
  [data-project-id="map-gallery"]:hover .wc-phone-sync,
  [data-project-id="map-gallery"]:focus-within .wc-phone-sync {
    translate: 8px 6px;
  }
  [data-project-id="map-gallery"]:hover .wc-kiosk-fan,
  [data-project-id="map-gallery"]:focus-within .wc-kiosk-fan {
    rotate: -2.5deg;
    translate: -7px -6px;
  }
  [data-project-id="map-gallery"]:hover .wc-sync-line,
  [data-project-id="map-gallery"]:focus-within .wc-sync-line {
    opacity: 1;
    animation: wc-sync-travel 800ms linear infinite;
  }
}

/* Reduced motion: the separated state + line still appear on hover (so
   the interaction isn't silently dead), but instantly — no slide
   transition, no draw-in, no traveling dash. */
@media (prefers-reduced-motion: reduce) {
  [data-project-id="map-gallery"]:hover .wc-phone-sync,
  [data-project-id="map-gallery"]:focus-within .wc-phone-sync {
    translate: 8px 6px;
  }
  [data-project-id="map-gallery"]:hover .wc-kiosk-fan,
  [data-project-id="map-gallery"]:focus-within .wc-kiosk-fan {
    rotate: -2.5deg;
    translate: -7px -6px;
  }
  [data-project-id="map-gallery"]:hover .wc-sync-line,
  [data-project-id="map-gallery"]:focus-within .wc-sync-line {
    opacity: 1;
  }
}

/* Kiosk screen's "touch to start" pill — a slow, quiet breathing pulse
   (scale + a touch of brightness) rather than a flashing/bouncing button,
   to read as a gentle invitation without drawing constant attention. Long
   pause between pulses comes from the keyframe holding identical values
   from 24% through 100% of the cycle. transform-box: fill-box scales the
   pill around its own center regardless of the kiosk group's own
   scale/translate, so the kiosk and phone themselves never move.

   Hover/focus uses the standalone `scale`/`opacity`/`filter` properties
   (not `transform`, which the ambient keyframe already owns) so the
   response is immediate — composing on top of wherever the slow pulse
   currently is, rather than only nudging a custom property that the
   keyframe might not read again for up to 4.5s. `filter` needs
   !important since a *running* CSS animation otherwise always wins that
   property over a plain hover rule. */
.wc-kiosk-cta {
  transform-box: fill-box;
  transform-origin: center;
  scale: 1;
  transition: scale 250ms ease;
}

@media (prefers-reduced-motion: no-preference) {
  .wc-kiosk-cta {
    animation: wc-kiosk-cta-pulse 4.5s ease-in-out infinite;
  }
}

[data-project-id="map-gallery"]:hover .wc-kiosk-cta,
[data-project-id="map-gallery"]:focus-within .wc-kiosk-cta {
  scale: 1.08;
  opacity: 1 !important;
  filter: brightness(1.2) !important;
}

@keyframes wc-kiosk-cta-pulse {
  0%, 24%, 100% {
    transform: scale(1);
    opacity: 0.9;
    filter: brightness(1);
  }
  12% {
    transform: scale(1.04);
    opacity: 1;
    filter: brightness(1.12);
  }
}

@keyframes wc-sync-travel {
  to {
    stroke-dashoffset: -12;
  }
}

/* Cross-product navigation frames' resting rotation — moved from the SVG
   transform attribute into CSS (with a matching transform-origin) so the
   hover state below can smoothly animate the same property instead of
   fighting an attribute-driven value. This declaration is NOT inside the
   reduced-motion gate below — it's the resting look, not the animation. */
.wc-thread-frame-a,
.wc-thread-frame-b,
.wc-thread-frame-mobile {
  transform-box: view-box;
}
.wc-thread-frame-a { transform: rotate(-5deg); transform-origin: 84px 55px; }
.wc-thread-frame-b { transform: rotate(3deg); transform-origin: 170px 78px; }
.wc-thread-frame-mobile { transform: rotate(7deg); transform-origin: 219px 114px; }

/* Illustration hover interactions — subtle, ~350ms ease, matching the
   rest of the site's hover language. Entirely gated behind
   prefers-reduced-motion: no-preference, so reduced-motion users just see
   each illustration's plain resting state with no hover-triggered
   movement at all (not just a faster/instant version of it). */
@media (prefers-reduced-motion: no-preference) {

  /* 2 & 3. Stacked frames (Event Overview Template, Sponsorship) — fan
     out a little further and the front frame picks up more shadow depth.
     `translate` (the standalone property) layers on top of each frame's
     own attribute-based rotate/position instead of fighting it. */
  .wc-stack-frame {
    translate: 0 0;
    transition: translate 350ms ease, filter 350ms ease;
  }
  [data-project-id="event-template"]:hover .wc-stack-back,
  [data-project-id="event-template"]:focus-within .wc-stack-back,
  [data-project-id="sponsorship"]:hover .wc-stack-back,
  [data-project-id="sponsorship"]:focus-within .wc-stack-back {
    translate: -4px -3px;
  }
  [data-project-id="event-template"]:hover .wc-stack-mid,
  [data-project-id="event-template"]:focus-within .wc-stack-mid,
  [data-project-id="sponsorship"]:hover .wc-stack-mid,
  [data-project-id="sponsorship"]:focus-within .wc-stack-mid {
    translate: -1px -1px;
  }
  [data-project-id="event-template"]:hover .wc-stack-front,
  [data-project-id="event-template"]:focus-within .wc-stack-front,
  [data-project-id="sponsorship"]:hover .wc-stack-front,
  [data-project-id="sponsorship"]:focus-within .wc-stack-front {
    translate: 4px 4px;
    filter: drop-shadow(0 4px 6px rgba(43, 30, 26, 0.28));
  }

  /* 4. Carmichael HART — the "after" frame slides further right/down,
     revealing more of the muted "before" frame beneath it. */
  .wc-curtain-front {
    translate: 0 0;
    transition: translate 350ms ease;
  }
  [data-project-id="carmichael-hart"]:hover .wc-curtain-front,
  [data-project-id="carmichael-hart"]:focus-within .wc-curtain-front {
    translate: 12px 9px;
  }

  /* 5. Cross-product navigation — connector dots grow slightly and each
     frame rotates a few more degrees, so the "thread" reads as active. */
  .wc-thread-dot {
    transform-box: fill-box;
    transform-origin: 50% 50%;
    transition: transform 350ms ease;
  }
  [data-project-id="cross-navigation"]:hover .wc-thread-dot,
  [data-project-id="cross-navigation"]:focus-within .wc-thread-dot {
    transform: scale(1.4);
  }

  .wc-thread-frame-a,
  .wc-thread-frame-b,
  .wc-thread-frame-mobile {
    transition: transform 350ms ease;
  }
  [data-project-id="cross-navigation"]:hover .wc-thread-frame-a,
  [data-project-id="cross-navigation"]:focus-within .wc-thread-frame-a { transform: rotate(-8deg); }
  [data-project-id="cross-navigation"]:hover .wc-thread-frame-b,
  [data-project-id="cross-navigation"]:focus-within .wc-thread-frame-b { transform: rotate(6deg); }
  [data-project-id="cross-navigation"]:hover .wc-thread-frame-mobile,
  [data-project-id="cross-navigation"]:focus-within .wc-thread-frame-mobile { transform: rotate(10deg); }
}

/* Row entrance + skill-tag stagger are minimized for reduced motion —
   both appear immediately with no movement. transition: none here also
   cancels the divider's border-bottom-color easing, so under reduced
   motion the dashed -> solid hover state still happens, just instantly
   rather than eased — consistent with how the illustration hovers behave
   under this same preference. */
@media (prefers-reduced-motion: reduce) {
  .menu-item {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .skill-tag {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

.portfolio-unlock-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-6);
  background-color: var(--color-accent-subtle);
  border: 1px solid var(--color-border-accent);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-8);
}

.portfolio-unlock-banner-text {
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-accent);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}


/* ==========================================================================
   3. About Me — a full-width, two-column notecard bio. Community, Fun
      Reads, Coffee, and Charlie now live in the shared popover system
      below rather than as always-visible sections.
   ========================================================================== */
.barista-environment {
  position: relative;
  background-color: var(--color-bg-primary);
  background-image: radial-gradient(rgba(43, 30, 26, 0.08) 1px, transparent 1px);
  background-size: 22px 22px;
}

.environment-masthead {
  max-width: 640px;
  margin-bottom: var(--space-10);
}

/* Bio notecard — spans the same content width as the rest of the page's
   full-width sections (no extra max-width of its own). */
.about-bio-card {
  position: relative;
  background-color: var(--color-bg-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-8);
  box-shadow: var(--shadow-md);
}

/* Magazine-style two-column layout so lines don't stretch too long at full
   section width — lead/body copy on the left, pull-quote + facts on the
   right. Stacks to one column below 860px. */
.about-bio-columns {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8);
}

@media (min-width: 860px) {
  .about-bio-columns {
    grid-template-columns: 1.15fr 0.85fr;
    gap: var(--space-12);
  }
}

.about-bio-col {
  min-width: 0;
}

.about-bio {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.about-bio p {
  font-size: var(--text-base);
  line-height: var(--line-height-relaxed);
  color: var(--color-text-secondary);
}

.about-lead {
  font-size: var(--text-lg);
  color: var(--color-text-primary);
  line-height: var(--line-height-relaxed);
}

.about-philosophy-quote {
  margin: 0 0 var(--space-6) 0;
  padding: 1.25rem 1.5rem;
  background: var(--color-accent-subtle);
  border-left: 3px solid var(--color-accent);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}

.about-bio-col:first-child .about-philosophy-quote {
  margin-top: var(--space-6);
}

.about-philosophy-quote p {
  font-family: var(--font-family-serif);
  font-style: italic;
  font-size: var(--text-lg);
  color: var(--color-text-primary);
  line-height: var(--line-height-snug);
  margin: 0;
}

.about-meta-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-6);
}

.about-meta-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}

.about-meta-icon {
  font-size: 1.05rem;
}

/* Toolkit — reads as a fourth item in the same quick-profile info area,
   not a separate card/subsection; the heading row reuses .about-meta-item
   as-is so it inherits identical icon/text sizing and hierarchy. */
.about-toolkit {
  margin-top: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.about-toolkit-divider {
  border-top: 1px dashed var(--color-border);
  width: 100%;
}

.about-toolkit-groups {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.about-toolkit-group {
  font-size: var(--text-xs);
  color: var(--color-text-secondary);
  line-height: var(--line-height-relaxed);
}

.about-toolkit-label {
  color: var(--color-text-accent);
  font-weight: var(--font-weight-semibold);
  margin-right: var(--space-1);
}

.about-closing-text {
  font-size: var(--text-base);
  color: var(--color-text-secondary);
  line-height: var(--line-height-relaxed);
  margin-bottom: 0;
}

/* ==========================================================================
   4. About Me — "Life outside the screen" photo carousel. A plain
      scroll-snap track (native touch/trackpad scrolling, plus a small
      mouse-drag handler in js/main.js) with click-to-scroll arrow buttons.
      Sized so ~3.5-3.75 items show on desktop with the next one peeking in
      on the right, rather than a neat, static-feeling grid of whole photos.
   ========================================================================== */
.life-carousel {
  margin-top: var(--space-8);
  padding-top: var(--space-6);
  border-top: 1px dashed var(--color-border);
}

.life-carousel-heading {
  margin: 0 0 var(--space-4) 0;
  font-family: var(--font-family-serif);
  font-style: italic;
  font-size: var(--text-xl);
  color: var(--color-text-primary);
}

.life-carousel-viewport {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.life-carousel-track {
  display: flex;
  gap: var(--space-4);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  list-style: none;
  margin: 0;
  /* Extra vertical padding (beyond what the photos need) so the hover
     lift/scale never gets clipped by the track's own scroll box. */
  padding: var(--space-3) 0;
  flex: 1;
  min-width: 0;
  cursor: grab;
}

.life-carousel-track.is-dragging {
  cursor: grabbing;
  scroll-snap-type: none;
}

@media (prefers-reduced-motion: no-preference) {
  .life-carousel-track {
    scroll-behavior: smooth;
  }
}

/* Percentage-based width (not a fixed px) so the "~3.5 items + a peek"
   composition holds regardless of how wide the About card ends up being;
   fewer, larger items peek on narrower screens instead of forcing the
   same item count everywhere. */
.life-carousel-item {
  flex: 0 0 78%;
  scroll-snap-align: start;
  list-style: none;
  rotate: 0deg;
  translate: 0 0;
  scale: 1;
  transition: rotate 200ms ease, translate 200ms ease, scale 200ms ease;
}

@media (min-width: 560px) {
  .life-carousel-item {
    flex-basis: 46%;
  }
}

@media (min-width: 900px) {
  .life-carousel-item {
    flex-basis: 27%;
  }
}

/* Very subtle differences in resting rotation per item — enough to break
   the rigid-grid feeling without any single photo looking crooked. Cycles
   across all 8 items (not just the first 4) so the pattern doesn't repeat
   identically back-to-back; each item's ambient drift (further down) is
   also independently timed regardless of which rotation/drift shape it
   shares with another item. */
.life-carousel-item:nth-child(8n+1) { rotate: -0.5deg; }
.life-carousel-item:nth-child(8n+2) { rotate: 0.75deg; }
.life-carousel-item:nth-child(8n+3) { rotate: -0.75deg; }
.life-carousel-item:nth-child(8n+4) { rotate: 0.4deg; }
.life-carousel-item:nth-child(8n+5) { rotate: -0.5deg; }
.life-carousel-item:nth-child(8n+6) { rotate: 0.75deg; }
.life-carousel-item:nth-child(8n+7) { rotate: -0.75deg; }
.life-carousel-item:nth-child(8n+8) { rotate: 0.4deg; }

/* Ambient drift lives on `transform` (translate + a small extra rotation
   wobble around 0) while the resting rotation above lives on the
   standalone `rotate` property — different properties, so they compose
   instead of fighting, and it's what lets hover below cleanly override
   just the standalone properties (which transition normally) without
   fighting the keyframe animation for control of the same property. */
.life-carousel-item:nth-child(1) { animation: life-photo-drift-a 8.2s ease-in-out infinite; animation-delay: -1.4s; }
.life-carousel-item:nth-child(2) { animation: life-photo-drift-b 9.6s ease-in-out infinite; animation-delay: -3.8s; }
.life-carousel-item:nth-child(3) { animation: life-photo-drift-c 7.4s ease-in-out infinite; animation-delay: -0.6s; }
.life-carousel-item:nth-child(4) { animation: life-photo-drift-d 11.2s ease-in-out infinite; animation-delay: -5.9s; }
.life-carousel-item:nth-child(5) { animation: life-photo-drift-b 10.1s ease-in-out infinite; animation-delay: -2.7s; }
.life-carousel-item:nth-child(6) { animation: life-photo-drift-c 7.9s ease-in-out infinite; animation-delay: -4.4s; }
.life-carousel-item:nth-child(7) { animation: life-photo-drift-d 11.8s ease-in-out infinite; animation-delay: -6.5s; }
.life-carousel-item:nth-child(8) { animation: life-photo-drift-a 8.7s ease-in-out infinite; animation-delay: -1.9s; }

/* Barely-perceptible on purpose — each oscillates around 0 (not around a
   resting value, since that part lives in the standalone `rotate` above)
   within 1-3px horizontal, 2-4px vertical, and well under a degree of
   rotation. Four shapes reused across the 8 items, but every item's own
   duration/delay is unique so none of them ever read as synchronized. */
@keyframes life-photo-drift-a {
  0%   { transform: translate(0, 0) rotate(0deg); }
  35%  { transform: translate(1.5px, 3px) rotate(0.5deg); }
  70%  { transform: translate(-1px, -2.5px) rotate(-0.35deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}

@keyframes life-photo-drift-b {
  0%   { transform: translate(0, 0) rotate(0deg); }
  30%  { transform: translate(-1.5px, 2.5px) rotate(-0.4deg); }
  65%  { transform: translate(1px, -3px) rotate(0.55deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}

@keyframes life-photo-drift-c {
  0%   { transform: translate(0, 0) rotate(0deg); }
  40%  { transform: translate(1px, -2.5px) rotate(0.3deg); }
  75%  { transform: translate(-1.5px, 3.5px) rotate(-0.6deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}

@keyframes life-photo-drift-d {
  0%   { transform: translate(0, 0) rotate(0deg); }
  32%  { transform: translate(-1px, 3px) rotate(-0.45deg); }
  68%  { transform: translate(1.5px, -2px) rotate(0.4deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}

/* Hover/focus: pause the drift, straighten, lift ~4px, scale up barely,
   and step forward in stacking order — like picking the photo up off the
   pile. All three are plain (non-animated) properties so the transition
   above plays smoothly in both directions, unlike trying to override the
   `transform` shorthand that the keyframes already control. */
.life-carousel-item:hover,
.life-carousel-item:focus-visible {
  rotate: 0deg;
  translate: 0 -4px;
  scale: 1.015;
  z-index: 5;
  animation-play-state: paused;
}

.life-carousel-item:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 4px;
  border-radius: var(--radius-md);
}

.life-carousel-item img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-md);
  border: 3px solid var(--color-bg-elevated);
  box-shadow: var(--shadow-pin);
  -webkit-user-drag: none;
  user-select: none;
}

.life-carousel-caption {
  display: block;
  margin-top: var(--space-2);
  font-size: 1rem;
  line-height: 1.25;
  transition: font-size 200ms ease;
}

.life-carousel-item:hover .life-carousel-caption,
.life-carousel-item:focus-visible .life-carousel-caption {
  font-size: 1.12rem;
}

.life-carousel-arrow {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-full);
  background-color: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  color: var(--color-text-secondary);
  cursor: pointer;
}

.life-carousel-arrow:hover {
  color: var(--color-text-primary);
  background-color: var(--color-border-subtle);
}

.life-carousel-arrow:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .life-carousel-item:nth-child(1),
  .life-carousel-item:nth-child(2),
  .life-carousel-item:nth-child(3),
  .life-carousel-item:nth-child(4),
  .life-carousel-item:nth-child(5),
  .life-carousel-item:nth-child(6),
  .life-carousel-item:nth-child(7),
  .life-carousel-item:nth-child(8) {
    animation: none;
  }
}

/* Scroll entrance — same soft/editorial motion language and easing as the
   hero (js/main.js adds .is-revealed once #about crosses ~20% into the
   viewport; see the IntersectionObserver next to the existing Work-row
   one). Unlike the hero's page-load @keyframes, this uses the same
   transition + class-toggle + transition-delay stagger convention already
   established for the Work section's .menu-item/.skill-tag reveal, just
   with the hero's cubic-bezier(0.22, 1, 0.36, 1) easing. Deliberately
   does NOT touch .life-carousel-item — its existing ambient drift keeps
   running underneath throughout, untouched; only the carousel's outer
   wrapper participates in this entrance. */
#about .environment-masthead,
#about .about-bio-card,
#about .about-bio-columns > .about-bio-col:first-child,
#about .about-meta-grid,
#about .about-toolkit,
#about .life-carousel {
  opacity: 0;
  transition: opacity 550ms cubic-bezier(0.22, 1, 0.36, 1), transform 550ms cubic-bezier(0.22, 1, 0.36, 1);
}

#about .environment-masthead {
  transform: translateY(9px);
}

#about .about-bio-card {
  transform: translateY(12px) scale(0.99);
  transition-delay: 90ms;
}

#about .about-bio-columns > .about-bio-col:first-child {
  transform: translateY(10px);
  transition-delay: 170ms;
}

#about .about-meta-grid {
  transform: translateY(10px);
  transition-delay: 230ms;
}

#about .about-toolkit {
  transform: translateY(10px);
  transition-delay: 290ms;
}

#about .life-carousel {
  transform: translateY(10px);
  transition-delay: 350ms;
}

#about.is-revealed .environment-masthead,
#about.is-revealed .about-bio-columns > .about-bio-col:first-child,
#about.is-revealed .about-meta-grid,
#about.is-revealed .about-toolkit,
#about.is-revealed .life-carousel {
  opacity: 1;
  transform: translateY(0);
}

#about.is-revealed .about-bio-card {
  opacity: 1;
  transform: translateY(0) scale(1);
}

@media (prefers-reduced-motion: reduce) {
  #about .environment-masthead,
  #about .about-bio-card,
  #about .about-bio-columns > .about-bio-col:first-child,
  #about .about-meta-grid,
  #about .about-toolkit,
  #about .life-carousel {
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ==========================================================================
   5. Closing Footer — Leaving the Café
   ========================================================================== */
.site-footer {
  border-top: 1px solid var(--color-border);
  background: var(--color-clay-deep);
  padding-top: var(--space-20);
  padding-bottom: var(--space-12);
}

.site-footer .section-label {
  color: var(--color-butter);
}

.footer-content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-8);
  max-width: 800px;
}

.footer-invitation {
  font-size: var(--text-3xl);
  line-height: var(--line-height-tight);
  color: var(--color-text-inverse);
  letter-spacing: -0.02em;
}

.footer-invitation .highlight-serif {
  font-family: var(--font-family-serif);
  font-style: italic;
  color: var(--color-butter);
  font-weight: var(--font-weight-medium);
}

.footer-subtext {
  font-size: var(--text-lg);
  color: var(--color-text-inverse-muted);
  line-height: var(--line-height-relaxed);
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-2);
}

.footer-links .btn-secondary {
  background-color: rgba(247, 243, 236, 0.06);
  color: var(--color-text-inverse);
  border-color: var(--color-border-espresso);
}

.footer-links .btn-secondary:hover {
  background-color: rgba(247, 243, 236, 0.12);
  border-color: var(--color-butter);
  color: var(--color-butter);
}

.footer-bottom {
  margin-top: var(--space-16);
  padding-top: var(--space-6);
  border-top: 1px solid var(--color-border-espresso);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  font-size: var(--text-xs);
  color: var(--color-text-inverse-muted);
}

.footer-bottom .nav-link {
  color: var(--color-text-inverse-muted);
}

.footer-bottom .nav-link:hover {
  color: var(--color-butter);
}

.footer-bottom p:first-of-type {
  color: var(--color-text-inverse-muted);
}

.footer-personal-callback {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-text-inverse-muted);
  font-family: var(--font-family-serif);
  font-style: italic;
  margin-top: var(--space-1);
}

.footer-closing-note {
  font-family: var(--font-family-hand);
  font-size: 1.15rem;
  color: var(--color-butter);
  margin-top: var(--space-2);
}

/* Scroll entrance — simpler/shorter than About's (js/main.js adds
   .is-revealed to #contact via the same IntersectionObserver instance
   used for #about). .footer-bottom (copyright/back-to-top) isn't part of
   the requested sequence, so it's left as normal, always-visible content. */
#contact .section-label,
#contact .footer-invitation,
#contact .footer-subtext,
#contact .footer-links {
  opacity: 0;
  transition: opacity 500ms cubic-bezier(0.22, 1, 0.36, 1), transform 500ms cubic-bezier(0.22, 1, 0.36, 1);
}

#contact .section-label {
  transform: translateY(9px);
}

#contact .footer-invitation {
  transform: translateY(10px);
  transition-delay: 120ms;
  transition-duration: 550ms;
}

#contact .footer-subtext {
  transform: translateY(6px);
  transition-delay: 240ms;
}

#contact .footer-links {
  transform: translateY(5px);
  transition-delay: 360ms;
  transition-duration: 450ms;
}

#contact.is-revealed .section-label,
#contact.is-revealed .footer-invitation,
#contact.is-revealed .footer-subtext,
#contact.is-revealed .footer-links {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  #contact .section-label,
  #contact .footer-invitation,
  #contact .footer-subtext,
  #contact .footer-links {
    transition: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}
