/* ========================================================================
   /projects/ and /projects/<album>/ — the Projects album system.

     1. .uk-hero3-*  -> 21st.dev/@ravikatiyar162/components/hero-3
                        ("Animated Marquee Hero", demo id 7611) — kept as the
                        first impression; its 12-image strip is written
                        out in index.html.
     2. .pa-*        -> the album page (level 2): hero + one grid of every
                        image. Data: projects-albums.js.
     3. .pv-*        -> the fullscreen coverflow viewer (level 3), adapted
                        from 21st.dev/@educalvolpz/components/coverflow-carousel.

   1b. .uk-gal-*   -> 21st.dev/@youcefbnm/components/animated-gallery —
                        the /projects/ landing gallery, as deployed. Its
                        cards link into the album routes.

   The How-It-Works explainer (.uk-hiw-*, .exp-*) styles were retired with
   the album pass; they are preserved in projects.css.before-albums.
   ======================================================================== */


/* ======================================================================
   1 - AnimatedMarqueeHero  (hero-3)
   ====================================================================== */

/* <section className="relative w-full h-screen overflow-hidden bg-background
                       flex flex-col items-center justify-center text-center px-4"> */
.uk-hero3 {
  position: relative;
  width: 100%;
  height: 100svh;
  overflow: hidden;
  background: var(--uk-bone);          /* bg-background */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding-inline: 1rem;                /* px-4 */
}

/* <div className="z-10 flex flex-col items-center"> */
.uk-hero3__content {
  position: relative;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* tagline: "mb-4 inline-block rounded-full border border-border bg-card/50
             px-4 py-1.5 text-sm font-medium text-muted-foreground backdrop-blur-sm" */
.uk-hero3__tagline {
  margin: 0 0 1rem;
  display: inline-block;
  border-radius: 9999px;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--uk-white) 50%, transparent);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  padding: 0.375rem 1rem;
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 500;
  color: var(--text-secondary);
}

/* <h1 className="text-5xl md:text-7xl font-bold tracking-tighter text-foreground"> */
.uk-hero3__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: calc(3.75rem * var(--k-display));
  line-height: 1.04;
  font-weight: 300;
  letter-spacing: -.03em;              /* tracking-tighter */
  color: var(--text-primary);
  max-width: none;
}
.uk-hero3__title span { display: inline-block; }   /* className="inline-block" */

/* marquee frame: "absolute bottom-0 left-0 w-full h-1/3 md:h-2/5
   [mask-image:linear-gradient(to_bottom,transparent,black_20%,black_80%,transparent)]" */
.uk-hero3__marquee {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 33.333333%;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 20%, #000 80%, transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 20%, #000 80%, transparent);
}

/* <motion.div className="flex gap-4" animate={{ x: ["-100%","0%"],
                 ease:"linear", duration:40, repeat:Infinity }}> */
/* The reference track is a plain block-level flex div inside the w-full
   frame, so its width is the frame's width and the flex-shrink-0 slides
   overflow it. That is what makes x:-100% a one-frame offset. Do not set
   width:max-content here. */
.uk-hero3__track {
  display: flex;
  gap: 1rem;
  animation: uk-hero3-marquee 40s linear infinite;
}
@keyframes uk-hero3-marquee {
  from { transform: translateX(-100%); }
  to   { transform: translateX(0%); }
}

/* <div className="relative aspect-[3/4] h-48 md:h-64 flex-shrink-0"
        style={{ rotate: index % 2 === 0 ? -2deg : 5deg }}> */
.uk-hero3__slide {
  position: relative;
  aspect-ratio: 3 / 4;
  height: 12rem;
  /* mobile pass 2026-10-01: never taller than the strip it runs in (a third
     of the screen), so short phones and landscape keep whole cards */
  height: min(12rem, 28svh);
  flex-shrink: 0;
}
.uk-hero3__slide:nth-child(odd)  { transform: rotate(-2deg); }  /* even index */
.uk-hero3__slide:nth-child(even) { transform: rotate(5deg); }   /* odd index  */

/* <img className="w-full h-full object-cover rounded-2xl shadow-md" /> */
.uk-hero3__slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 1rem;
  box-shadow: 0 4px 6px -1px rgba(63, 36, 34, .12), 0 2px 4px -2px rgba(63, 36, 34, .12);
}

/* md: breakpoint (Tailwind md = 768px) */
@media (min-width: 768px) {
  .uk-hero3__title   { font-size: calc(clamp(5.5rem, 7.6vw, 7.5rem) * var(--k-display)); }
  .uk-hero3__marquee { height: 40%; }              /* md:h-2/5 */
  .uk-hero3__slide   { height: 16rem; height: min(16rem, 34svh); }   /* md:h-64 */
}

/* FADE_IN_ANIMATION_VARIANTS - hidden { opacity 0, y 10 } -> show { opacity 1, y 0 },
   spring stiffness 100 / damping 20. Title words stagger 0.1s. (The
   description and CTA were removed in the 2026-10-01 client feedback.) */
@keyframes uk-hero3-fade-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
.uk-hero3__tagline,
.uk-hero3__title span {
  animation: uk-hero3-fade-in .7s cubic-bezier(.22, 1, .36, 1) backwards;
}
.uk-hero3__title span:nth-child(1) { animation-delay: 0s; }
.uk-hero3__title span:nth-child(2) { animation-delay: .1s; }
.uk-hero3__title span:nth-child(3) { animation-delay: .2s; }
.uk-hero3__title span:nth-child(4) { animation-delay: .3s; }





/* ======================================================================
   1b - Animated Scroll Gallery  (animated-gallery / animated-scroll-gallery)

   The /projects/ landing gallery, restored verbatim from the deployed
   stylesheet (projects.css.before-albums). Album pages never render it.
   ====================================================================== */

/* demo wrapper: <div className="relative bg-white"> */
.uk-gallery {
  position: relative;
  background: var(--uk-white);
}

/* ContainerScroll - "relative min-h-[120vh]", demo override "h-[350vh]",
   perspective 1000px / perspectiveOrigin center top / preserve-3d */
.uk-gal-scroll {
  position: relative;
  height: 350vh;
  perspective: 1000px;
  perspective-origin: center top;
  transform-style: preserve-3d;
}

/* ContainerSticky - "sticky left-0 top-0 min-h-[30rem] w-full overflow-hidden",
   demo override "h-svh" */
.uk-gal-sticky {
  position: sticky;
  left: 0;
  top: 0;
  min-height: 30rem;
  min-height: min(30rem, 100svh);      /* landscape phones: never taller than the screen */
  height: 100svh;
  width: 100%;
  overflow: hidden;
  perspective: 1000px;
  perspective-origin: center top;
  transform-style: preserve-3d;
  transform-origin: 50% 50%;
}

/* GalleryContainer - "relative grid size-full grid-cols-3 gap-2 rounded-2xl"
   style: rotateX (75 -> 0 over scroll [0, 0.5]), scale (1.2 -> 1 over [0.5, 0.9]) */
.uk-gal-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
  width: 100%;
  height: 100%;
  border-radius: 1rem;
  transform-style: preserve-3d;
  perspective: 1000px;
  transform: rotateX(75deg) scale(1.2);   /* progress = 0 resting state */
  will-change: transform;
}

/* GalleryCol - "relative flex w-full flex-col gap-2", style y from scroll */
.uk-gal-col {
  position: relative;
  display: flex;
  width: 100%;
  flex-direction: column;
  gap: 0.5rem;
  will-change: transform;
}
/* demo column offsets + yRange start values (driven by projects.js) */
.uk-gal-col--1 { margin-top: -0.5rem; transform: translateY(-10%); }  /* -mt-2,     yRange ["-10%","2%"] */
.uk-gal-col--2 { margin-top: -50%;    transform: translateY(15%);  }  /* mt-[-50%], yRange ["15%","5%"]  */
.uk-gal-col--3 { margin-top: -0.5rem; transform: translateY(-10%); }  /* -mt-2,     yRange ["-10%","2%"] */

/* item: the reference is a bare
   <img className="aspect-video block h-auto max-h-full w-full rounded-md object-cover shadow" />
   The anchor takes the img's box so the column rhythm is unchanged; the
   permitted card text (title / category / "View study") sits on top of the
   image rather than adding height to the column. */
/* ---- deployment fix: the four cards share the column ----
   The reference sizes each item `aspect-video` and lets the column overflow
   the sticky stage, which `overflow-hidden` then clips. At this site's
   viewport that put a 960px column inside a 632px stage: card 3 was cut to a
   strip and card 4 never appeared at all — the vertical slivers this pass was
   asked to fix, and six of the twelve generated assets simply not visible.
   The cards now flex to share their column, so all twelve read as whole cards
   and the crop lands inside the image instead of across the card. The columns
   still differ in height because the centre column's `mt-[-50%]` stretches its
   box past the stage, which is what keeps the reference's staggered rhythm. */
.uk-gal-card {
  position: relative;
  display: block;
  flex: 1 1 0;
  min-height: 0;
  width: 100%;
  border-radius: 0.375rem;             /* rounded-md */
  overflow: hidden;
  box-shadow: 0 1px 3px 0 rgba(63, 36, 34, .12), 0 1px 2px -1px rgba(63, 36, 34, .12);
  text-decoration: none;
  color: var(--uk-white);
  transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.uk-gal-card img {
  display: block;
  height: 100%;
  width: 100%;
  object-fit: cover;
}

/* hover/focus brings the card slightly forward — kept from the accepted state */
.uk-gal-card:hover,
.uk-gal-card:focus-visible {
  transform: translateY(-4px);
  box-shadow: 0 10px 22px -6px rgba(63, 36, 34, .34);
  z-index: 2;
}
/* Card text: category, project name, "View images" — shown at every width
   (client, 2026-10-01: "Just keep categories for each project"). The scrim
   is a fade-in above the text and a band at least 74% opaque behind it, so
   every line holds 4.5:1 even over a white render: measured worst case (the
   band over pure white) is 5.5:1 for the category, 6.8:1 for "View images"
   and 7:1 for the title. --uk-gal-fade is the height of the fade. */
.uk-gal-card__meta {
  --uk-gal-fade: 2.25rem;
  position: absolute;
  inset: auto 0 0 0;
  padding: var(--uk-gal-fade) 0.75rem 0.625rem;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  background: linear-gradient(to top,
    rgba(40, 24, 22, .86) 0,
    rgba(40, 24, 22, .74) calc(100% - var(--uk-gal-fade)),
    rgba(40, 24, 22, 0) 100%);
  font-family: var(--font-body);
  text-align: left;
}
/* The small tracked label above the project name: the album's category
   (Residential, Institutional). */
.uk-gal-card__facet {
  margin: 0;
  font-family: var(--font-display);
  font-style: normal;
  font-size: 1rem;
  line-height: 1.2;
  letter-spacing: 0;
  color: color-mix(in srgb, var(--uk-sandstone) 90%, var(--uk-white));
}
.uk-gal-card__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: .95rem;
  line-height: 1.05;
  font-weight: 400;
  color: var(--uk-white);
}
.uk-gal-card__open {
  font-size: 0.8rem;
  line-height: 1.3;
  letter-spacing: 0;
  /* the accent, as on the home "View images" links; ochre lifted toward the
     cream so it holds 4.5:1 on the scrim band even over a white render */
  color: color-mix(in srgb, var(--uk-ochre) 70%, var(--text-on-dark));
}
.uk-gal-card:focus-visible { outline: 2px solid var(--uk-ochre); outline-offset: 2px; }


/* ---- one card per album (.uk-gal-grid--albums) ----
   The reference wall is four image tiles per column; the landing carries one
   card per published album. The scroll maths and tilt/scale are untouched;
   what changes is the frame the cards land in.

   Below 1100px: a two-column board. The columns step aside (display:
   contents, so their parallax transform has nothing to move) and the cards
   take album order (--gal-order, written by build_site.mjs). With an odd
   number of albums the first card spans both columns (.uk-gal-card--lead),
   so 5 albums read 1 + 2 + 2 with no gaps, and each card is wide enough to
   carry its text. The rows share the stage height equally. */
/* Mobile pass 2026-10-01: the side gutters are 9vw (were 0.5rem), the same
   rule as desktop's 7.5vw, so the board at scale(1.2) lands inside the
   screen instead of cutting the outer cards' text off both edges. The two
   board columns also keep the reference's parallax: projects.js moves each
   column's cards on their own range (--gal-y) over the second half of the
   scroll, so they settle level at the end, as the desktop columns do. */
.uk-gal-grid--albums {
  box-sizing: border-box;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  padding: 6.25rem 9vw 0.75rem;        /* top: clears the fixed header */
}
.uk-gal-grid--albums .uk-gal-col { display: contents; }
.uk-gal-grid--albums .uk-gal-card {
  order: var(--gal-order, 0);
  transform: translate3d(0, var(--gal-y, 0%), 0);
  transition: box-shadow var(--dur-fast) var(--ease-out);
}
.uk-gal-grid--albums .uk-gal-card:hover,
.uk-gal-grid--albums .uk-gal-card:focus-visible { transform: translate3d(0, calc(var(--gal-y, 0%) - 4px), 0); }
.uk-gal-grid--albums .uk-gal-card--lead { grid-column: 1 / -1; }
/* legibility on phones: the tracked labels at 12px, not 11px */
.uk-gal-grid--albums .uk-gal-card__facet { font-size: 0.95rem; }
.uk-gal-grid--albums .uk-gal-card__open { font-size: 0.8rem; }
@media (max-height: 640px) {
  .uk-gal-grid--albums .uk-gal-card__meta { --uk-gal-fade: 1.5rem; padding-bottom: 0.5rem; }
}

/* The grid holds scale(1.2) until the tilt has finished (progress 0.5). The
   7.5vw side gutters are sized so the wall at 1.2x lands just inside the
   viewport (1.2 * 7.5vw ≈ the 10vw the scale adds per side). The deeper
   bottom inset (3rem + 4svh) grows on tall screens. */
@media (min-width: 768px) {
  .uk-gal-grid--albums {
    gap: 0.75rem;
    padding: 6.25rem 9vw calc(3rem + 4svh);
  }
  .uk-gal-grid--albums .uk-gal-card__meta { --uk-gal-fade: 2.75rem; padding-inline: 1.125rem; padding-bottom: 0.875rem; }
  .uk-gal-grid--albums .uk-gal-card__title { font-size: calc(1.5rem * var(--k-display)); }
}

/* 1100px and up: the reference's three columns, each moving on its own
   yRange. Cards deal row by row; a remainder goes to the centre column first
   (build_site.mjs), so 5 albums are 2 / 1 / 2 — two stacks flanking one
   full-height centre card, a triptych with no holes. The centre column drops
   the reference's mt-[-50%] (with this few cards it pushes the top card off
   the stage). It takes the side columns' -mt-2 instead and is lifted by the
   difference between its final yRange (5%) and theirs (2%) of the column
   height (the stage less the grid's block padding: 96svh - 9.25rem, plus the
   0.5rem), with a matching bottom margin, so all three columns are the same
   height and land level at the end of the scroll. */
/* Landscape phones (wide and short) take the three-column triptych too:
   three short board rows in a 390px-high stage left every card a strip. */
@media (min-width: 1100px), (orientation: landscape) and (min-width: 640px) and (max-height: 560px) {
  .uk-gal-grid--albums { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .uk-gal-grid--albums .uk-gal-col { display: flex; gap: 0.75rem; }
  .uk-gal-grid--albums .uk-gal-card--lead { grid-column: auto; }
  .uk-gal-grid--albums .uk-gal-card { transform: none; transition: transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }
  .uk-gal-grid--albums .uk-gal-card:hover,
  .uk-gal-grid--albums .uk-gal-card:focus-visible { transform: translateY(-4px); }
  .uk-gal-grid--albums .uk-gal-col--2 {
    --uk-gal-lift: calc((96svh - 8.75rem) * -0.03);
    margin-top: calc(-0.5rem + var(--uk-gal-lift));
    margin-bottom: calc(var(--uk-gal-lift) * -1);
  }
  .uk-gal-grid--albums .uk-gal-card__title { font-size: calc(1.85rem * var(--k-display)); }
}
@media (min-width: 1100px) {
  .uk-gal-grid--albums { padding-inline: 7.5vw; }   /* desktop gutters as deployed */
}
@media (orientation: landscape) and (min-width: 640px) and (max-height: 560px) and (max-width: 1099px) {
  .uk-gal-grid--albums { gap: 0.5rem; padding-bottom: 0.75rem; }
  .uk-gal-grid--albums .uk-gal-col { gap: 0.5rem; }
  .uk-gal-grid--albums .uk-gal-col--2 { --uk-gal-lift: calc((100svh - 6.5rem) * -0.03); }
  .uk-gal-grid--albums .uk-gal-card__title { font-size: calc(1rem * var(--k-display)); }
  .uk-gal-grid--albums .uk-gal-card__meta { --uk-gal-fade: 1.25rem; padding: var(--uk-gal-fade) 0.75rem 0.5rem; }
}


/* ======================================================================
   2 - Project album system  (.pa-*)

   Image-first portfolio: gallery cards (/projects/) -> the album page
   (/projects/<slug>/: hero + one grid of every image) -> the fullscreen
   coverflow viewer (.pv, below). Markup is generated by
   tools/build_site.mjs from projects-albums.js.

   Palette is the site's own: walnut (--uk-umber / --uk-brown), cream
   (--uk-bone / --uk-sandstone), copper (--uk-ochre / --uk-clay). Mobile
   first; 768 and 1100 widen the grid.
   ====================================================================== */

/* ======================================================================
   2b - Album page hero  (level 2)

   An autoplaying album stage. projects.js (initHero) steps through every
   still of the album, one every 1.7s: the incoming slide dissolves in over the
   outgoing one with a short slide and a soft light flash, then breathes (a
   slow zoom) while it holds. Two ghost frames behind it carry the next two
   images, and the blurred ground behind everything follows the active one.
   Without JS, or with reduced motion, it is the album cover, still.
   ====================================================================== */

.pa-hero {
  --pa-hero-fade: 600ms;               /* keep in step with HERO.fade in projects.js */
  --pa-hero-cycle: 1700ms;             /* HERO.step: arrival to arrival */
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: 100svh;
  display: grid;
  align-content: start;
  background: var(--uk-umber);
  color: var(--text-on-dark);
  padding: 8.25rem 1rem 2.5rem;        /* top clears the stacked header lockup */
}
.pa-hero__bg {
  position: absolute;
  inset: -8%;
  z-index: -2;
}
.pa-hero__bgimg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(46px) saturate(1.2) brightness(.55);
  transform: scale(1.1);
  opacity: 0;
  transition: opacity .9s var(--ease-out);
}
.pa-hero__bgimg.is-on { opacity: 1; }
.pa-hero::before {                     /* walnut vignette over the blur */
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(80% 70% at 70% 35%, transparent 0%, rgba(40, 22, 18, .55) 100%),
    linear-gradient(180deg, rgba(40, 22, 18, .35), rgba(40, 22, 18, .1) 40%, rgba(40, 22, 18, .85) 100%);
}

/* ---- the stage: ghosts behind, the frame in front ----
   Mobile: in flow above the text, so a two-line title can never ride up over
   the picture. Desktop (900+) turns it into the right-hand column. */
.pa-hero__stage {
  --pa-dir: 1;
  position: relative;
  height: min(52svh, 32rem);
  /* Mobile pass 2026-10-01: at most a 5:4 landscape on phones, so the
     album's mostly landscape images fill the frame (cover) instead of
     sitting letterboxed in a tall box, and the title and button stay on
     the first screen; never below 12rem on a short phone. */
  height: clamp(12rem, min(52svh, (100vw - 2rem) * .82, 100svh - 21.75rem), 32rem);
  margin: .75rem 0 1.5rem;
  animation: pa-hero-in 1.2s var(--ease-out) both;
}
.pa-hero__frame {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: block;
  border-radius: 1.25rem;
  overflow: hidden;
  background: #1c100d;
  box-shadow: 0 50px 90px -40px rgba(0, 0, 0, .8), 0 0 0 1px rgba(239, 231, 218, .08);
}
.pa-hero__slide {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  overflow: hidden;
  opacity: 0;
  transform: translate3d(calc(var(--pa-dir) * 3.5%), 0, 0) scale(1.05);
  transition:
    opacity var(--pa-hero-fade) cubic-bezier(.33, 0, .2, 1),
    transform var(--pa-hero-fade) cubic-bezier(.2, .7, .2, 1);
}
.pa-hero__slide img {
  position: relative;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform-origin: 50% 55%;
}
.pa-hero__slide.is-active { z-index: 2; opacity: 1; transform: none; }
.pa-hero__slide.is-leaving {
  z-index: 1;
  opacity: 0;
  transform: translate3d(calc(var(--pa-dir) * -2.5%), 0, 0) scale(.985);
  /* hold opaque under the incoming slide, then go: a dissolve, never a dip to black */
  transition:
    opacity calc(var(--pa-hero-fade) * .5) ease calc(var(--pa-hero-fade) * .5),
    transform var(--pa-hero-fade) cubic-bezier(.2, .7, .2, 1);
}
/* the breathing zoom while an image holds */
.pa-hero.is-live .pa-hero__slide.is-active img { animation: pa-hero-breathe calc(var(--pa-hero-cycle) + var(--pa-hero-fade)) linear both; }
.pa-hero.is-paused .pa-hero__slide.is-active img { animation-play-state: paused; }
@keyframes pa-hero-breathe {
  from { transform: scale(1); }
  to   { transform: scale(1.055); }
}
/* an image whose shape is far from the frame's: shown whole, on its own blur */
.pa-hero__slide.is-contain::before {
  content: "";
  position: absolute;
  inset: -10%;
  background: var(--pa-slide-bg) center / cover no-repeat;
  filter: blur(28px) saturate(1.1) brightness(.5);
}
.pa-hero__slide.is-contain img { object-fit: contain; }

/* soft light flash across the frame at each step */
.pa-hero__frame::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  background: radial-gradient(120% 90% at 50% 40%, rgba(255, 244, 230, .9), rgba(255, 244, 230, 0) 70%);
  opacity: 0;
  mix-blend-mode: soft-light;
}
.pa-hero__stage.is-stepping .pa-hero__frame::after { animation: pa-hero-flash var(--pa-hero-fade) ease-out; }
@keyframes pa-hero-flash {
  0%   { opacity: 0; }
  35%  { opacity: .55; }
  100% { opacity: 0; }
}

/* ghost frames: the next two images, stacked behind like album leaves */
.pa-hero__ghost {
  position: absolute;
  inset: 0;
  display: block;
  border-radius: 1.2rem;
  overflow: hidden;
  background: #2a1814;
  box-shadow: 0 30px 60px -30px rgba(0, 0, 0, .7);
  transform: var(--g-t);
  transform-origin: 30% 100%;
}
.pa-hero__ghost img { width: 100%; height: 100%; object-fit: cover; filter: brightness(.62) saturate(.8); }
.pa-hero__ghost--1 { --g-t: translate3d(2.5%, -3.5%, 0) rotate(2deg) scale(.96); z-index: 2; opacity: .62; }
.pa-hero__ghost--2 { --g-t: translate3d(5%, -7%, 0) rotate(4deg) scale(.92); z-index: 1; opacity: .34; }
.pa-hero:not(.is-live) .pa-hero__ghost { display: none; }
.pa-hero__stage.is-stepping .pa-hero__ghost { animation: pa-hero-ghost var(--pa-hero-fade) cubic-bezier(.2, .7, .2, 1); }
@keyframes pa-hero-ghost {
  from { transform: var(--g-t) translate3d(3%, -2%, 0) rotate(1.5deg); opacity: 0; }
}

/* caption: which section the image is from, and where in the album */
.pa-hero__caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 5;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  padding: 2.5rem 1.1rem .95rem;
  background: linear-gradient(180deg, transparent, rgba(28, 16, 13, .72));
  pointer-events: none;
}
.pa-hero__sec {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .82rem;
  color: rgba(255, 255, 255, .88);
}
.pa-hero__tick {                       /* hairline timer under the frame, no dots */
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 6;
  height: 2px;
  background: var(--uk-ochre);
  transform: scaleX(0);
  transform-origin: 0 50%;
  opacity: .9;
}
.pa-hero:not(.is-live) .pa-hero__tick { display: none; }
.pa-hero__tick.is-running { animation: pa-hero-tick var(--pa-hero-cycle) linear forwards; }
.pa-hero.is-paused .pa-hero__tick { animation-play-state: paused; }
@keyframes pa-hero-tick { to { transform: scaleX(1); } }

@keyframes pa-hero-in {
  from { opacity: 0; transform: translateY(28px) scale(.97); }
}
.pa-hero__text {
  position: relative;
  display: grid;
  gap: .6rem;
  max-width: 40rem;
  animation: pa-hero-in 1s var(--ease-out) .15s both;
}
.pa-crumb {
  display: flex;
  gap: .5rem;
  font-size: .8rem;
  color: color-mix(in srgb, var(--text-on-dark) 70%, transparent);
}
.pa-crumb a { display: inline-block; padding-block: .7rem; margin-block: -.7rem; color: var(--text-on-dark); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: color-mix(in srgb, var(--text-on-dark) 40%, transparent); }
.pa-crumb a:hover { text-decoration-color: currentColor; }
.pa-hero__title {
  font-family: var(--font-display);
  font-size: calc(clamp(3.2rem, 10.5vw, 7.25rem) * var(--k-display));
  font-weight: 300;
  line-height: 1.04;
  letter-spacing: -.03em;
  color: #fff;
  text-wrap: balance;
}
/* The project type, as a quiet line under the name (round 6: no label
   above the heading; round 12: in the sans, upright). */
.pa-hero__label {
  font-family: var(--font-display);
  font-style: normal;
  font-size: clamp(1.2rem, 1.6vw, 1.45rem);
  line-height: 1.2;
  color: color-mix(in srgb, var(--text-on-dark) 72%, transparent);
}
.pa-hero__actions { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: .9rem; }

@media (min-width: 900px), (orientation: landscape) and (min-width: 640px) and (max-height: 560px) {
  .pa-hero {
    grid-template-columns: minmax(0, 4.5fr) minmax(0, 7.5fr);
    align-content: center;
    padding: 8rem clamp(2rem, 5vw, 5rem) 5rem;
    gap: clamp(2rem, 4vw, 4.5rem);
  }
  .pa-hero__text { grid-column: 1; grid-row: 1; align-self: end; padding-bottom: 2rem; }
  .pa-hero__stage {
    grid-column: 2;
    grid-row: 1;
    margin: 0;
    height: min(74svh, 48rem);
  }
  .pa-hero__caption { padding: 3rem 1.5rem 1.25rem; }
  .pa-hero__sec { font-size: .9rem; }
}
/* Landscape phones: the desktop two-column hero, fitted to a short screen
   (stage under the header lockup, title sized to the column). */
@media (orientation: landscape) and (min-width: 640px) and (max-height: 560px) and (max-width: 899px) {
  .pa-hero { padding: 7rem 1.5rem 1.25rem; gap: 1.5rem; }
  .pa-hero__stage { height: calc(100svh - 8.5rem); min-height: 12rem; }
  .pa-hero__text { padding-bottom: 0; gap: .45rem; }
  .pa-hero__title { font-size: calc(clamp(2rem, 11svh, 3rem) * var(--k-display)); }
  .pa-hero__actions { margin-top: .5rem; }
  .pa-hero__caption { padding: 2rem 1rem .8rem; }
}


/* ======================================================================
   2c - Album images  (level 2)

   Every image in the album, in one grid under one heading, "Project
   gallery": no sections, no captions (client feedback, 2026-10-01). Uniform 4:3 crops so mixed portrait and landscape
   frames sit in tidy rows; the viewer shows each one whole. Films are poster
   tiles with a play mark. 1 column, 2 from 768px, 3 from 1100px.
   ====================================================================== */

.pa-photos {
  background: var(--uk-bone);
  padding-block: 2.5rem 3rem;
}
/* The site's section-heading voice (light display weight, tight tracking,
   umber on bone), held a step below --fs-h2 so the images stay the subject.
   It shares the grid's measure, so its left edge is the first tile's. */
.pa-photos__title {
  width: min(100% - 2rem, 96rem);
  max-width: none;
  margin: 0 auto 1.25rem;
  font-family: var(--font-display);
  font-size: calc(clamp(2.4rem, 4.2vw, 3.75rem) * var(--k-display));
  font-weight: 300;
  line-height: 1.05;
  letter-spacing: -.015em;
  color: var(--text-primary);
  text-wrap: balance;
}
.pa-photos__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: .75rem;
  width: min(100% - 2rem, 96rem);
  margin: 0 auto;
  padding: 0;
  list-style: none;
}
.pa-photos__item { min-width: 0; }
.pa-tile {
  position: relative;
  display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: .75rem;
  background: var(--uk-sandstone);
  box-shadow: 0 10px 24px -16px rgba(63, 36, 34, .45);
}
.pa-tile img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .9s var(--ease-out), filter .4s var(--ease-out);
}
.pa-tile:hover img,
.pa-tile:focus-visible img { transform: scale(1.04); }
.pa-tile:focus-visible { outline: 2px solid var(--uk-ochre); outline-offset: 3px; }
.pa-tile__play {
  position: absolute;
  left: .75rem;
  bottom: .75rem;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: rgba(40, 22, 18, .72);
  color: #fff;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .35) inset;
}
.pa-tile__play svg { margin-left: 2px; }

@media (min-width: 768px) {
  .pa-photos { padding-block: 3.5rem 4.5rem; }
  .pa-photos__title { width: min(100% - 4rem, 96rem); margin-bottom: 1.75rem; }
  .pa-photos__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;
    width: min(100% - 4rem, 96rem);
  }
}
@media (min-width: 1100px) {
  .pa-photos { padding-block: 4.5rem 6rem; }
  .pa-photos__title { width: min(100% - clamp(4rem, 8vw, 10rem), 96rem); margin-bottom: 2rem; }
  .pa-photos__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 1.25rem;
    width: min(100% - clamp(4rem, 8vw, 10rem), 96rem);
  }
}

/* ---- buttons (album-local; the site's .btn is tuned for cream grounds) ---- */
.pa-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.9rem;
  padding: .7rem 1.4rem;
  border-radius: 999px;
  font-family: var(--font-body);
  font-size: .92rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .25s ease, color .25s ease, border-color .25s ease, transform .2s var(--ease-out);
}
.pa-btn:active { transform: scale(.97); }
.pa-btn:focus-visible { outline: 2px solid var(--uk-ochre); outline-offset: 3px; }
.pa-btn--solid { background: var(--uk-ochre); color: #fff; border: 1px solid var(--uk-ochre); }
.pa-btn--solid:hover { background: color-mix(in srgb, var(--uk-ochre) 85%, var(--uk-brown)); }


/* ======================================================================
   3 - Fullscreen coverflow viewer  (.pv, level 3)

   Vanilla port of 21st.dev/@educalvolpz/components/coverflow-carousel:
   each frame's transform is set per offset from the active frame by
   projects.js (rotateY, translateX, translateZ, scale; ±3 visible). This
   file owns the stage, the walnut ground, the blurred active-image
   backdrop and the chrome.
   ====================================================================== */

.pv {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  background: #1c100d;
  color: var(--text-on-dark);
  opacity: 0;
  visibility: hidden;
  transition: opacity .35s var(--ease-out), visibility 0s linear .35s;
  overscroll-behavior: contain;
  touch-action: none;
}
.pv[hidden] { display: none; }
.pv.is-open { opacity: 1; visibility: visible; transition: opacity .35s var(--ease-out); }
html.pv-lock, html.pv-lock body { overflow: hidden; }

.pv__backdrop { position: absolute; inset: -10%; z-index: 0; overflow: hidden; }
.pv__bd {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(60px) saturate(1.25) brightness(.5);
  transform: scale(1.15);
  opacity: 0;
  transition: opacity .9s var(--ease-out);
}
.pv__bd.is-on { opacity: 1; }
.pv__veil {
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(70% 60% at 50% 48%, rgba(28, 16, 13, 0) 0%, rgba(28, 16, 13, .55) 70%, rgba(28, 16, 13, .9) 100%),
    linear-gradient(180deg, rgba(28, 16, 13, .7) 0%, transparent 18%, transparent 80%, rgba(28, 16, 13, .8) 100%);
}

.pv__top,
.pv__bottom { position: relative; z-index: 3; }

.pv__top {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 1rem;
  padding: max(.9rem, env(safe-area-inset-top)) 1rem .5rem;
}
.pv__back {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  min-height: 2.75rem;
  padding: .55rem 1rem .55rem .8rem;
  border-radius: 999px;
  border: 1px solid rgba(239, 231, 218, .28);
  background: rgba(28, 16, 13, .35);
  -webkit-backdrop-filter: blur(10px);
          backdrop-filter: blur(10px);
  color: var(--text-on-dark);
  font-size: .88rem;
  font-weight: 600;
  transition: border-color .25s ease, background-color .25s ease;
}
.pv__back:hover { border-color: rgba(239, 231, 218, .7); background: rgba(28, 16, 13, .55); }
.pv__heading { min-width: 0; text-align: center; }
.pv__album {
  font-family: var(--font-mono);
  font-size: 0.858rem;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--ochre-on-dark);
}
.pv__title {
  margin-top: .15rem;
  font-family: var(--font-display);
  font-size: clamp(1rem, 2.4vw, 1.35rem);
  font-weight: 400;
  color: #fff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pv__count {
  font-variant-numeric: tabular-nums;
  font-size: .9rem;
  color: color-mix(in srgb, var(--text-on-dark) 70%, transparent);
  min-width: 4.5rem;
  text-align: right;
}
.pv__n { color: #fff; font-size: 1.15rem; margin-right: .3rem; }

/* ---- stage ---- */
.pv__stage {
  position: relative;
  z-index: 2;
  perspective: 1400px;
  overflow: hidden;
  cursor: grab;
  outline: none;
}
.pv__stage.is-dragging { cursor: grabbing; }
.pv__track {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  /* The track is a flat plane at z=0; the side frames sit behind it in Z, so
     it would win every hit test. Only the frames take the pointer. */
  pointer-events: none;
}
.pv__item {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--w, 60vw);
  height: var(--h, 60vh);
  margin: 0;
  border-radius: .9rem;
  overflow: hidden;
  background: rgba(0, 0, 0, .25);
  box-shadow: 0 40px 80px -30px rgba(0, 0, 0, .85), 0 0 0 1px rgba(239, 231, 218, .08);
  transform-style: preserve-3d;
  will-change: transform, opacity;
  transition:
    transform .55s cubic-bezier(.22, 1.08, .36, 1),
    opacity .45s var(--ease-out),
    filter .45s var(--ease-out);
  cursor: pointer;
  pointer-events: auto;
  user-select: none;
  -webkit-user-drag: none;
}
.pv__item.is-active { cursor: default; }
.pv__item:not(.is-active) { filter: brightness(.62) saturate(.85); }
.pv__item img,
.pv__item video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  -webkit-user-drag: none;
}
.pv__stage.is-dragging .pv__item { transition: none; }
/* pinch / double-tap zoom inside the active frame (projects.js) */
.pv__item img,
.pv__item video { transform-origin: 50% 50%; transition: transform .28s var(--ease-out); }
.pv__stage.is-pinching .pv__item img,
.pv__stage.is-pinching .pv__item video { transition: none; }
.pv.is-zoomed .pv__stage { cursor: move; }
.pv__item[data-type="video"]::after {      /* a quiet film marker on side frames */
  content: "Film";
  position: absolute;
  top: .7rem;
  left: .7rem;
  padding: .2rem .55rem;
  border-radius: 999px;
  background: rgba(28, 16, 13, .6);
  font-size: 0.884rem;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--text-on-dark);
}
.pv__item.is-active[data-type="video"]::after { display: none; }

/* ---- bottom chrome ---- */
.pv__bottom {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 1rem;
  padding: .5rem 1rem max(1rem, env(safe-area-inset-bottom));
}
.pv__tag {
  justify-self: start;
  font-family: var(--font-mono);
  font-size: 0.884rem;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--ochre-on-dark);
  min-height: 1em;
}
.pv__hint {
  justify-self: end;
  font-size: .75rem;
  color: color-mix(in srgb, var(--text-on-dark) 55%, transparent);
}
.pv__controls { display: flex; align-items: center; gap: .9rem; }
.pv__nav {
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  border: 1px solid rgba(239, 231, 218, .3);
  background: rgba(28, 16, 13, .35);
  -webkit-backdrop-filter: blur(10px);
          backdrop-filter: blur(10px);
  color: #fff;
  transition: border-color .25s ease, background-color .25s ease, opacity .25s ease, transform .2s var(--ease-out);
}
.pv__nav:hover:not(:disabled) { border-color: var(--uk-ochre); background: color-mix(in srgb, var(--uk-ochre) 30%, transparent); }
.pv__nav:active:not(:disabled) { transform: scale(.94); }
.pv__nav:disabled { opacity: .3; cursor: default; }
.pv__back:focus-visible,
.pv__nav:focus-visible { outline: 2px solid var(--uk-ochre); outline-offset: 3px; }
/* The stage is the full-bleed middle band, so a ring round it would draw two
   rules across the screen. Focus there is shown on the active frame instead. */
.pv__stage:focus-visible { outline: none; }
.pv__stage:focus-visible .pv__item.is-active { box-shadow: 0 0 0 2px var(--uk-ochre), 0 40px 80px -30px rgba(0, 0, 0, .85); }
.pv__progress {
  position: relative;
  width: clamp(5rem, 16vw, 11rem);
  height: 2px;
  border-radius: 2px;
  background: rgba(239, 231, 218, .18);
  overflow: hidden;
}
.pv__bar {
  position: absolute;
  inset: 0 auto 0 0;
  width: 100%;
  background: var(--uk-ochre);
  transform-origin: 0 50%;
  transform: scaleX(var(--p, 0));
  transition: transform .45s var(--ease-out);
}
.pv__live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

@media (max-width: 767px) {
  .pv__top { grid-template-columns: auto 1fr; }
  .pv__heading { text-align: right; }
  .pv__count { display: none; }
  .pv__back span { display: none; }
  .pv__back { padding: .55rem .8rem; }
  .pv__bottom { grid-template-columns: 1fr; justify-items: center; gap: .6rem; }
  .pv__tag { justify-self: center; }
  .pv__hint { display: none; }
  .pv__controls::after {                 /* the counter moves down beside the controls */
    content: attr(data-count);
    order: 1;
    font-variant-numeric: tabular-nums;
    font-size: .85rem;
    color: color-mix(in srgb, var(--text-on-dark) 75%, transparent);
    min-width: 3.5rem;
    text-align: center;
  }
  .pv__progress { display: none; }
  .pv__nav--next { order: 2; }
}
/* Landscape phones: slimmer chrome so the frames get the height */
@media (orientation: landscape) and (max-height: 560px) {
  .pv__top { padding-top: max(.4rem, env(safe-area-inset-top)); padding-bottom: .2rem; padding-inline: max(1rem, env(safe-area-inset-left)) max(1rem, env(safe-area-inset-right)); }
  .pv__bottom { padding-top: .2rem; padding-bottom: max(.4rem, env(safe-area-inset-bottom)); padding-inline: max(1rem, env(safe-area-inset-left)) max(1rem, env(safe-area-inset-right)); }
  .pv__nav { width: 2.75rem; height: 2.75rem; }
}


/* ======================================================================
   Reduced motion: no 3D, no tile zoom, no marquee drift. The viewer
   shows only the active frame (the reference does the same) and swaps it.
   ====================================================================== */
@media (prefers-reduced-motion: reduce) {
  .uk-hero3__track { animation: none; }
  .uk-hero3__tagline,
  .uk-hero3__title span,
  .pa-hero__stage,
  .pa-hero__text { animation: none; }
  .pa-hero__slide,
  .pa-hero__bgimg { transition: none !important; }
  .pa-hero__slide img,
  .pa-hero__ghost,
  .pa-hero__frame::after,
  .pa-hero__tick { animation: none !important; }
  .pa-tile img,
  .pv, .pv__bd, .pv__item, .pv__bar, .pv__item img, .pv__item video { transition: none !important; }
  .pa-tile:hover img,
  .pa-tile:focus-visible img { transform: none; }

  .uk-gal-scroll { height: auto; }
  /* still one screen tall, so the album board keeps its final composition */
  .uk-gal-sticky { position: static; }
  .uk-gal-grid   { transform: none; }
  .uk-gal-col--1,
  .uk-gal-col--2,
  .uk-gal-col--3 { transform: none; }
  .uk-gal-col--2,
  .uk-gal-grid--albums .uk-gal-col--2 { margin-top: -0.5rem; margin-bottom: 0; }
}


/* ======================================================================
   Project journey (client, 2026-10-02) — replaces the uniform 4:3 grid.
   Justified rows of two or three: each tile's flex share and box follow its
   own aspect (--ar, set by the build), so the tiles in a row share one
   height and no picture is cropped; portrait and landscape frames mix
   without gaps. The Linear House and The Vertical House have two chapters, "Design" then "On site". Phones
   stack the tiles one per row at their natural shape.
   ====================================================================== */
.pa-journey {
  display: grid;
  /* minmax(0, 1fr), not the default auto track: an auto track grows to the
     images' intrinsic widths, which pushed rows past the screen edge. */
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(2.5rem, 5vw, 4.5rem);
  width: min(100% - 2rem, 96rem);
  margin: 0 auto;
}
.pa-chapter { display: grid; grid-template-columns: minmax(0, 1fr); gap: .75rem; }
/* Chapter heading (round 6): "Design" / "On site", no rule, no caps
   (round 12: in the sans, upright). Renders and site photographs stay
   clearly separate. */
.pa-chapter__title {
  margin: 0 0 .35rem;
  font-family: var(--font-display);
  font-style: normal;
  font-size: calc(clamp(1.6rem, 2.3vw, 2.1rem) * var(--k-display));
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -.005em;
  color: var(--clay-on-light, #7a5136);
}
.pa-row { display: flex; flex-direction: column; gap: .75rem; }
.pa-journey .pa-tile { aspect-ratio: var(--ar); width: 100%; }
@media (min-width: 600px) {
  /* --sum (from the build) is the row's combined aspect: capping the row's
     width at --sum x the row height cap keeps a row of portraits from
     growing taller than the screen; such a row sits centred. */
  .pa-row {
    --row-max: min(34rem, 72svh);
    flex-direction: row;
    width: min(100%, calc(var(--sum) * var(--row-max)));
    margin-inline: auto;
  }
  .pa-journey .pa-tile { flex: var(--ar) 1 0; width: auto; min-width: 0; }
}
@media (min-width: 768px) {
  .pa-journey { width: min(100% - 4rem, 96rem); }
  .pa-chapter, .pa-row { gap: 1rem; }
}
@media (min-width: 1100px) {
  .pa-journey { width: min(100% - clamp(4rem, 8vw, 10rem), 96rem); }
  .pa-chapter, .pa-row { gap: 1.25rem; }
}

/* Saving: no drag-out, no long-press save sheet, no selection highlight on
   album images (projects.js also blocks the context menu there). */
.pa-album-page .pa-tile,
.pa-album-page .pa-tile img,
.pa-album-page .pa-hero img,
.pa-album-page .pv img,
.pa-album-page .pv video {
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
  user-select: none;
}
