/* ════════════════════════════════════════════════════════════════════════
   torrhes.com — v2 template
   Palette: Hearth, verbatim from ../../../../brand/tokens/brand-tokens.css.
   Rules for what may carry text are in brand/GUIDELINES.md §2.
   ════════════════════════════════════════════════════════════════════════ */

:root{
  /* colour — Hearth
     Terracotta and Amber are NOT interchangeable. The guidelines set hard
     limits and every ratio below is measured against --cream:
       --terra      4.24:1  large text (≥18px, or ≥14px bold) and UI only
       --terra-ink  5.47:1  the small-text terracotta — eyebrows, links, 11px
       --amber      2.00:1  decorative ONLY on cream; on --dark it is 7.99 (AAA)
     Warm Stone (#A8A095) is 2.40:1 and never carries text, so --muted follows
     the guidelines' own escape hatch: espresso at 72% → 6.55:1. */
  --cream:      #FAF6EE;
  --cream-2:    #F4EEE0;
  --card:       #ffffff;
  --ink:        #1F1A17;
  --ink-soft:   #4A413B;
  --muted:      rgba(31,26,23,.72);
  --line:       #E8E1D2;
  --terra:      #C2542A;
  --terra-ink:  #A84620;
  --amber:      #E8A33D;
  --dark:       #1F1A17;
  --dark-2:     #2A2320;
  --dark-line:  #3A322C;
  --dark-text:  #DED6C8;

  /* type
     Fraunces stays the display face for headings — it is the site's voice.
     Quicksand 700 is loaded for the wordmark ONLY: the logotype has to match
     brand/logos/wordmark.svg and every other brand surface, headings do not. */
  --display:  'Fraunces', Georgia, 'Times New Roman', serif;
  --wordmark: 'Quicksand', 'Nunito', 'Poppins', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --body:     'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --mono:     'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* space + shape */
  --wrap:    1140px;
  --gutter:  clamp(20px, 5vw, 40px);
  --r-sm:    10px;
  --r-md:    16px;
  --r-lg:    24px;

  --shadow-sm: 0 1px 2px rgba(31,26,23,.05), 0 4px 14px rgba(31,26,23,.04);
  --shadow-md: 0 2px 6px rgba(31,26,23,.06), 0 18px 40px rgba(31,26,23,.08);

  --ease: cubic-bezier(.22,.61,.36,1);
}

/* ── reset ─────────────────────────────────────────────────────────────── */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{
  background:var(--cream);
  color:var(--ink);
  font-family:var(--body);
  font-size:17px;
  line-height:1.65;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img,video,svg{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
button,input,textarea{font:inherit;color:inherit}
ul,ol{list-style:none}

:focus-visible{outline:2px solid var(--terra);outline-offset:3px;border-radius:4px}

.skip-link{
  position:absolute;left:-9999px;top:12px;z-index:200;
  background:var(--ink);color:var(--cream);
  padding:10px 16px;border-radius:var(--r-sm);font-size:14px;
}
.skip-link:focus{left:12px}

.wrap{width:100%;max-width:var(--wrap);margin-inline:auto;padding-inline:var(--gutter)}

/* ── shared type ───────────────────────────────────────────────────────── */
.eyebrow{
  font-family:var(--mono);
  font-size:11px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--terra-ink);
  margin-bottom:18px;
}
.section-title{
  font-family:var(--display);
  font-weight:400;
  font-size:clamp(30px,4.6vw,46px);
  line-height:1.12;
  letter-spacing:-.02em;
  text-wrap:balance;
}
.section-lead{
  margin-top:16px;
  max-width:56ch;
  color:var(--ink-soft);
  font-size:clamp(16px,1.9vw,18px);
  text-wrap:pretty;
}

/* ── buttons ───────────────────────────────────────────────────────────── */
.btn{
  --btn-bg:transparent; --btn-fg:var(--ink); --btn-bd:var(--line);
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  background:var(--btn-bg);color:var(--btn-fg);
  border:1px solid var(--btn-bd);
  padding:14px 26px;border-radius:100px;
  font-family:var(--mono);font-size:13px;letter-spacing:.06em;
  cursor:pointer;
  transition:transform .25s var(--ease), box-shadow .25s var(--ease),
             background-color .25s var(--ease), border-color .25s var(--ease);
}
.btn:hover{transform:translateY(-2px)}
.btn:active{transform:translateY(0)}
.btn-primary{--btn-bg:var(--ink);--btn-fg:var(--cream);--btn-bd:var(--ink);box-shadow:var(--shadow-sm)}
/* --terra-ink, not --terra: the label is 13px letterspaced mono, i.e. small
   text, and cream on #C2542A is only 4.24:1. On #A84620 it is 5.47:1. */
.btn-primary:hover{--btn-bg:var(--terra-ink);--btn-bd:var(--terra-ink);box-shadow:var(--shadow-md)}
.btn-ghost{--btn-bg:transparent;--btn-bd:var(--line)}
.btn-ghost:hover{--btn-bd:var(--terra)}
.btn-sm{padding:10px 18px;font-size:12px}
.btn-block{width:100%}

/* ════════════════════════  HEADER  ════════════════════════ */
.site-header{
  position:sticky;top:0;z-index:100;
  background:rgba(250,246,238,.72);
  backdrop-filter:saturate(180%) blur(14px);
  -webkit-backdrop-filter:saturate(180%) blur(14px);
  border-bottom:1px solid transparent;
  transition:border-color .3s var(--ease), background-color .3s var(--ease);
}
.site-header.is-scrolled{border-bottom-color:var(--line);background:rgba(250,246,238,.9)}
.header-inner{display:flex;align-items:center;justify-content:space-between;gap:20px;height:68px}

/* ── brand lockup ──────────────────────────────────────────────────────────
   Symbol + wordmark, per "2. Logo system" in the brand guidelines: bars+H mark
   on the left, lowercase Quicksand 700 wordmark on the right, and ONLY the h in
   terracotta — it is what ties the wordmark to the mark. Never title case.   */
.brand{
  display:inline-flex;align-items:center;gap:10px;
  font-family:var(--wordmark);font-size:23px;font-weight:700;
  letter-spacing:-.03em;line-height:1;
}
/* The wordmark MUST stay inside .brand-word. .brand is a flex container, so a
   bare "torr<span>h</span>es" becomes three anonymous flex items and the gap
   above pushes them apart into "torr h es". One wrapper = one flex item. */
.brand-word{white-space:nowrap}
.brand-accent{color:var(--terra)}
/* viewBox crops the artwork's own bounds (x 18–160, y 44–160) plus a little
   optical padding — the source file pads to a 200×200 square, which at logo
   size renders the mark small and floating high. Artwork itself is untouched. */
.brand-mark{width:31px;height:26px;flex:none}

/* Symbol colours live here, not on the SVG, so the mark inherits the palette. */
.bm-bar1,.bm-bar2{fill:var(--amber)}
.bm-ul,.bm-cross,.bm-ur{fill:var(--terra)}

/* Build animation, ported from brand/logos/symbol-animated-v2.svg ("click"):
   the amber bars pop in, both H uprights rise, then the crossbar clicks across
   to lock the H. The bars then breathe in a phase-offset loop.
   The global prefers-reduced-motion block at the end of this file collapses
   every animation to its end state, so no separate reduced-motion rule here. */
.bm-bar1,.bm-bar2,.bm-ul,.bm-cross,.bm-ur{transform-box:fill-box;transform-origin:center}
.bm-bar1{
  --end-opacity:.55;
  animation:bmPop .38s cubic-bezier(.34,1.56,.64,1) 0s both,
            bmPulse1 4s ease-in-out 1.7s infinite;
}
.bm-bar2{
  --end-opacity:.80;
  animation:bmPop .38s cubic-bezier(.34,1.56,.64,1) .18s both,
            bmPulse2 3.6s ease-in-out 2.2s infinite;
}
.bm-ul   {animation:bmRise    .44s cubic-bezier(.22,1,.36,1) .40s both}
.bm-ur   {animation:bmRise    .44s cubic-bezier(.22,1,.36,1) .68s both}
.bm-cross{animation:bmStretch .32s cubic-bezier(.22,1,.36,1) 1.00s both}

@keyframes bmPop{
  from{opacity:0;transform:scale(.55) translateY(14px)}
  to  {opacity:var(--end-opacity);transform:scale(1) translateY(0)}
}
@keyframes bmRise{
  from{opacity:0;transform:translateY(28px)}
  to  {opacity:1;transform:translateY(0)}
}
@keyframes bmStretch{
  from{opacity:0;transform:scaleX(0)}
  to  {opacity:1;transform:scaleX(1)}
}
@keyframes bmPulse1{0%,100%{opacity:.55} 50%{opacity:.78}}
@keyframes bmPulse2{0%,100%{opacity:.80} 50%{opacity:.96}}

.nav{display:flex;align-items:center;gap:30px}
.nav > a:not(.btn){
  font-size:15px;color:var(--ink-soft);position:relative;padding:4px 0;
  transition:color .2s var(--ease);
}
.nav > a:not(.btn)::after{
  content:'';position:absolute;left:0;bottom:0;height:1px;width:0;
  background:var(--terra);transition:width .3s var(--ease);
}
.nav > a:not(.btn):hover{color:var(--ink)}
.nav > a:not(.btn):hover::after{width:100%}

.header-tools{display:flex;align-items:center;gap:12px}

.lang-switch{
  display:flex;border:1px solid var(--line);border-radius:100px;
  background:rgba(255,255,255,.7);padding:2px;
}
/* Anchors, not buttons: these navigate between two real URLs now. The block
   display and text-decoration reset are what the button gave for free. */
.lang-switch a{
  border:0;background:transparent;cursor:pointer;
  display:block;text-decoration:none;
  font-family:var(--mono);font-size:11px;letter-spacing:.08em;
  padding:6px 11px;border-radius:100px;color:var(--muted);
  transition:background-color .25s var(--ease), color .25s var(--ease);
}
.lang-switch a:hover{color:var(--ink)}
.lang-switch a.is-active{background:var(--ink);color:var(--cream)}
.lang-switch a.is-active:hover{color:var(--cream)}

.menu-btn{
  display:none;width:42px;height:42px;border:1px solid var(--line);
  border-radius:12px;background:rgba(255,255,255,.7);cursor:pointer;
  flex-direction:column;align-items:center;justify-content:center;gap:5px;
}
.menu-btn span{
  display:block;width:17px;height:1.5px;background:var(--ink);border-radius:2px;
  transition:transform .3s var(--ease), opacity .2s var(--ease);
}
.menu-btn[aria-expanded="true"] span:first-child{transform:translateY(3.25px) rotate(45deg)}
.menu-btn[aria-expanded="true"] span:last-child{transform:translateY(-3.25px) rotate(-45deg)}

/* ════════════════════════  HERO  ════════════════════════ */
.hero{
  position:relative;
  padding-block:clamp(72px,13vw,132px) clamp(84px,14vw,140px);
  overflow:hidden;
}
.hero-bg{position:absolute;inset:0;z-index:0;pointer-events:none}
.blob{
  position:absolute;border-radius:50%;filter:blur(64px);opacity:.5;
  animation:drift 22s var(--ease) infinite alternate;
}
.blob-1{width:46vw;height:46vw;max-width:520px;max-height:520px;top:-12%;left:-8%;
  background:radial-gradient(circle,rgba(232,163,61,.45),transparent 68%)}
.blob-2{width:40vw;height:40vw;max-width:460px;max-height:460px;top:18%;right:-10%;
  background:radial-gradient(circle,rgba(194,84,42,.30),transparent 68%);animation-delay:-7s;animation-duration:26s}
/* was a sage green — the only off-palette colour on the page. Warm Stone is the
   Hearth neutral and keeps this blob reading as a counterweight, not a hue. */
.blob-3{width:34vw;height:34vw;max-width:400px;max-height:400px;bottom:-16%;left:34%;
  background:radial-gradient(circle,rgba(168,160,149,.34),transparent 68%);animation-delay:-13s;animation-duration:30s}
@keyframes drift{
  from{transform:translate3d(0,0,0) scale(1)}
  to  {transform:translate3d(4%,6%,0) scale(1.14)}
}
.grid-lines{
  position:absolute;inset:0;
  background-image:linear-gradient(var(--line) 1px,transparent 1px),
                   linear-gradient(90deg,var(--line) 1px,transparent 1px);
  background-size:72px 72px;
  opacity:.5;
  mask-image:radial-gradient(ellipse 78% 62% at 50% 42%,#000 20%,transparent 78%);
  -webkit-mask-image:radial-gradient(ellipse 78% 62% at 50% 42%,#000 20%,transparent 78%);
}

.hero-inner{position:relative;z-index:1;max-width:900px}
.hero-title{
  font-family:var(--display);
  font-weight:300;
  font-size:clamp(38px,7.4vw,76px);
  line-height:1.04;
  letter-spacing:-.032em;
  text-wrap:balance;
}
.hero-title em{
  display:block;
  font-style:italic;font-weight:400;
  /* Stops at terracotta rather than running out into amber: this is the one
     line of hero copy, and amber on cream is 2.00:1 — it would fade to
     unreadable at the tail. Both stops here clear AA-Large at this size. */
  background:linear-gradient(100deg,var(--terra-ink),var(--terra));
  -webkit-background-clip:text;background-clip:text;
  color:transparent;
  padding-bottom:.08em;
}
.hero-sub{
  margin-top:26px;max-width:58ch;
  font-size:clamp(17px,2.2vw,20px);
  color:var(--ink-soft);
  text-wrap:pretty;
}
/* Credibility line. Deliberately quiet: mono, small, muted — it is evidence,
   not a claim, and shouting it would undercut the point. */
/* The answer paragraph sits tighter under the question than a fresh block would. */
.hero-sub + .hero-sub{margin-top:14px}
.hero-cred{
  margin-top:20px;
  font-family:var(--mono);font-size:13px;letter-spacing:.04em;
  color:var(--ink-soft);
}
.hero-cta{display:flex;flex-wrap:wrap;gap:14px;margin-top:32px}

.hero-strip{
  display:flex;flex-wrap:wrap;gap:10px 26px;
  margin-top:52px;padding-top:26px;border-top:1px solid var(--line);
}
.hero-strip li{
  font-family:var(--mono);font-size:11px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--muted);
  display:flex;align-items:center;gap:9px;
}
.hero-strip li::before{
  content:'';width:5px;height:5px;border-radius:50%;background:var(--amber);opacity:.9;
}

.scroll-hint{
  position:absolute;bottom:26px;left:50%;transform:translateX(-50%);
  width:22px;height:34px;border:1px solid var(--line);border-radius:100px;
  display:flex;justify-content:center;padding-top:7px;z-index:1;
}
.scroll-hint span{
  width:3px;height:7px;border-radius:2px;background:var(--amber);
  animation:scrolldot 1.9s var(--ease) infinite;
}
@keyframes scrolldot{
  0%{opacity:0;transform:translateY(-3px)}
  35%{opacity:1}
  100%{opacity:0;transform:translateY(11px)}
}

/* hero entrance */
.hero-anim{opacity:0;transform:translateY(22px);animation:heroIn .9s var(--ease) forwards}
.hero-anim:nth-child(1){animation-delay:.06s}
.hero-anim:nth-child(2){animation-delay:.16s}
.hero-anim:nth-child(3){animation-delay:.28s}
.hero-anim:nth-child(4){animation-delay:.40s}
.hero-anim:nth-child(5){animation-delay:.52s}
/* 6 children since the credibility line was added between the sub and the CTAs;
   without this the last one (the capability strip) never gets a delay. */
.hero-anim:nth-child(6){animation-delay:.64s}
/* 7 since the hero sub was split into a question and its answer. */
.hero-anim:nth-child(7){animation-delay:.76s}
@keyframes heroIn{to{opacity:1;transform:none}}

/* ════════════════════════  SECTIONS  ════════════════════════ */
.section{padding-block:clamp(72px,11vw,124px)}
.section-alt{background:var(--cream-2);border-block:1px solid var(--line)}
.section-head{max-width:64ch;margin-bottom:clamp(40px,6vw,64px)}

/* ── service cards ─────────────────────────────────────────────────────── */
/* Explicit steps rather than auto-fit: auto-fit dropped the 4th card onto a
   lonely second row at ~1440px. 1 → 2 → 4 keeps every row full. */
.cards{display:grid;gap:20px;grid-template-columns:1fr}
@media (min-width:620px){  .cards{grid-template-columns:repeat(2,1fr)} }
/* Three, not four: there are six service cards, and four across leaves a row of
   two orphans. Three gives 2×3 on a desktop and 3×2 on a tablet, both full. */
@media (min-width:1040px){ .cards{grid-template-columns:repeat(3,1fr)} }
.card{
  background:var(--card);
  border:1px solid var(--line);
  border-radius:var(--r-lg);
  padding:30px 28px 32px;
  position:relative;overflow:hidden;
  transition:transform .35s var(--ease), box-shadow .35s var(--ease), border-color .35s var(--ease);
}
.card::before{
  content:'';position:absolute;inset:0 0 auto;height:2px;
  background:linear-gradient(90deg,var(--amber),var(--terra));
  transform:scaleX(0);transform-origin:left;
  transition:transform .45s var(--ease);
}
.card:hover{transform:translateY(-5px);box-shadow:var(--shadow-md);border-color:transparent}
.card:hover::before{transform:scaleX(1)}
.card-icon{
  width:44px;height:44px;border-radius:13px;
  display:grid;place-items:center;margin-bottom:20px;
  background:linear-gradient(150deg,rgba(232,163,61,.16),rgba(194,84,42,.10));
  color:var(--terra);
}
.card-icon svg{width:21px;height:21px}
.card h3{
  font-family:var(--display);font-weight:500;font-size:20px;
  letter-spacing:-.01em;margin-bottom:10px;
}
.card p{font-size:15.5px;color:var(--ink-soft);text-wrap:pretty}

/* ── steps ─────────────────────────────────────────────────────────────── */
.steps{display:grid;gap:20px;grid-template-columns:1fr}
@media (min-width:620px){  .steps{grid-template-columns:repeat(2,1fr)} }
@media (min-width:1000px){ .steps{grid-template-columns:repeat(4,1fr)} }
.step{position:relative;padding:28px 24px 26px;border-radius:var(--r-md);
  background:rgba(255,255,255,.55);border:1px solid var(--line)}
.step-num{
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;
  color:var(--terra-ink);display:block;margin-bottom:14px;
}
.step h3{font-family:var(--display);font-weight:500;font-size:19px;margin-bottom:8px}
.step p{font-size:15px;color:var(--ink-soft);text-wrap:pretty}

/* ════════════════════════  WORK (dark)  ════════════════════════ */
.section-dark{background:var(--dark);color:var(--dark-text)}
.section-dark .section-title{color:#f6f1e7}
.section-dark .section-lead{color:#a79c8b}
.section-dark .eyebrow{color:var(--amber)}

.projects{display:grid}
.project{
  display:grid;
  grid-template-columns:1fr;
  row-gap:16px;
  column-gap:clamp(20px,3.4vw,44px);
}

/* ── one card, stacked ───────────────────────────────────────────────────────
   Source order is head → media → body, which is the reading order here.
   The old layout put the media above its own title, and the gap between cards
   (28px) was only 8px wider than the gap inside one (20px), so the media read
   as belonging to the card above it. Both halves of that are fixed: the title
   comes first, and the cards are separated by a rule rather than by 8px of
   hope. */
@media (max-width:859px){
  .project + .project{
    margin-top:40px;padding-top:40px;
    border-top:1px solid var(--dark-line);
  }
  /* The head→media gap is row-gap alone; on desktop the h3 margin does that job. */
  .project-head h3{margin-bottom:0}
}

/* ── one card, two columns ───────────────────────────────────────────────────
   Head and body go back into a single column beside the media. They sit in
   separate rows that the spanned media stretches equally, so end-aligning the
   head and start-aligning the body meets them in the middle: the pair ends up
   centred against the media, exactly as align-items:center used to do it.
   row-gap stays 0 so the 14px under the h3 is the only thing between them. */
@media (min-width:860px){
  .projects{gap:clamp(28px,5vw,52px)}
  .project{
    grid-template-columns:1.15fr 1fr;
    grid-template-areas:"media head" "media body";
    row-gap:0;
  }
  .project-head{grid-area:head;align-self:end}
  .media       {grid-area:media;align-self:center}
  .project-body{grid-area:body;align-self:start}

  .project:nth-child(even){
    grid-template-columns:1fr 1.15fr;
    grid-template-areas:"head media" "body media";
  }
}

.media{
  position:relative;
  aspect-ratio:16/9;
  border-radius:var(--r-lg);
  border:1px dashed var(--dark-line);
  background:
    linear-gradient(150deg,rgba(232,163,61,.09),rgba(194,84,42,.06)),
    var(--dark-2);
  display:grid;place-items:center;
  overflow:hidden;
}
.media::before{ /* faint grid inside the placeholder */
  content:'';position:absolute;inset:0;
  background-image:linear-gradient(rgba(222,214,200,.05) 1px,transparent 1px),
                   linear-gradient(90deg,rgba(222,214,200,.05) 1px,transparent 1px);
  background-size:34px 34px;
}
.media img,.media video{width:100%;height:100%;object-fit:cover;border-radius:inherit}

/* A card that actually has artwork drops the dashed placeholder treatment: the border and
   the faint grid were there to say "nothing here yet", and they read as noise over a real
   screenshot. */
.media.has-media{border-style:solid;border-color:var(--dark-line);background:var(--dark-2)}
.media.has-media::before{display:none}

.media-play{
  display:block;width:100%;height:100%;padding:0;border:0;background:none;
  cursor:pointer;border-radius:inherit;position:relative;
}
/* The card poster and the viewer's video slide share one badge. Kept as a single
   set of rules on purpose: the sizing here is the only thing stopping the <svg>
   from stretching to fill whatever box it lands in, so a second copy of the badge
   that misses it renders a play triangle the size of the slide. */
.media-play .play-badge,
.viewer-videobtn .play-badge{
  position:absolute;inset:0;display:grid;place-items:center;
  background:rgba(12,14,19,.20);transition:background .18s ease;
  border-radius:inherit;
}
.media-play .play-badge::before,
.viewer-videobtn .play-badge::before{
  content:'';width:74px;height:74px;border-radius:50%;
  background:rgba(255,255,255,.94);box-shadow:0 8px 26px rgba(0,0,0,.34);
  position:absolute;
}
.media-play .play-badge svg,
.viewer-videobtn .play-badge svg{
  position:relative;width:26px;height:26px;margin-left:5px;fill:#1a2233;
}
.media-play:hover .play-badge,
.media-play:focus-visible .play-badge{background:rgba(12,14,19,.36)}
.media-play:focus-visible{outline:2px solid var(--amber);outline-offset:3px}

/* How many items are behind the poster. Filled by main.js from the same list the
   viewer builds from, so it cannot drift from what actually opens. */
.media-count{
  position:absolute;right:12px;bottom:12px;
  font-family:var(--mono);font-size:11px;letter-spacing:.06em;
  color:#f1ece1;background:rgba(12,14,19,.62);
  border:1px solid rgba(241,236,225,.22);border-radius:100px;
  padding:5px 11px;backdrop-filter:blur(6px);
}
.media-count:empty{display:none}

/* ---- video modal --------------------------------------------------------- */
/* Present to screen readers, invisible on screen. The dialog needs an accessible name
   and there is nowhere visible to put one. */
.visually-hidden{
  position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;
}

/* ---- media viewer -------------------------------------------------------- */
/* A dialog with only max-width sits wherever the UA puts it, which is top-left.
   Fixed with inset:0 and auto margins is what actually centres it in both axes. */
.viewer{
  position:fixed;inset:0;margin:auto;
  width:min(1180px,94vw);max-width:none;
  border:0;padding:0;background:transparent;overflow:visible;
  color:#f1ece1;
  display:flex;flex-direction:column;gap:14px;
}
.viewer:not([open]){display:none}
.viewer::backdrop{background:rgba(8,10,15,.9)}

/* Open and close are animated, including the close: without holding the dialog open for
   the duration, .close() removes it from the top layer instantly and the exit never runs. */
.viewer[open]{animation:vmIn .26s var(--ease) both}
.viewer[open]::backdrop{animation:vmFade .26s ease both}
.viewer.is-closing{animation:vmOut .18s ease both}
.viewer.is-closing::backdrop{animation:vmFadeOut .18s ease both}
@keyframes vmIn     {from{opacity:0;transform:scale(.965) translateY(10px)} to{opacity:1;transform:none}}
@keyframes vmOut    {from{opacity:1;transform:none} to{opacity:0;transform:scale(.985)}}
@keyframes vmFade   {from{opacity:0} to{opacity:1}}
@keyframes vmFadeOut{from{opacity:1} to{opacity:0}}

.viewer-stage{position:relative;height:min(74vh,760px)}

/* The track is the whole interaction. Native horizontal scrolling gives swipe,
   momentum and snapping on touch for free; the arrows just set scrollLeft. One
   source of truth for "which slide", so the counter and thumbs cannot disagree
   with what is on screen. */
/* Absolute rather than height:100%. On mobile the stage is a flex:1 item with an
   auto height, and a percentage height against that does not resolve — the track
   would size to its content and push the tallest slide out of the bottom of the
   viewer. inset:0 gives every slide a definite height to be contained inside. */
.viewer-track{
  position:absolute;inset:0;
  display:flex;
  overflow-x:auto;overflow-y:hidden;
  scroll-snap-type:x mandatory;scroll-behavior:smooth;
  scrollbar-width:none;-webkit-overflow-scrolling:touch;
  overscroll-behavior-x:contain;
  border-radius:var(--r-lg);
}
.viewer-track::-webkit-scrollbar{display:none}
.viewer-slide{
  flex:0 0 100%;height:100%;scroll-snap-align:center;scroll-snap-stop:always;
  display:grid;place-items:center;position:relative;
}
/* contain, never cover: a screenshot that has been cropped to fit is a screenshot
   with the answer missing.

   min-width/min-height:0 is what actually makes that true. A grid item defaults to
   min-height:auto, which for a replaced element is its intrinsic height — 996px for
   the phone stills — and that minimum beats max-height:100%. Without these two the
   image overflows the stage and the bottom of every screenshot is quietly cut off. */
.viewer-slide img,
.viewer-slide video{
  max-width:100%;max-height:100%;width:auto;height:auto;
  min-width:0;min-height:0;
  object-fit:contain;border-radius:12px;
  box-shadow:0 24px 70px rgba(0,0,0,.5);
}
.viewer-slide img{background:#0d0f14}
.viewer-slide video{background:#000}
/* The video slide starts as its poster with a play badge. The <video> is built on
   demand, so the 2.7 MB file is only fetched by someone who asks for it. */
.viewer-videobtn{
  display:block;width:100%;height:100%;padding:0;border:0;background:none;
  cursor:pointer;position:relative;
}
.viewer-videobtn img{margin:0 auto;display:block}
.viewer-videobtn .play-badge{position:absolute;inset:0;display:grid;place-items:center;border-radius:12px}

.viewer-nav{
  position:absolute;top:50%;transform:translateY(-50%);
  width:46px;height:46px;border-radius:50%;
  border:1px solid rgba(241,236,225,.24);background:rgba(12,14,19,.62);
  color:#f1ece1;font-size:26px;line-height:1;cursor:pointer;
  display:grid;place-items:center;backdrop-filter:blur(6px);
  transition:background .18s ease,opacity .18s ease;
}
.viewer-nav:hover{background:rgba(12,14,19,.86)}
.viewer-nav:focus-visible{outline:2px solid var(--amber);outline-offset:2px}
.viewer-nav[disabled]{opacity:.28;cursor:default}
.viewer-prev{left:10px}
.viewer-next{right:10px}

.viewer-close{
  position:absolute;top:-44px;right:0;
  background:none;border:0;color:#e8e2d8;font-size:20px;line-height:1;
  cursor:pointer;padding:6px 10px;border-radius:8px;
}
.viewer-close:hover{color:#fff}
.viewer-close:focus-visible{outline:2px solid var(--amber);outline-offset:2px}

.viewer-count{
  text-align:center;
  font-family:var(--mono);font-size:11px;letter-spacing:.1em;
  color:rgba(241,236,225,.62);
}

.viewer-thumbs{
  display:flex;gap:8px;overflow-x:auto;padding:2px;
  scrollbar-width:none;justify-content:center;
}
.viewer-thumbs::-webkit-scrollbar{display:none}
.viewer-thumb{
  flex:0 0 auto;width:74px;height:52px;padding:0;cursor:pointer;
  border:1px solid rgba(241,236,225,.18);border-radius:8px;overflow:hidden;
  background:#14171d;opacity:.5;transition:opacity .18s ease,border-color .18s ease;
  position:relative;
}
.viewer-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.viewer-thumb:hover{opacity:.85}
.viewer-thumb.is-active{opacity:1;border-color:var(--amber)}
.viewer-thumb:focus-visible{outline:2px solid var(--amber);outline-offset:2px}
.viewer-thumb .thumb-play{
  position:absolute;inset:0;display:grid;place-items:center;background:rgba(12,14,19,.35);
}
.viewer-thumb .thumb-play svg{width:16px;height:16px;fill:#fff}

/* On a phone the viewer takes the whole screen, the way a native photo viewer does.
   Every pixel spent on margin is a pixel the screenshot does not get, and the
   screenshot is the entire reason the viewer exists. dvh, not vh, so the browser's
   own chrome does not push the thumbs off the bottom. */
@media (max-width:860px){
  .viewer{
    width:100vw;height:100dvh;max-height:none;
    border-radius:0;gap:10px;
    padding:52px 8px calc(10px + env(safe-area-inset-bottom));
    background:#0b0d11;
  }
  .viewer::backdrop{background:#0b0d11}
  .viewer-stage{flex:1;height:auto;min-height:0}
  .viewer-close{top:-42px;right:2px;font-size:24px}
  .viewer-nav{width:40px;height:40px;font-size:22px}
  .viewer-thumbs{justify-content:flex-start;padding-bottom:2px}
  .viewer-thumb{width:58px;height:42px}
}

@media (prefers-reduced-motion:reduce){
  .media-play .play-badge{transition:none}
  .viewer-track{scroll-behavior:auto}
  .viewer[open],.viewer[open]::backdrop,
  .viewer.is-closing,.viewer.is-closing::backdrop{animation:none}
}
.media-badge{
  position:relative;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  color:#9a8f7c;border:1px solid var(--dark-line);border-radius:100px;
  padding:8px 15px;background:rgba(31,26,23,.7);text-align:center;
}

.project-kicker{
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--amber);margin-bottom:12px;
}
.project-head h3{
  font-family:var(--display);font-weight:400;
  font-size:clamp(24px,3.4vw,34px);letter-spacing:-.02em;
  color:#f6f1e7;margin-bottom:14px;
}
.project-body > p{color:#a79c8b;font-size:16px;max-width:52ch;text-wrap:pretty}

/* One sourced fact per project, above the tech tags. Amber is the only accent
   that clears AAA on the dark band (see the palette note at the top), and the
   dark band is the only place this class is used. */
.project-proof{
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--amber);margin-top:18px;
}
.tags{display:flex;flex-wrap:wrap;gap:8px;margin-top:22px}
.tags li{
  font-family:var(--mono);font-size:11px;letter-spacing:.06em;
  color:#b8ac98;border:1px solid var(--dark-line);border-radius:100px;
  padding:5px 12px;
}
.project-link{
  display:inline-flex;align-items:center;gap:7px;margin-top:22px;
  font-family:var(--mono);font-size:12.5px;letter-spacing:.05em;color:var(--amber);
  border-bottom:1px solid transparent;padding-bottom:2px;
  transition:border-color .25s var(--ease),gap .25s var(--ease);
}
.project-link:hover{border-bottom-color:var(--amber);gap:11px}

/* ════════════════════════  ABOUT  ════════════════════════ */
.about-grid{
  display:grid;gap:clamp(26px,5vw,54px);
  grid-template-columns:1fr;align-items:center;
}
@media (min-width:820px){ .about-grid{grid-template-columns:290px 1fr} }
/* width+height on the <img> reserve the box, so the text never jumps when the
   portrait finishes loading. */
.about-photo img{
  width:100%;max-width:290px;height:auto;aspect-ratio:1;
  object-fit:cover;border-radius:var(--r-lg);box-shadow:var(--shadow-md);
}
.about-body .section-lead + .section-lead{margin-top:14px}
.about-link{
  display:inline-flex;align-items:center;gap:7px;margin-top:24px;
  font-family:var(--mono);font-size:12.5px;letter-spacing:.05em;
  color:var(--terra-ink);
  border-bottom:1px solid transparent;padding-bottom:2px;
  transition:border-color .25s var(--ease), gap .25s var(--ease);
}
.about-link:hover{border-bottom-color:var(--terra-ink);gap:11px}

/* ════════════════════════  CONTACT  ════════════════════════ */
.contact-grid{
  display:grid;gap:clamp(32px,5vw,64px);
  grid-template-columns:1fr;align-items:start;
}
@media (min-width:900px){ .contact-grid{grid-template-columns:1fr 1fr} }

/* WhatsApp: styled as a site button rather than a green slab so it sits in the
   palette, then adopts WhatsApp green on hover for recognition. */
.contact-wa-line{margin-top:22px;font-size:15px;color:var(--muted)}
.btn-wa{
  margin-top:12px;
  --btn-bg:#fff; --btn-bd:var(--line); --btn-fg:var(--ink);
  font-size:13.5px;letter-spacing:.04em;
}
.btn-wa svg{width:17px;height:17px;color:#25D366;transition:color .25s var(--ease)}
.btn-wa:hover{--btn-bg:#25D366;--btn-fg:#fff;--btn-bd:#25D366}
.btn-wa:hover svg{color:#fff}

/* Booking button. Same shape as .btn-wa so the three contact routes read as one
   set; terracotta instead of WhatsApp green, because this one is ours. */
.contact-cal-line{margin-top:22px;font-size:15px;color:var(--muted)}
.btn-cal{
  margin-top:12px;
  --btn-bg:#fff; --btn-bd:var(--line); --btn-fg:var(--ink);
  font-size:13.5px;letter-spacing:.04em;
}
.btn-cal svg{width:17px;height:17px;color:var(--terra-ink);transition:color .25s var(--ease)}
.btn-cal:hover{--btn-bg:var(--terra-ink);--btn-fg:#fff;--btn-bd:var(--terra-ink)}
.btn-cal:hover svg{color:#fff}

.contact-direct{margin-top:26px;font-size:15px;color:var(--muted)}
.contact-direct a{
  color:var(--terra-ink);border-bottom:1px solid var(--line);
  transition:border-color .2s var(--ease);
}
.contact-direct a:hover{border-bottom-color:var(--terra-ink)}

.contact-form{
  background:var(--card);border:1px solid var(--line);
  border-radius:var(--r-lg);padding:clamp(24px,3.6vw,34px);
  box-shadow:var(--shadow-sm);
}
.field{margin-bottom:18px}
.field label{
  display:block;margin-bottom:7px;
  font-family:var(--mono);font-size:11px;letter-spacing:.11em;
  text-transform:uppercase;color:var(--ink-soft);
}
.field .opt{text-transform:none;letter-spacing:0;color:var(--muted)}
.field input,.field textarea{
  width:100%;
  background:var(--cream);
  border:1px solid var(--line);border-radius:12px;
  padding:13px 15px;font-size:16px;   /* 16px avoids iOS zoom-on-focus */
  transition:border-color .2s var(--ease), background-color .2s var(--ease),
             box-shadow .2s var(--ease);
}
.field textarea{resize:vertical;min-height:112px}
.field input:focus,.field textarea:focus{
  outline:none;background:#fff;border-color:var(--terra);
  box-shadow:0 0 0 3px rgba(194,84,42,.15);
}
.field .error{
  display:none;margin-top:6px;
  font-family:var(--mono);font-size:11px;letter-spacing:.03em;color:var(--terra);
}
.field.is-invalid input,.field.is-invalid textarea{border-color:var(--terra)}
.field.is-invalid .error{display:block}

.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

.form-status{
  margin-top:14px;min-height:20px;
  font-family:var(--mono);font-size:12px;letter-spacing:.03em;text-align:center;
}
.form-status.ok{color:var(--ink)}
.form-status.err{color:var(--terra-ink)}

/* ════════════════════════  FOOTER  ════════════════════════ */
.site-footer{
  border-top:1px solid var(--line);
  background:var(--cream-2);
  padding-block:44px;
}
.footer-inner{
  display:flex;flex-wrap:wrap;align-items:center;gap:14px 28px;
}
.footer-tag{flex:1 1 240px;font-size:14.5px;color:var(--muted)}
.footer-mail{
  font-family:var(--mono);font-size:12.5px;color:var(--terra-ink);
  border-bottom:1px solid transparent;transition:border-color .2s var(--ease);
}
.footer-mail:hover{border-bottom-color:var(--terra-ink)}
.footer-copy{font-family:var(--mono);font-size:11px;letter-spacing:.1em;color:var(--muted)}

/* ════════════════════════  SCROLL REVEAL  ════════════════════════ */
.reveal{
  opacity:0;transform:translateY(26px);
  transition:opacity .75s var(--ease), transform .75s var(--ease);
  transition-delay:var(--d,0ms);
}
.reveal.is-visible{opacity:1;transform:none}

/* ════════════════════════  MOBILE NAV  ════════════════════════ */
@media (max-width:900px){
  .menu-btn{display:flex}
  .nav{
    position:fixed;left:0;right:0;top:68px;
    flex-direction:column;align-items:stretch;gap:0;
    background:var(--cream);
    border-bottom:1px solid var(--line);
    padding:8px var(--gutter) 24px;
    transform:translateY(-14px);opacity:0;visibility:hidden;
    transition:opacity .28s var(--ease), transform .28s var(--ease), visibility .28s;
    box-shadow:var(--shadow-md);
    max-height:calc(100dvh - 68px);overflow-y:auto;
  }
  .nav.is-open{transform:none;opacity:1;visibility:visible}
  .nav > a:not(.btn){
    padding:15px 0;border-bottom:1px solid var(--line);
    font-family:var(--display);font-size:20px;color:var(--ink);
  }
  .nav > a:not(.btn)::after{display:none}
  .nav-cta{margin-top:20px;align-self:flex-start}
  body.nav-open{overflow:hidden}
}

@media (max-width:520px){
  body{font-size:16px}
  .hero-strip{gap:8px 18px}
  .footer-inner{flex-direction:column;align-items:flex-start}
}

/* ════════════════════════  MOTION PREFERENCES  ════════════════════════ */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{
    animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
  }
  .reveal,.hero-anim{opacity:1 !important;transform:none !important}
  .scroll-hint{display:none}
}
