/* ============================================================
   HUÂNREAL — huanreal.studio
   Light architectural portfolio. No framework, no build step.
   ============================================================ */

:root {
  /* Light architectural palette — paper bg, ink fg, terracotta accent.
     Warm rust/clay tone sits naturally against the warm cream bg and reads
     as architectural / aged-paper character. */
  --bg:           #f6f4ee;
  --bg-alt:       #efebe2;
  --bg-card:      #ffffff;
  --border:       #e3dfd4;
  --fg:           #0c0e12;
  --fg-muted:     #6b6862;
  --fg-soft:      #2a2d33;
  --accent:       #b5532d;
  --accent-soft:  #c46c45;

  --max:        1100px;
  --pad:        clamp(1.25rem, 4vw, 2rem);

  --font-sans:  ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono:  ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", monospace;

  --ease:       cubic-bezier(.2,.7,.2,1);
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: clamp(16px, 1.05vw + 12px, 18px);
  line-height: 1.55;
  color: var(--fg);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { display: block; max-width: 100%; }

a {
  color: var(--fg);
  text-decoration: none;
  transition: color .2s var(--ease);
}
a:hover { color: var(--accent); }

.container {
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 var(--pad);
}

/* ---------- Header / nav ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  backdrop-filter: blur(10px);
  background: rgba(246,244,238,.82);
  border-bottom: 1px solid var(--border);
}

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 1rem;
  padding-bottom: 1rem;
}

.brand {
  font-weight: 700;
  font-size: .95rem;
  letter-spacing: .14em;
}

nav[aria-label="Primary"] a {
  margin-left: 1.75rem;
  font-size: .9rem;
  color: var(--fg-muted);
  letter-spacing: .04em;
}
nav[aria-label="Primary"] a:hover { color: var(--fg); }

/* Primary nav stays hidden while the hero's "On this page" rail is in
   view (it already offers the same links), then slides in once scrolled
   past it. */
.primary-nav {
  transition: opacity .4s var(--ease), transform .4s var(--ease);
}
.primary-nav[data-nav-hidden] {
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .primary-nav { transition: none; }
}

/* On narrow screens the brand + four nav links are wider than the viewport,
   so the links wrap onto their own row under the brand — every link stays
   visible. The taller header then auto-hides on scroll-down and reappears on
   scroll-up (see .site-header--hidden + site-header.js). */
@media (max-width: 640px) {
  .nav {
    flex-wrap: wrap;
    gap: .5rem 1.25rem;
  }
  .primary-nav {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem 1.25rem;
  }
  nav[aria-label="Primary"] a { margin-left: 0; }
}

/* Auto-hiding header: slide the whole bar out of view when scrolling down,
   back in when scrolling up. */
.site-header {
  transition: transform .3s var(--ease);
}
.site-header--hidden {
  transform: translateY(-100%);
}
@media (prefers-reduced-motion: reduce) {
  .site-header { transition: none; }
}

/* ---------- Hero ---------- */
.hero {
  padding: clamp(4rem, 12vh, 8rem) 0 clamp(3rem, 8vh, 5rem);
  border-bottom: 1px solid var(--border);
  background:
    radial-gradient(900px 380px at 95% 0%, rgba(181,83,45,.06), transparent 60%),
    radial-gradient(700px 320px at 0% 60%, rgba(181,83,45,.035), transparent 60%);
}

.hero-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 3rem;
  align-items: start;
}
@media (min-width: 900px) {
  .hero-grid {
    grid-template-columns: minmax(0, 1fr) minmax(240px, 300px);
    gap: 4rem;
  }
}

.hero-main { min-width: 0; }

.eyebrow {
  font-family: var(--font-mono);
  font-size: .8rem;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: .2em;
  margin: 0 0 1.5rem;
}

.hero h1 {
  /* Be Vietnam Pro is designed specifically for Vietnamese — diacritics
     (â, ê, ư, ơ, etc.) are positioned and spaced properly. Falls back to
     the system sans stack if the font hasn't loaded yet. */
  font-family: "Be Vietnam Pro", var(--font-sans);
  margin: 0 0 1.5rem;
  font-size: clamp(2.75rem, 8vw, 5.75rem);
  line-height: 1.02;
  letter-spacing: -.02em;
  font-weight: 700;
}

.lede {
  font-size: clamp(1.1rem, 1.6vw, 1.35rem);
  color: var(--fg-soft);
  max-width: 60ch;
  margin: 0 0 1.25rem;
  text-wrap: balance;
}
.lede .cat { white-space: nowrap; }
.lede .sep { color: var(--accent); margin: 0 .35rem; }

.hero-blurb {
  max-width: 60ch;
  color: var(--fg-muted);
  margin: 0 0 2.25rem;
}

.hero-cta {
  display: flex;
  gap: .75rem;
  flex-wrap: wrap;
}

/* Hero aside: "Currently working with" panel.
   Fills the right column so the hero composition reads complete
   without needing a background image. */
.hero-aside {
  border-top: 1px solid var(--border);
  padding-top: 1.5rem;
}
@media (min-width: 900px) {
  .hero-aside {
    border-top: 0;
    border-left: 1px solid var(--border);
    padding: .25rem 0 .25rem 2rem;
  }
}

/* Stacked fact blocks fill the rail; subtle rules separate them so the
   column reads as a deliberate index rather than one stray entry. */
.now-block + .now-block {
  margin-top: 1.75rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--border);
}

/* Rail navigation: jump-links to page sections and case studies. Uses the
   site's standard accent-dash list marker (see .competencies / .now-sub),
   with an underlined link so it still reads as navigable. */
.rail-links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: .4rem;
}
.rail-links li {
  font-size: .95rem;
  padding-left: 1.1rem;
  position: relative;
}
.rail-links li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .7em;
  width: .5rem;
  height: 1px;
  background: var(--accent);
}
.rail-links a {
  color: var(--fg-soft);
  border-bottom: 1px solid var(--border);
  padding-bottom: 1px;
  transition: color .2s var(--ease), border-color .2s var(--ease);
}
.rail-links a:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.aside-label {
  font-family: var(--font-mono);
  font-size: .72rem;
  color: var(--accent);
  letter-spacing: .22em;
  text-transform: uppercase;
  margin: 0 0 1.25rem;
}

.now-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 1.1rem;
  counter-reset: now;
}
.now-list > li {
  counter-increment: now;
  font-size: .98rem;
  line-height: 1.45;
  padding-left: 2.25rem;
  position: relative;
}
.now-list > li::before {
  content: counter(now, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: .2em;
  font-family: var(--font-mono);
  font-size: .7rem;
  color: var(--fg-muted);
  letter-spacing: .12em;
}
/* Single client: drop the index number and its gutter. */
.now-list > li:only-child {
  padding-left: 0;
}
.now-list > li:only-child::before {
  content: none;
}
.now-list a {
  color: var(--fg);
  border-bottom: 1px solid var(--border);
  padding-bottom: 1px;
  transition: color .2s var(--ease), border-color .2s var(--ease);
}
.now-list a:hover { color: var(--accent); border-bottom-color: var(--accent); }
.now-sep { color: var(--fg-muted); margin: 0 .15rem; }

.now-sub {
  list-style: none;
  padding: .7rem 0 0;
  margin: 0;
  display: grid;
  gap: .4rem;
}
.now-sub li {
  font-size: .86rem;
  color: var(--fg-muted);
  padding-left: 1rem;
  position: relative;
}
.now-sub li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .7em;
  width: .5rem;
  height: 1px;
  background: var(--accent);
}
.now-sub a {
  color: var(--fg-muted);
  border-bottom: none;
}
.now-sub a:hover { color: var(--accent); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-block;
  padding: .8rem 1.25rem;
  border-radius: 999px;
  font-size: .9rem;
  font-weight: 500;
  letter-spacing: .04em;
  border: 1px solid transparent;
  transition: transform .2s var(--ease), background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease);
}
.btn:hover { transform: translateY(-1px); }

.btn-primary {
  background: var(--fg);
  color: var(--bg);
  font-weight: 700;
}
.btn-primary:hover { background: var(--accent); color: #ffffff; }

.btn-ghost {
  border-color: var(--fg);
  color: var(--fg);
}
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }

/* ---------- Sections ---------- */
.section {
  padding: clamp(4rem, 10vh, 7rem) 0;
  border-bottom: 1px solid var(--border);
}
.section-alt { background: var(--bg-alt); }

/* Alternate the content bands automatically, counting the hero as the first
   (light) band — so the first content section flips to the alt tone instead of
   sitting light-on-light beneath the hero. nth-of-type counts every <section>
   sibling, so the hero is #1 and content sections start at #2 (even → alt).
   This drives the rhythm on its own; the hand-placed .section-alt classes are
   no longer needed. */
main > .section:nth-of-type(even) { background: var(--bg-alt); }
main > .section:nth-of-type(odd)  { background: var(--bg); }

.section-label {
  font-family: var(--font-mono);
  font-size: .75rem;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: .22em;
  margin: 0 0 1.25rem;
}
.section-label a {
  color: inherit;
  border-bottom: 1px solid currentColor;
  padding-bottom: 1px;
  transition: opacity .2s var(--ease);
}
.section-label a:hover { opacity: .7; }

.section-heading,
.two-col h2 {
  font-size: clamp(1.6rem, 3.2vw, 2.4rem);
  line-height: 1.15;
  letter-spacing: -.015em;
  margin: 0 0 2.5rem;
  max-width: 22ch;
  font-weight: 600;
  text-wrap: balance;
}

/* About: two-column */
.two-col {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
}
.two-col h2 { margin-bottom: 0; }
@media (min-width: 820px) {
  .two-col { grid-template-columns: 1fr 1.4fr; gap: 4rem; }
}
.prose p {
  margin: 0 0 1.1rem;
  color: var(--fg-soft);
}

.about-cta {
  margin-top: 2rem;
}

.competencies {
  list-style: none;
  padding: 0;
  margin: 2rem 0 0;
  display: grid;
  gap: .85rem;
}
.competencies li {
  color: var(--fg-muted);
  padding-left: 1.1rem;
  position: relative;
}
.competencies li::before {
  content: "";
  position: absolute;
  left: 0; top: .65em;
  width: .4rem; height: 1px;
  background: var(--accent);
}
.competencies strong { color: var(--fg); font-weight: 600; }

/* ---------- Projects ---------- */
/* Selected work is split into thematic groups. The group wrapper owns the
   project counter so the corner numbers run continuously (01, 02, …) across
   every category instead of restarting in each group. */
.project-groups {
  counter-reset: project;
  display: grid;
  gap: clamp(2.5rem, 5vw, 3.5rem);
}
.project-group-title {
  font-family: var(--font-mono);
  font-size: .8rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .18em;
  color: var(--fg);
  margin: 0 0 1.25rem;
  padding-bottom: .65rem;
  border-bottom: 1px solid var(--border);
}

/* Tabbed variant — progressively enhanced by JS (see index.html). With no
   JS the three groups stack with their h3 titles (the fallback); with JS a
   horizontal tablist replaces the titles and reveals one group at a time. */
.project-groups.tabs-active { gap: 1.75rem; }
.project-groups.tabs-active .project-group-title { display: none; }

.work-tablist {
  display: flex;
  gap: clamp(1.25rem, 3vw, 2rem);
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.work-tablist::-webkit-scrollbar { display: none; }

.work-tab {
  flex: 0 0 auto;
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin: 0 0 -1px;
  padding: 0 0 .75rem;
  font-family: var(--font-mono);
  font-size: .8rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .18em;
  color: var(--fg-muted);
  white-space: nowrap;
  cursor: pointer;
  transition: color .2s var(--ease), border-color .2s var(--ease);
}
.work-tab:hover { color: var(--fg); }
.work-tab[aria-selected="true"] { color: var(--accent); }
/* Middot separator between categories (a non-interactive flex item). */
.work-tab-sep {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: .8rem;
  color: var(--fg-muted);
  user-select: none;
  pointer-events: none;
}
.work-tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}

/* Wrapper gives the mobile indicator a positioning context and the drag-scroll
   cursor state. min-width:0 lets this grid item shrink to the column width so
   the scrolling tablist inside scrolls instead of widening the whole grid. */
.work-tabs { position: relative; min-width: 0; }
.work-tablist.is-dragging { cursor: grabbing; user-select: none; }

/* Desktop uses the per-tab underline (above); the sliding segment indicator
   is mobile-only. */
.work-tab-indicator { display: none; }

/* Desktop: the active tab carries an accent underline; on mobile that role is
   handed to the distributed segment indicator below (so there's no two-rule
   tie over the same property between the modes). */
@media (min-width: 641px) {
  .work-tab[aria-selected="true"] { border-bottom-color: var(--accent); }
}

@media (max-width: 640px) {
  /* Swipe CTA, right-aligned just above the bar. Absolutely positioned so it
     floats inside the section heading's existing bottom-margin gap rather than
     pushing the bar down — keeping the heading→bar distance unchanged. The
     1.25rem offset matches the testimonials hint's gap to its content. Out of
     the section label it no longer inherits the mono label type, so that's
     restored here (color/transition come from the base .swipe-hint). */
  .work-tabs .swipe-hint {
    position: absolute;
    right: 0;
    bottom: calc(100% + 1.25rem);
    margin: 0;
    font-family: var(--font-mono);
    font-size: .72rem;
    line-height: 1;
    text-transform: uppercase;
    letter-spacing: .2em;
  }
  /* Mobile: the per-tab underline is gone (the base border stays transparent);
     one indicator split into equal segments (one per category) slides to the
     active one — a position cue that stays put while the bar itself scrolls. */
  .work-tab-indicator {
    display: block;
    position: absolute;
    bottom: 0;
    left: calc(var(--active-index) * 100% / var(--tab-count));
    height: 2px;
    width: calc(100% / var(--tab-count));
    background: var(--accent);
    transition: left .25s var(--ease);
  }
}

.projects {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 1.25rem;
}

.project {
  counter-increment: project;
  position: relative;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 1.75rem clamp(1.25rem, 3vw, 2rem);
  transition: border-color .25s var(--ease), transform .25s var(--ease), box-shadow .25s var(--ease);
}
.project::before {
  content: counter(project, decimal-leading-zero);
  position: absolute;
  top: 1.25rem;
  right: 1.5rem;
  font-family: var(--font-mono);
  font-size: .75rem;
  color: var(--fg-muted);
  letter-spacing: .12em;
}
/* Hover lift only on cards that actually navigate — see linked variant
   below. Text-only project cards stay flat so the affordance doesn't lie. */
.project--linked:hover {
  border-color: rgba(181,83,45,.5);
  transform: translateY(-2px);
  box-shadow: 0 12px 28px -14px rgba(12,14,18,.12);
}

.project-meta {
  font-family: var(--font-mono);
  font-size: .75rem;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: .15em;
  margin-bottom: 1rem;
}
.project-year { color: var(--accent); }
.project-client::before { content: " · "; }
/* On narrow viewports the long client names crowd the corner number;
   drop the client onto its own line below the year. */
@media (max-width: 700px) {
  .project-client { display: block; }
  .project-client::before { content: ""; }
}

.project h4 {
  margin: 0 0 .85rem;
  font-size: clamp(1.15rem, 1.7vw, 1.4rem);
  line-height: 1.25;
  letter-spacing: -.01em;
  font-weight: 600;
}

.project p {
  margin: 0 0 1.25rem;
  color: var(--fg-soft);
  max-width: 68ch;
}

.tags {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
}
.tags li {
  font-family: var(--font-mono);
  font-size: .72rem;
  color: var(--fg-muted);
  padding: .25rem .55rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  letter-spacing: .05em;
}

/* ---------- Public work (open source & samples) ---------- */
.public-grid {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}
@media (min-width: 640px)  { .public-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .public-grid { grid-template-columns: repeat(3, 1fr); } }

.public-card { margin: 0; }
.public-card > a {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 1.4rem 1.5rem;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  transition: border-color .25s var(--ease), transform .25s var(--ease), box-shadow .25s var(--ease);
}
.public-card > a:hover {
  border-color: rgba(181,83,45,.5);
  transform: translateY(-2px);
  box-shadow: 0 12px 28px -14px rgba(12,14,18,.12);
}

.public-badge {
  font-family: var(--font-mono);
  font-size: .68rem;
  color: var(--accent);
  letter-spacing: .18em;
  text-transform: uppercase;
  margin-bottom: .85rem;
}

.public-card h3 {
  margin: 0 0 .5rem;
  font-size: 1rem;
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: -.005em;
  color: var(--fg);
}
.public-card > a:hover h3 { color: var(--accent); }

.public-card p {
  margin: 0 0 1rem;
  color: var(--fg-muted);
  font-size: .88rem;
  line-height: 1.5;
  flex: 1;
}
.public-card p code {
  font-family: var(--font-mono);
  font-size: .85em;
  padding: 0 .25em;
  background: rgba(181,83,45,.08);
  color: var(--fg-soft);
  border-radius: 3px;
}

.public-tag {
  font-family: var(--font-mono);
  font-size: .68rem;
  color: var(--fg-muted);
  padding: .2rem .55rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  letter-spacing: .06em;
  align-self: flex-start;
}

.public-footer {
  margin: 2.5rem 0 0;
  font-family: var(--font-mono);
  font-size: .85rem;
  color: var(--fg-muted);
  letter-spacing: .04em;
}
.public-footer a {
  color: var(--fg-soft);
  border-bottom: 1px solid var(--border);
  padding-bottom: .15rem;
}
.public-footer a:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* ---------- Quotes ---------- */
/* Default layout — simple grid. Case-study pages use this for showing
   a small set of testimonials in a static side-by-side block. Items
   align to start so cards keep their natural height instead of being
   stretched to match the tallest sibling. */
.quotes {
  display: grid;
  gap: 1.5rem;
  align-items: start;
}
.quotes--two-col { grid-template-columns: 1fr; }
@media (min-width: 900px) {
  .quotes--two-col { grid-template-columns: 1fr 1fr; }
}

.quote {
  margin: 0;
  padding: 1.75rem;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--bg-card);
}

/* Homepage testimonials carousel — overrides the default grid with a
   full-bleed horizontal scroller and column-wrap so consecutive short
   cards stack inside one visual column, bounded by the tallest single
   card's height (set inline by JS once cards have rendered). The row
   breaks out of the container's gutters via 100vw-based negative
   margins, with matching padding so first/last columns still line up
   with the section heading / right margin at the scroll extremes.
   `overflow-x: clip` on the section swallows the ~17px scrollbar-width
   overshoot of 100vw so the page itself doesn't scroll sideways; `clip`
   over `hidden` so we don't establish a scroll container that would
   break sticky positioning elsewhere. */
#testimonials { overflow-x: clip; }
#testimonials .quotes {
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  align-items: flex-start;
  align-content: flex-start;
  gap: 1.5rem;
  max-height: 32rem;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 1rem;
  -webkit-overflow-scrolling: touch;
  cursor: grab;
  scrollbar-width: none;
  -ms-overflow-style: none;
  margin-left: calc((100vw - 100%) / -2);
  margin-right: calc((100vw - 100%) / -2);
  padding-left: calc((100vw - 100%) / 2);
  padding-right: calc((100vw - 100%) / 2);
}
#testimonials .quotes::-webkit-scrollbar { display: none; }
#testimonials .quotes.is-dragging {
  cursor: grabbing;
  user-select: none;
}
/* Homepage testimonial cards carry a subtly accent-tinted border — a warm
   tint of the accent blended into the regular border color — so they read as
   a touch more prominent than the plain-bordered cards used elsewhere
   (e.g. case-study testimonials). */
#testimonials .quote {
  width: 22rem;
  flex: 0 0 auto;
  border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
}

/* Explicit "swipe →" affordance, mobile only — fades out once the user
   has actually scrolled the row. Inline with the section label so it
   doesn't add vertical space; pushed to the right of the label via
   margin-left:auto inside a flexified label. */
/* Shared "swipe →" affordance, mobile only — used by the testimonials row
   and the Selected-work category bar. Fades out via .is-faded once the user
   has got the message. Sits inline with the section label (pushed right via
   margin-left:auto in a flexified label) so it adds no vertical space. */
.swipe-hint {
  display: none;
}
@media (max-width: 640px) {
  .section-label:has(.swipe-hint) {
    display: flex;
    align-items: baseline;
  }
  .swipe-hint {
    display: inline;
    margin-left: auto;
    color: var(--fg-muted);
    transition: opacity .3s var(--ease);
  }
}
.swipe-hint.is-faded { opacity: 0; }
/* Looping nudge on the arrow so the swipe hint reads as a gesture
   prompt, not a static label. inline-block needed for transform. The
   global prefers-reduced-motion rule disables the animation. */
.swipe-hint-arrow {
  display: inline-block;
  animation: swipe-hint-nudge 1.8s var(--ease) infinite;
}
@keyframes swipe-hint-nudge {
  0%, 60%, 100% { transform: translateX(0); }
  30%          { transform: translateX(.4em); }
}

.quote blockquote {
  margin: 0 0 1rem;
  color: var(--fg-soft);
  font-size: .98rem;
  line-height: 1.6;
  position: relative;
  padding-left: 1rem;
}
.quote blockquote::before {
  content: "“";
  position: absolute;
  left: -.1rem;
  top: -.6rem;
  font-size: 2.2rem;
  color: var(--accent);
  line-height: 1;
}
.quote figcaption {
  font-family: var(--font-mono);
  font-size: .75rem;
  color: var(--fg-muted);
  letter-spacing: .08em;
  line-height: 1.6;
}
.quote figcaption a {
  color: var(--fg-soft);
  border-bottom: 1px solid var(--border);
  transition: color .2s var(--ease), border-color .2s var(--ease);
  position: relative;
}
/* Enlarge the touch target without shifting visual layout. A pseudo
   element stretches the hit zone ~.85rem vertically and ~.4rem
   horizontally so it lands near the 44px mobile tap-target guideline
   while the link text keeps its original baseline position. */
.quote figcaption a::before {
  content: "";
  position: absolute;
  inset: -.85rem -.4rem;
}
.quote figcaption a:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}
/* Card hover bumps the underline value, mirroring the work-card meta
   links. Scoped to #testimonials so case-study testimonial blocks
   (which aren't interactive on hover) stay unaffected. */
#testimonials .quote:hover figcaption a:not(:hover) {
  border-bottom-color: var(--fg-muted);
}
.cite-org { color: var(--fg-muted); }

/* ---------- Contact ---------- */
.contact .section-heading { max-width: 28ch; }

.email-link {
  display: inline-block;
  font-size: clamp(1.4rem, 4.5vw, 2.6rem);
  font-weight: 600;
  letter-spacing: -.015em;
  margin: .5rem 0 2.5rem;
  border-bottom: 1px solid var(--border);
  padding-bottom: .35rem;
}
.email-link:hover { border-bottom-color: var(--accent); }

.socials {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem 2rem;
}
.socials a {
  font-family: var(--font-mono);
  font-size: .85rem;
  color: var(--fg-muted);
  letter-spacing: .08em;
  text-transform: uppercase;
  position: relative;
  padding-bottom: .2rem;
}
.socials a:hover { color: var(--accent); }
.socials a[data-placeholder]::after {
  content: "todo";
  margin-left: .5rem;
  font-size: .65rem;
  padding: .15rem .4rem;
  border-radius: 4px;
  background: rgba(181,83,45,.12);
  color: var(--accent);
  letter-spacing: .1em;
}

/* ---------- Footer ---------- */
.site-footer {
  padding: 2rem 0;
  color: var(--fg-muted);
  font-family: var(--font-mono);
  font-size: .78rem;
  letter-spacing: .1em;
}
/* The footer is the band after the last section, so keep the alternation
   going: when the last section is light (odd in the nth-of-type rhythm), the
   footer flips to the alt tone so the two don't merge. */
body:has(main > section:last-of-type:nth-of-type(odd)) .site-footer {
  background: var(--bg-alt);
}
.footer-row {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
}

/* ---------- Focus ring ---------- */
a:focus-visible,
.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    transition: none !important;
    animation: none !important;
  }
}

/* ============================================================
   Case study pages (case-studies/<slug>/index.html)
   Outcome-led layout. Reuses .container, .section, .section-alt,
   .section-label, .tags, .email-link, .quote tokens.
   ============================================================ */

/* Hero: project-focused, narrower than the homepage .hero */
.case-hero {
  padding: clamp(4rem, 12vh, 8rem) 0 clamp(2.5rem, 6vh, 4rem);
  border-bottom: 1px solid var(--border);
  background:
    radial-gradient(900px 380px at 95% 0%, rgba(181,83,45,.06), transparent 60%),
    radial-gradient(700px 320px at 0% 60%, rgba(181,83,45,.035), transparent 60%);
}

/* Keep the hero in the same centred column as the content sections
   (.container.case-prose is 780px), so the title, subtitle and meta line
   up with the body copy below rather than spanning the full container. */
.case-hero .container { max-width: 780px; }

/* …but let the hero video break back out to the full container width, so
   the narrowed text column doesn't crop the PropAlchemy hero clip. */
.case-hero .case-video-hero {
  width: var(--max);
  max-width: calc(100vw - 2 * var(--pad));
  margin-left: 50%;
  transform: translateX(-50%);
}

/* Hero with a vertical clip: text column on the left, phone-shaped video on
   the right, so the portrait footage anchors the layout instead of leaving a
   wide gap. Stacks (text over centred video) below the breakpoint. */
.case-hero-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
}
.case-hero-text { min-width: 0; }
.case-hero-media {
  margin: 0 auto;
  width: 100%;
  max-width: 300px;
}
@media (min-width: 860px) {
  .case-hero-grid {
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: clamp(2.5rem, 5vw, 4.5rem);
    align-items: center;
  }
  .case-hero-text .case-subtitle { margin-bottom: 2.5rem; }
  .case-hero-text .case-meta { margin-bottom: 0; }
  .case-hero-media { margin: 0; }
}

/* Split layout — prose beside a single vertical clip, reusing the hero
   treatment for sections that feature one portrait video. Stacks on mobile;
   --reverse puts the clip on the left for an alternating rhythm. */
.case-split {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
}
.case-split-text { min-width: 0; }
/* Optional "tightener": when JS finds the text column shorter than the media
   beside it, it narrows the measure (--tighten-w) so the prose reflows taller
   and closes the vertical gap — font size unchanged. The narrowed block stays
   centred in its column. Defaults to none, so without JS the column fills its
   grid cell as usual. */
.case-split-text[data-tighten] {
  /* --tighten-w is set by JS on the grid container (.case-split) and inherits
     down to here, so the text fills the narrowed track exactly. */
  max-width: var(--tighten-w, none);
}
.case-split-media {
  margin: 0 auto;
  width: 100%;
  max-width: 300px;
}
@media (min-width: 860px) {
  .case-split {
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: clamp(2.5rem, 5vw, 4.5rem);
    align-items: start;
  }
  .case-split-media { margin: 0; }
  .case-split--reverse { grid-template-columns: 300px minmax(0, 1fr); }
  .case-split--reverse .case-split-media { order: -1; }

  /* Tightened: JS has set --tighten-w on the container, so size the text track
     to exactly that width and centre the whole media+text pair in the row
     (leftover space splits evenly on both outer sides). */
  .case-split[style*="--tighten-w"] { justify-content: center; }
  .case-split[style*="--tighten-w"]:not(.case-split--reverse) {
    grid-template-columns: var(--tighten-w) 300px;
  }
  .case-split--reverse[style*="--tighten-w"] {
    grid-template-columns: 300px var(--tighten-w);
  }
}

.case-eyebrow {
  font-family: var(--font-mono);
  font-size: .8rem;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: .22em;
  margin: 0 0 1.5rem;
}

.case-title {
  font-family: "Be Vietnam Pro", var(--font-sans);
  margin: 0 0 1.25rem;
  font-size: clamp(2.5rem, 7vw, 5rem);
  line-height: 1.02;
  letter-spacing: -.02em;
  font-weight: 700;
}

.case-subtitle {
  font-size: clamp(1.1rem, 1.6vw, 1.35rem);
  color: var(--fg-soft);
  max-width: 60ch;
  margin: 0 0 2.5rem;
}

.case-meta {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  margin: 0 0 3rem;
  padding: 1.25rem 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
@media (min-width: 700px) {
  .case-meta { grid-template-columns: repeat(2, 1fr); gap: 1.5rem 2rem; }
}
/* When nothing follows the meta in the hero (no hero video), the closing
   rule is redundant — drop it and its trailing space. */
.case-meta:last-child {
  border-bottom: none;
  padding-bottom: 0;
  margin-bottom: 0;
}
.case-meta > div { min-width: 0; }
.case-meta dt {
  font-family: var(--font-mono);
  font-size: .7rem;
  color: var(--accent);
  letter-spacing: .2em;
  text-transform: uppercase;
  margin: 0 0 .35rem;
}
.case-meta dd {
  margin: 0;
  color: var(--fg-soft);
  font-size: .95rem;
  line-height: 1.45;
}
/* Subtle underline on meta links (clients, team) so they read as clickable,
   matching the .case-study-card-meta / .project-meta treatment. */
.case-meta a {
  border-bottom: 1px solid var(--border);
  padding-bottom: 1px;
  transition: color .2s var(--ease), border-bottom-color .2s var(--ease);
}
.case-meta a:hover {
  border-bottom-color: var(--accent);
}

/* Prose container — tighter than the full 1100px so reading lines stay short */
.case-prose {
  max-width: 780px;
}
.case-prose h2 {
  font-size: clamp(1.4rem, 2.6vw, 2rem);
  line-height: 1.2;
  letter-spacing: -.015em;
  margin: 0 0 1.75rem;
  max-width: 28ch;
  font-weight: 600;
  text-wrap: balance;
}
.case-prose p {
  margin: 0 0 1.1rem;
  color: var(--fg-soft);
}
.case-prose p:last-child { margin-bottom: 0; }

/* Deliverables: capability headline + technical sub-line per item */
.case-deliverables {
  list-style: none;
  padding: 0;
  margin: 2rem 0 0;
  display: grid;
  gap: 1.75rem;
}
.case-deliverables li {
  position: relative;
  padding-left: 1.5rem;
}
.case-deliverables li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .6em;
  width: .6rem;
  height: 1px;
  background: var(--accent);
}
.case-deliverables strong {
  display: block;
  color: var(--fg);
  font-weight: 600;
  margin-bottom: .35rem;
  font-size: 1.02rem;
}
.case-deliverables span {
  color: var(--fg-muted);
  line-height: 1.55;
}

/* Production pillars: simpler accented list */
.case-pillars {
  list-style: none;
  padding: 0;
  margin: 1.75rem 0 0;
  display: grid;
  gap: .9rem;
}
.case-pillars li {
  color: var(--fg-muted);
  padding-left: 1.1rem;
  position: relative;
  line-height: 1.55;
}
.case-pillars li::before {
  content: "";
  position: absolute;
  left: 0; top: .65em;
  width: .4rem; height: 1px;
  background: var(--accent);
}

/* Image figure + placeholder.
   When real assets arrive, drop them in case-studies/<slug>/images/
   and replace the .case-image-placeholder with a <figure class="case-image">
   containing an <img>. The placeholder shows exactly what's missing. */
.case-image {
  margin: 2.5rem 0 0;
}
.case-image img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 12px;
  border: 1px solid var(--border);
}
.case-image figcaption {
  margin-top: .75rem;
  font-family: var(--font-mono);
  font-size: .75rem;
  color: var(--fg-muted);
  letter-spacing: .06em;
}
/* Photos open in a full-screen view on click */
.case-image img { cursor: zoom-in; }

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(0, 0, 0, .92);
  cursor: zoom-out;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s var(--ease);
}
.lightbox.is-open { opacity: 1; pointer-events: auto; }
.lightbox img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 8px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, .5);
}
.lightbox-close {
  position: absolute;
  top: clamp(.75rem, 3vw, 1.5rem);
  right: clamp(.75rem, 3vw, 1.5rem);
  width: 2.5rem;
  height: 2.5rem;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, .12);
  color: #fff;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  transition: background .2s var(--ease);
}
.lightbox-close:hover { background: rgba(255, 255, 255, .25); }

.case-image-placeholder .case-image-frame {
  aspect-ratio: 16 / 9;
  background:
    repeating-linear-gradient(135deg,
      var(--bg-card) 0,
      var(--bg-card) 14px,
      var(--bg-alt) 14px,
      var(--bg-alt) 28px);
  border: 1px dashed var(--border);
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 1rem;
}
.case-image-hero .case-image-frame {
  aspect-ratio: 21 / 9;
}
.case-image-label {
  font-family: var(--font-mono);
  font-size: .8rem;
  color: var(--fg-muted);
  letter-spacing: .12em;
  text-transform: uppercase;
  background: var(--bg);
  padding: .4rem .75rem;
  border: 1px solid var(--border);
  border-radius: 999px;
}

/* Stack tags — reuses .tags but allows the line to breathe */
.case-stack {
  margin-top: 1.5rem;
  gap: .5rem;
}

/* Footer section: contact CTA on the left, back link on the right */
.case-footer-section {
  padding-top: clamp(3rem, 8vh, 5rem);
  padding-bottom: clamp(3rem, 8vh, 5rem);
}
.case-footer-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  align-items: end;
}
@media (min-width: 700px) {
  .case-footer-grid {
    grid-template-columns: 1fr auto;
    gap: 3rem;
  }
}
.case-footer-section .email-link {
  margin: .5rem 0 0;
  font-size: clamp(1.2rem, 3.5vw, 2rem);
}
.case-backlink {
  font-family: var(--font-mono);
  font-size: .85rem;
  color: var(--fg-soft);
  letter-spacing: .08em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--border);
  padding-bottom: .25rem;
  transition: color .2s var(--ease), border-color .2s var(--ease);
  align-self: end;
}
.case-backlink:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* ---------- Homepage project card: linked variant ----------
   When a project has a case study, the card becomes clickable and
   shows a "Read case study" affordance bottom-right. The other cards
   stay flat — the asymmetry signals "this one has more". */
.project--linked {
  position: relative;
}
.project-case-link {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  font-family: var(--font-mono);
  font-size: .78rem;
  color: var(--accent);
  letter-spacing: .12em;
  white-space: nowrap;
  text-transform: uppercase;
}
.project-case-link::after {
  content: "→";
  font-family: var(--font-sans);
  transition: transform .25s var(--ease);
}
/* Whole-card click target via stretched pseudo-element on the link */
.project--linked .project-case-link::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 14px;
}
/* Inner links inside the meta sit above the stretched overlay so they
   capture their own clicks rather than triggering the card-wide link. */
.project--linked .project-meta a {
  position: relative;
  z-index: 1;
}
.project--linked:hover .project-case-link::after {
  transform: translateX(3px);
}

/* ---------- PropAlchemy case-study card (sits inside Selected Work) ----------
   Larger and more visual than the surrounding text-only project cards:
   video preview on the left, meta + title + description + tags + CTA on
   the right. Single column on mobile, two-column at ≥800px. */
.case-study-card { margin: 0; counter-increment: project; }

.case-study-card-link {
  position: relative;
  display: block;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
  color: var(--fg);
  transition: border-color .25s var(--ease), transform .25s var(--ease), box-shadow .25s var(--ease);
}
/* The card-wide click target. An invisible anchor stretched across the
   card via inset:0 — leaves inner links (meta client names) free to be
   clicked individually when they're lifted above it with z-index. */
.case-study-card-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
}
/* Inner links inside the card must sit above the overlay so clicks land
   on them rather than navigating to the case study. */
.case-study-card-meta a {
  position: relative;
  z-index: 2;
}
/* Hover/focus reactions only on cards that contain the overlay link —
   `<div>` wrappers with no case study to link to shouldn't look
   interactive. Hovering the meta links also triggers it via :hover
   bubbling up to the link wrapper, which is fine. */
.case-study-card-link:has(.case-study-card-overlay):hover {
  border-color: rgba(181,83,45,.5);
  transform: translateY(-2px);
  box-shadow: 0 12px 28px -14px rgba(12,14,18,.12);
}

.case-study-card-body {
  position: relative;
  padding: clamp(1.5rem, 3vw, 2rem);
  display: flex;
  flex-direction: column;
  gap: .85rem;
  min-width: 0;
}
/* Project number sits inside the body area so it's visible at every
   viewport (on mobile the media is on top — placing the number on the
   card root would overlay the video). */
.case-study-card-body::before {
  content: counter(project, decimal-leading-zero);
  position: absolute;
  top: 1.25rem;
  right: 1.5rem;
  font-family: var(--font-mono);
  font-size: .75rem;
  color: var(--fg-muted);
  letter-spacing: .12em;
}

.case-study-card-meta {
  font-family: var(--font-mono);
  font-size: .72rem;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: .14em;
  padding-right: 2.5rem;
}

.case-study-card h4 {
  margin: 0;
  font-size: clamp(1.15rem, 1.7vw, 1.4rem);
  line-height: 1.25;
  letter-spacing: -.01em;
  font-weight: 600;
  color: var(--fg);
}

.case-study-card p {
  margin: 0;
  color: var(--fg-soft);
  line-height: 1.55;
  max-width: 68ch;
}

.project-footer {
  display: flex;
  align-items: flex-end;
  gap: 1rem;
  flex-wrap: wrap;
}
.project-footer .tags {
  flex: 1 1 auto;
  min-width: 0;
}
.project-footer > :last-child {
  margin-left: auto;
}

.case-study-card-cta {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-family: var(--font-mono);
  font-size: .78rem;
  color: var(--accent);
  letter-spacing: .12em;
  text-transform: uppercase;
  white-space: nowrap;
}
.case-study-card-cta span[aria-hidden="true"] {
  font-family: var(--font-sans);
  transition: transform .25s var(--ease);
}
.case-study-card-link:has(.case-study-card-overlay):hover .case-study-card-cta span[aria-hidden="true"] {
  transform: translateX(3px);
}
/* Underline the CTA label only — not the arrow. The label and arrow
   are separate spans so we can target each: label gets the underline
   on card hover, arrow keeps its translate animation. */
.case-study-card-link:has(.case-study-card-overlay):hover .case-study-card-cta-label {
  text-decoration: underline;
  text-underline-offset: .25em;
}
.project--linked:hover .project-case-link > span {
  text-decoration: underline;
  text-underline-offset: .25em;
}
/* Subtle default underline on inline client links in the meta row,
   matching the .email-link / .now-list a treatment. Direct link hover
   bumps to accent; card hover bumps the line to fg-muted — a softer
   "this card is interactive" cue. :not(:hover) lets the link's own
   accent hover win when the link itself is the cursor target, since
   the card-hover selector outranks the link :hover by specificity. */
.case-study-card-meta a,
.project-meta a {
  border-bottom: 1px solid var(--border);
  padding-bottom: 1px;
  transition: color .2s var(--ease), border-bottom-color .2s var(--ease);
}
.case-study-card-meta a:hover,
.project-meta a:hover {
  border-bottom-color: var(--accent);
}
.case-study-card-link:has(.case-study-card-overlay):hover .case-study-card-meta a:not(:hover),
.project:hover .project-meta a:not(:hover) {
  border-bottom-color: var(--fg-muted);
}

.case-study-card-overlay:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}


/* ---------- Case-study video embed ---------- */
.case-video {
  margin: 2.5rem 0 0;
}
/* When a video is also the split media (the "brief" / "what we built" clips),
   keep it centred in the single-column mobile layout — the shorthand above
   would otherwise zero out the `margin: 0 auto` centering from
   .case-split-media. The desktop grid realigns it to the column edge. */
.case-video.case-split-media {
  margin-inline: auto;
}
@media (min-width: 860px) {
  .case-video.case-split-media { margin-inline: 0; }
}
.case-video-hero { margin-top: 0; }
.case-video-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: #000;
}
.case-video-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
/* Locally-hosted clips (mp4). The source files are portrait 9:16, so the
   video is a plain in-flow block at full width with height:auto — no aspect
   frame, no absolute positioning. It rounds its own corners rather than being
   clipped by an overflow:hidden wrapper. */
.case-video > video {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: #000;
}
.case-video figcaption {
  margin-top: .75rem;
  font-family: var(--font-mono);
  font-size: .75rem;
  color: var(--fg-muted);
  letter-spacing: .06em;
}

/* Portrait media (phone-captured clips + on-site photos). The footage is
   vertical 9:16, so it gets a phone-shaped frame rather than the wide
   landscape default. */
.case-portrait {
  max-width: 300px;
  margin-left: auto;
  margin-right: auto;
}
.case-portrait .case-video-frame {
  aspect-ratio: 9 / 16;
}
.case-portrait figcaption {
  text-align: center;
}

/* Gallery — lay several vertical clips/photos out in a centred, wrapping
   row of phone-width cards instead of one tall single-file column. Each
   card holds its phone width regardless of how many are in the row. */
.case-gallery {
  margin-top: 2.5rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 2rem 1.5rem;
}
.case-gallery > figure {
  flex: 0 1 240px;
  max-width: 260px;
  margin: 0;
}
.case-gallery .case-video-frame {
  aspect-ratio: 9 / 16;
}
.case-gallery .case-image img {
  aspect-ratio: 9 / 16;
  object-fit: cover;
}
.case-gallery figcaption {
  text-align: center;
}
/* A 3-up gallery: tighten the card basis so three vertical clips sit on
   one row inside the prose column instead of wrapping to 2 + 1. */
.case-gallery--wide > figure {
  flex: 0 1 205px;
  max-width: 230px;
}

/* A fixed single-row gallery — keeps N vertical clips on one line (used in
   the full-width container so they don't get cramped). Collapses to two
   columns on small screens. */
.case-gallery--row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.75rem 1.5rem;
  align-items: start;
}
.case-gallery--row > figure { max-width: none; }
@media (max-width: 640px) {
  .case-gallery--row { grid-template-columns: repeat(2, 1fr); }
}

