/* ============================================================
   DWELL DESIGNS
   A single-page "desk" of draggable scanned ephemera.
   Static HTML/CSS/JS — no framework, no build step.
   ============================================================ */

/* ============================================================
   DESIGN TOKENS — the single source of truth. See DESIGN.md.
   Every color, size, weight, radius, shadow, and space value on
   the site maps to a token below. Nothing hard-coded off-system.
   ============================================================ */
:root {
  /* ---- Brand color ---- */
  --bg:          #223132;   /* deep forest teal — dark home "desk" canvas */
  --ink:         #0e3d45;   /* primary text, headings, logo, borders */
  --ink-soft:    #2c5b63;   /* secondary text, ledes, labels */
  --ink-faint:   #44676d;   /* tertiary text, placeholders, fine print (AA: 5.5:1 on paper, 4.8:1 on bg-page; the old #5d8389 failed at 3.7/3.2) */
  --accent:      #8ccfd8;   /* light turquoise — selection, hover wash, focus glow */
  --accent-deep: #12606c;   /* dark accent — links, prices, active states (NEVER headings) */
  --paper:       #f5f3e8;   /* base cream — cards, nav, sheets */
  --paper-hi:    #fffdf6;   /* brightest paper — top edges, inputs, photo mats, hovers */
  --line:        rgba(14,61,69,.18);   /* hairline dividers */

  /* ---- Surfaces (replace scattered one-off hex) ---- */
  --bg-page:        #dae4e5;   /* inner-page background */
  --surface-media:  #dce2e0;   /* photo mats / image placeholders */
  --surface-tape:   #e9e1cb;   /* portfolio masking-tape */

  /* ---- Elevation — ONE light source, base rgba(16,40,46) ---- */
  --shadow-sm:   0 1px 2px rgba(16,40,46,.06);
  --shadow-card: 0 1px 1px rgba(16,40,46,.05), 0 18px 40px -30px rgba(16,40,46,.42);
  --shadow-pop:  0 2px 4px rgba(16,40,46,.10), 0 30px 70px -30px rgba(16,40,46,.50);
  --shadow-nav:  0 8px 24px -14px rgba(16,40,46,.30), inset 0 1px 0 rgba(255,255,255,.5);

  /* ---- Radius ----
     Squared-off per client (2026-07-09): surfaces are square, CONTROLS stay
     rounded. Buttons keep their 8px on the dedicated --r-btn token. */
  --r-xs:    0;      /* small chips, tape (was 4px) */
  --r-media: 0;      /* all photos, thumbnails, image frames (was 8px) */
  --r-card:  0;      /* content cards, CTA bands, sheets (was 16px) */
  --r-btn:   8px;    /* buttons ONLY — client keeps these rounded */
  --r-pill: 999px;   /* nav pill, pill buttons, filters */

  /* ---- Space scale (4px grid) ---- */
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px; --space-4: 16px;
  --space-5: 24px; --space-6: 32px; --space-7: 48px; --space-8: 64px; --space-9: 96px;

  /* ---- Type: the NEW brand system (Avenir-Light direction, client 2026-07-09) ----
     Reference: the client's current site — Avenir Light, airy positive tracking,
     uppercase display type. Nunito Sans (variable, self-hosted) is the free
     stand-in: closest Google match to Avenir. Hierarchy is carried by TRACKING
     and case, not weight; the bigger the type, the more air. The OLD system
     (Hanken, bold sentence-case) lives in html.brand-classic below, behind the
     temporary comparison toggle. */
  --font: 'Nunito Sans', system-ui, -apple-system, sans-serif;
  --font-sans: var(--font);   /* legacy alias — every element uses the single brand font */

  /* weights */
  --w-regular: 400; --w-medium: 500; --w-semibold: 600; --w-bold: 700;
  --w-heading: 300;        /* Display/Title/Subtitle + footer wordmark — the Avenir-Light look */
  --w-heading-minor: 400;  /* quieter titles: portfolio-grid project names */
  --w-btn: 500;            /* button labels (600 caps reads too heavy in the light system) */
  /* line-heights */
  --lh-tight: 1.1; --lh-snug: 1.3; --lh-body: 1.6;

  /* type role sizes + tracking + case (see DESIGN.md §2.1)
     Heading tracking pulled in ~50% from the first light-caps pass (client
     2026-07-09: the big titles read too airy) — display .14->.07, title
     .12->.06, subtitle .10->.05. Still positive + graded (bigger = wider),
     just far less gappy. Small eyebrow Labels keep .12em: at 11px, caps NEED
     the air to stay legible. */
  --t-heading-case:  uppercase;
  --t-display-size:  clamp(32px, 5vw, 52px);    --t-display-track: 0.07em;
  --t-title-size:    clamp(21px, 2.4vw, 29px);  --t-title-track:   0.06em;
  --t-subtitle-size: 18px;                      --t-subtitle-track: 0.05em;
  --t-lede-size:     clamp(16px, 1.4vw, 18px);
  --t-body-size:     16px;
  --t-body-sm-size:  14px;
  --t-value-size:    16px;
  --t-fine-size:     13px;
  --t-label-size:    11px;                      --t-label-track:    0.12em;
  /* Buttons + nav share ONE tracking so the small-caps UI stays cohesive.
     Pulled in to 0.04em (from 0.08) to sit UNDER the tightened display titles
     (0.07em) — small-caps UI should never out-track the big headings, or the
     buttons read oversized. Word gaps handled separately by --t-caps-wordgap. */
  --t-button-size:   14px;                      --t-button-track:   0.04em;
  --t-button-case:   uppercase;
  --t-nav-size:      14px;                      /* skip-link + project pop-up pills/links */
  --t-nav:           13px;                      --t-nav-track: 0.04em;
  --t-nav-case:      uppercase;                 /* caps nav per client; weight stays 500 — never thin the nav */
  /* letter-spacing also widens the SPACE characters, so multi-word caps UI text
     ("VIEW FULL PORTFOLIO") read stretched-apart. This pulls the word gaps back
     to normal while the letters keep their tracking. Buttons + nav + small nav
     links only — big headings keep their airy gaps (that IS the reference look). */
  --t-caps-wordgap:  -0.06em;

  /* ---- Media: ONE scanned-paper filter recipe site-wide ---- */
  --photo-filter: brightness(1.03) contrast(.96) saturate(.96);

  /* ---- Desk polaroid "scanned look" knobs (home desk only) ---- */
  --scan-brightness: 1.03;
  --scan-wash:       0.06;
}

/* ---- CLASSIC BRAND (archived revert path — kept on purpose) ----
   The pre-2026-07-09 system: Hanken Grotesk, bold sentence-case headings,
   sentence-case nav/buttons. Nothing sets .brand-classic anymore (the
   comparison toggle was removed 2026-07-09 when the client locked the new
   direction), so this block is inert dead weight by design. TO REVERT the
   whole site to the old look: copy these token values over the matching ones
   in :root above (and swap the <link rel="preload"> in each page's <head>
   back to hanken-grotesk-var.woff2). Hanken's files + @font-face stay in
   assets/fonts/ — unreferenced fonts cost visitors zero bytes. */
html.brand-classic {
  --font: 'Hanken Grotesk', system-ui, -apple-system, sans-serif;
  --w-heading: 700; --w-heading-minor: 600; --w-btn: 600;
  --t-heading-case: none;
  --t-display-size:  clamp(34px, 5.5vw, 56px);  --t-display-track: -0.02em;
  --t-title-size:    clamp(23px, 2.6vw, 32px);  --t-title-track:   -0.015em;
  --t-subtitle-size: 20px;                      --t-subtitle-track: -0.01em;
  --t-label-track: 0.04em;
  --t-button-size: 15px; --t-button-track: 0; --t-button-case: none;
  --t-nav: 15px; --t-nav-track: 0; --t-nav-case: none;
  --t-caps-wordgap: 0;
}

/* ---- Type role utility classes (apply the system, don't re-declare sizes) ---- */
.t-display  { font-family: var(--font); font-weight: var(--w-heading); font-size: var(--t-display-size); letter-spacing: var(--t-display-track); line-height: var(--lh-tight); color: var(--ink); text-transform: var(--t-heading-case, none); }
.t-title    { font-family: var(--font); font-weight: var(--w-heading); font-size: var(--t-title-size); letter-spacing: var(--t-title-track); line-height: var(--lh-tight); color: var(--ink); text-transform: var(--t-heading-case, none); }
.t-subtitle { font-family: var(--font); font-weight: var(--w-heading); font-size: var(--t-subtitle-size); letter-spacing: var(--t-subtitle-track); line-height: var(--lh-snug); color: var(--ink); text-transform: var(--t-heading-case, none); }
.t-lede     { font-family: var(--font); font-weight: var(--w-regular); font-size: var(--t-lede-size); line-height: 1.55; color: var(--ink-soft); }
.t-body     { font-family: var(--font); font-weight: var(--w-regular); font-size: var(--t-body-size); line-height: var(--lh-body); color: var(--ink); }
.t-body-sm  { font-family: var(--font); font-weight: var(--w-regular); font-size: var(--t-body-sm-size); line-height: 1.55; color: var(--ink-soft); }
.t-value    { font-family: var(--font); font-weight: var(--w-semibold); font-size: var(--t-value-size); line-height: var(--lh-snug); color: var(--ink); }
.t-fine     { font-family: var(--font); font-weight: var(--w-regular); font-size: var(--t-fine-size); line-height: 1.5; color: var(--ink-faint); }
.t-label    { font-family: var(--font); font-weight: var(--w-semibold); font-size: var(--t-label-size); letter-spacing: var(--t-label-track); line-height: 1.2; text-transform: uppercase; color: var(--ink-soft); }
.t-label.accent { color: var(--accent-deep); }

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
::selection { background: var(--accent); color: var(--ink); }
/* touch-action: manipulation removes the double-tap-to-zoom gesture (and the 300ms
   tap delay) site-wide. Pinch-zoom is blocked separately in JS (iOS ignores
   user-scalable=no). */
html, body { margin: 0; height: 100%; overflow-x: hidden; touch-action: manipulation; }
/* The home "desk" is one fixed screen that never scrolls, so it reserves NO
   scrollbar gutter — the background texture then fills the whole viewport edge
   to edge with no solid strip where a scrollbar would sit. The scrolling inner
   pages reserve a stable gutter instead (see portfolio.css), so the centered
   glassnav still doesn't shift between the two. */

body {
  font-family: var(--font-sans);
  font-weight: 400;
  line-height: 1.55;
  color: var(--ink);
  background: var(--bg);
  overflow: hidden;
  position: relative;
}
/* whisper-soft warmth so the pieces feel placed, not floating */
body::after {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background: radial-gradient(130% 100% at 50% 38%, transparent 64%, rgba(8,40,46,.05) 100%);
}

/* ---------------- Header ---------------- */
/* header wrapper imposes no layout — JS positions the logo + nav on the stage */
.topbar { display: contents; }

/* ---------------- Floating glass nav — Island (centered compact pill) ----------------
   Auto-width pill, centered and floating. Glass kept; shadow softened to a faint
   ambient lift so the desk pieces stay the star. Brand sits inline, same size as the
   links (just bold). No transitions/animations — hover states change instantly. */
.glassnav {
  position: fixed; z-index: 60;
  top: calc(clamp(14px, 1.8vh, 22px) + env(safe-area-inset-top, 0px));
  left: 50vw; transform: translateX(-50%);   /* 50vw (not 50%) → centres on the FULL viewport incl. scrollbar, so the pill sits in the exact same place on the no-scrollbar home page and the scrolling inner pages */
  --nav-gap: 20px;                 /* ← spacing between every nav item; adjust this one number */
  display: flex; align-items: center; gap: var(--nav-gap);
  padding: 9px 12px 9px 32px;
  max-width: calc(100% - 2 * clamp(16px, 3vw, 44px));
  /* "Paper Strip" — the nav reads as a piece of the studio's warm stationery:
     a solid cream pill with deep-teal ink. Works on the dark home desk and the
     light inner pages alike (portfolio.css tunes the shadow for the light bg). */
  background: linear-gradient(180deg, var(--paper-hi) 0%, var(--paper) 100%);
  border: 1px solid rgba(14,61,69,.08);
  border-radius: 999px;
  box-shadow: 0 10px 28px -16px rgba(0,0,0,.5), 0 2px 5px -3px rgba(0,0,0,.28), inset 0 1px 0 rgba(255,255,255,.55);
}

.gn-brand {
  font-family: var(--font); font-weight: var(--w-medium); font-size: var(--t-nav);
  letter-spacing: var(--t-nav-track, 0); text-transform: var(--t-nav-case, none);
  word-spacing: var(--t-caps-wordgap, 0);
  color: var(--ink); text-decoration: none; white-space: nowrap; cursor: pointer;
  opacity: 1; transition: opacity .25s ease;   /* spotlight-dim: on base so it eases IN and OUT the same */
}
/* divider is spaced by the shared nav gap like every other item */
.gn-div { width: 1px; height: 18px; background: var(--line); flex: 0 0 auto; }

.gn-link {
  font-family: var(--font); font-weight: var(--w-medium); font-size: var(--t-nav);
  letter-spacing: var(--t-nav-track, 0); text-transform: var(--t-nav-case, none);
  word-spacing: var(--t-caps-wordgap, 0);
  color: var(--ink); text-decoration: none; white-space: nowrap;
  opacity: 1; transition: opacity .25s ease;   /* spotlight-dim: on base so it eases IN and OUT the same */
}

/* Spotlight-dim — the brand + links form ONE group (.gn-group). Hovering any member
   dims the OTHERS to .55; the hovered one stays full. The "Book a call" CTA is OUTSIDE
   .gn-group, so it never dims and never triggers a dim. .gn-group is display:contents on
   mobile (see the ≤768 block) so the burger-row order rules on .gn-brand keep working. */
.gn-group { display: flex; align-items: center; gap: var(--nav-gap); }
/* POINTER GATE: the dim only applies to real-pointer devices (mouse/trackpad), gated by
   INPUT TYPE not width — so a touch tablet in a wide viewport never gets sticky hover
   (tap-dims-and-stays) and a laptop in a narrow window keeps its hover. The base
   `transition: opacity` above stays ungated so the ease is symmetric in AND out. */
@media (hover: hover) and (pointer: fine) {
  .gn-group:hover .gn-brand,
  .gn-group:hover .gn-link { opacity: .55; }
  .gn-group:hover .gn-brand:hover,
  .gn-group:hover .gn-link:hover { opacity: 1; }
}

.gn-cta {
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--font); font-weight: var(--w-semibold); font-size: var(--t-nav);
  letter-spacing: var(--t-nav-track, 0); text-transform: var(--t-nav-case, none);
  word-spacing: var(--t-caps-wordgap, 0);
  color: var(--paper); background: var(--ink); cursor: pointer; white-space: nowrap; text-decoration: none;
  border: 1px solid var(--ink); border-radius: 999px; padding: 8px 18px;
  box-shadow: inset 0 1px 0 rgba(245,243,232,.16);
}
/* phone icon that sits to the left of a "Book a call" label (nav CTA + solid buttons) */
.gn-cta .ph { width: 14px; height: 14px; }
.btn .ph { width: 16px; height: 16px; }
.gn-cta .ph, .btn .ph, .gn-mlink .ph {
  fill: currentColor; stroke: none; flex: none;
}
.gn-mlink--call { display: flex; align-items: center; gap: 8px; }
.gn-mlink--call .ph { width: 16px; height: 16px; }
@media (hover: hover) and (pointer: fine) { .gn-cta:hover { background: var(--accent-deep); border-color: var(--accent-deep); color: var(--paper); } }

.glassnav a:focus-visible, .gn-cta:focus-visible, .gn-burger:focus-visible {
  outline: 2px solid var(--accent-deep); outline-offset: 3px; border-radius: 999px;
}

/* Burger + dropdown appearance defined unconditionally but hidden by default;
   the collapse media queries below only flip `display` on. This lets the same
   collapsed pill serve BOTH the mobile tier (≤768) and the opt-in tablet-burger
   variant (769–1024, body[data-tablet-nav="burger"]) with no duplication. */
.gn-burger {
  display: none; flex-direction: column; justify-content: center; gap: 5px;
  width: 40px; height: 40px; padding: 0 10px; background: transparent; border: 0; cursor: pointer;
}
.gn-burger span { display: block; height: 2px; width: 20px; background: var(--ink); border-radius: 999px; }
.gn-burger[aria-expanded="true"] span:first-child { transform: translateY(3.5px) rotate(45deg); }
.gn-burger[aria-expanded="true"] span:last-child  { transform: translateY(-3.5px) rotate(-45deg); }

/* Burger dropdown — reads as an extension of the paper-strip nav: same solid
   cream gradient, border and ambient shadow (no glassmorphism). Width + insets
   match the strip so it sits directly beneath it. */
.gn-mobile {
  display: none; position: fixed; z-index: 59;
  top: calc(clamp(14px, 1.8vh, 22px) + env(safe-area-inset-top, 0px) + 54px);
  left: 50vw; transform: translateX(-50%);   /* 50vw (not 50%) → centres on the FULL viewport incl. scrollbar, so the menu sits in the exact same place on the no-scrollbar home page and the scrolling inner pages */
  width: min(264px, calc(100% - 32px));   /* compact menu, centred under the compact pill */
  gap: 2px; padding: 7px;
  background: linear-gradient(180deg, var(--paper-hi) 0%, var(--paper) 100%);
  border: 1px solid rgba(14,61,69,.08); border-radius: 18px;
  box-shadow: 0 18px 40px -18px rgba(16,40,46,.5), 0 3px 8px -4px rgba(16,40,46,.22), inset 0 1px 0 rgba(255,255,255,.6);
}
.gn-mobile[hidden] { display: none; }
/* menu rows match the persistent nav text (the "Dwell Designs" brand): 16px, --ink.
   On hover/tap they bold to 600 — no background wash. */
.gn-mlink { display: block; font-family: var(--font-sans); font-weight: 500; font-size: 16px;
  letter-spacing: var(--t-nav-track, .005em); text-transform: var(--t-nav-case, none);
  word-spacing: var(--t-caps-wordgap, 0);
  color: var(--ink); text-decoration: none; padding: 12px 14px; border-radius: 11px;
  background: none; border: 0; text-align: left; cursor: pointer; transition: color .12s ease; }
/* visible keyboard focus ring on the mobile menu rows (DESIGN.md §4.1: never strip
   :focus-visible). Inset so it hugs the row and doesn't overlap the rows above/below. */
.gn-mlink:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: -2px; }

@media (prefers-reduced-motion: reduce) {
  .topbar, .gn-mobile { transition: none !important; }
}

/* ---- TABLET (769–1024): full links by default, just tightened so the pill
   never risks wrapping on a portrait iPad. The dev toggle can opt this range
   into the burger via <body data-tablet-nav="burger">. ---- */
@media (min-width: 769px) and (max-width: 1024px) {
  .glassnav { --nav-gap: 15px; padding: 8px 12px 8px 24px; }
  body[data-tablet-nav="burger"] .glassnav { gap: 12px; padding: 7px 8px 7px 18px; }
  body[data-tablet-nav="burger"] .gn-div,
  body[data-tablet-nav="burger"] .gn-link,
  body[data-tablet-nav="burger"] .gn-cta { display: none; }
  body[data-tablet-nav="burger"] .gn-burger { display: flex; }
  body[data-tablet-nav="burger"] .gn-mobile:not([hidden]) { display: grid; }
}

/* ---- MOBILE (≤768): ONE morphing paper surface (Apple "Liquid Glass" style).
   Closed = a compact pill (burger · wordmark · Book-a-call). Open = the same
   surface grows downward into a rounded sheet holding the menu — never a separate
   card. The paper fill lives on .topbar::before (an ABSOLUTE child), so iOS 26
   Safari never samples the fixed nav to tint the status/toolbar bars. ---- */
@media (max-width: 768px) {
  /* the shell — fixed, but carries NO background-color itself (sampling-safe) */
  .topbar {
    display: block; position: fixed; z-index: 60;
    top: calc(clamp(14px, 1.8vh, 22px) + env(safe-area-inset-top, 0px) - 6px);
    left: 50vw; transform: translateX(-50%);
    /* full-width bar: burger + brand stay a tight group on the left, the CTA locks
       to the right (see .gn-cta margin-left:auto); only the gap between them grows */
    width: calc(100% - 32px); max-width: calc(100% - 32px);
    border: 1px solid rgba(14,61,69,.08);
    /* CONSTANT radius = half the CLOSED bar height (7+44+7 padding/CTA + 2px border
       = 60px tall → 30px), so the closed bar reads as a true stadium pill like every
       other breakpoint, and the open menu is a rounded sheet. Never animated — the
       pill top stays intact and the menu just slides out the bottom, no corner
       morphing. If the closed height changes, re-derive this. */
    border-radius: 30px;
    box-shadow: 0 10px 28px -16px rgba(0,0,0,.5), 0 2px 5px -3px rgba(0,0,0,.28);
    overflow: hidden;
  }
  .topbar::before {   /* the paper surface — absolute, so it is NOT sampled by iOS.
       Gradient settles to --paper within the pill's height so the whole surface reads
       as the exact desktop cream, not a bright white. */
    content: ""; position: absolute; inset: 0; z-index: 0; border-radius: inherit;
    background: linear-gradient(180deg, var(--paper-hi) 0%, var(--paper) 44px);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.55);
  }

  /* nav row — now transparent; the surface belongs to the shell.
     position:relative (not static) so z-index lifts it above .topbar::before. */
  .glassnav {
    position: relative; transform: none; inset: auto; z-index: 1;
    width: auto; max-width: none;
    background: none; border: 0; box-shadow: none;
    gap: 15px; padding: 7px 8px 7px 16px;
  }
  /* dissolve the desktop group wrapper so brand/burger/CTA order rules below apply
     directly in the .glassnav flex row (as they did before .gn-group existed) */
  .gn-group { display: contents; }
  .gn-div, .gn-link { display: none; }

  /* burger — box hugs its icon so its gaps are even; ::before is the tap target (kept
     ≥44px per DESIGN.md §5: 18+2*13 = 44 wide, 40+2*8 = 56 tall).
     gap:6px makes the icon 10px tall (2 + 6 + 2) = the brand's cap height at 14px caps, so
     the top bar aligns with the cap-top and the bottom bar with the baseline of the wordmark. */
  .gn-burger { display: flex; order: 1; align-items: center; position: relative; width: 18px; height: 40px; padding: 0; gap: 6px; }
  .gn-burger span { width: 18px; }
  .gn-burger::before { content: ""; position: absolute; inset: -8px -13px; }
  /* X-morph offset tracks the 6px gap (gap/2 + thickness/2 = 4px) so the bars meet cleanly */
  .gn-burger[aria-expanded="true"] span:first-child { transform: translateY(4px) rotate(45deg); }
  .gn-burger[aria-expanded="true"] span:last-child  { transform: translateY(-4px) rotate(-45deg); }

  .gn-brand { order: 2; font-size: 14px; font-weight: 600; color: var(--ink); }

  /* margin-left:auto absorbs all the extra width, locking the CTA to the right edge
     while the burger + brand stay a tight group on the left */
  /* min-height 44px = a full touch tap target (DESIGN.md §5); base align-items:center
     keeps the label centred as the pill grows from ~30px to 44px. */
  .gn-cta { display: inline-flex; order: 3; margin-left: auto; min-height: 44px; font-size: 13px; font-weight: 500; padding: 7px 14px; }
  .gn-cta .ph { width: 13px; height: 13px; }

  /* menu — part of the SAME surface; revealed by growing height, no separate card */
  .gn-mobile {
    position: relative; transform: none; inset: auto; z-index: 1;
    width: auto; max-width: none; background: none; border: 0; box-shadow: none; border-radius: 0;
    display: block; overflow: hidden;
    max-height: 0; opacity: 0; padding: 0 8px;
    transition: max-height .28s cubic-bezier(.22, 1, .36, 1), opacity .2s ease, padding .28s ease;
  }
  .gn-mobile[hidden] { display: none; }            /* fully out of layout when closed */
  .topbar.nav-open .gn-mobile { max-height: 240px; opacity: 1; padding: 3px 8px 8px; }
  /* vertical padding 11px → 8.8px = 20% less vertical spacing between the rows */
  .gn-mlink { padding: 8.8px 12px; border-radius: 12px; font-size: 15px; }
}

/* ---------------- The desk + pieces ---------------- */
.desk { position: fixed; inset: 0; z-index: 10; }

.piece {
  position: absolute; top: 0; left: 0;
  margin: 0; padding: 0; border: 0; background: none;
  transform: translate3d(0,0,0) rotate(0deg);
  will-change: transform, filter; touch-action: none; cursor: grab;
  -webkit-user-select: none; user-select: none;
  opacity: 0; transition: filter .18s ease-out, opacity .3s ease;
}
/* reveal once JS has run its first layout (prevents the corner-stacked flash) */
body.ready .logo, body.ready .navlink, body.ready .piece { opacity: 1; }
.piece:active { cursor: grabbing; }
.piece img {
  display: block; width: 100%; height: auto;
  pointer-events: none; user-select: none; -webkit-user-drag: none;
}
/* Scanned look — brightness + wash-out. Driven by the two knobs in :root
   (--scan-brightness, --scan-wash). Applied to the image only, so the piece's
   drop-shadows (on the parent .piece) stay dark and grounded. */
.piece img {
  filter:
    brightness(var(--scan-brightness))
    contrast(calc(1 - var(--scan-wash)))
    saturate(calc(1 - var(--scan-wash)));
}
/* Polaroids get a lighter touch than the rest of the desk. */
.piece[data-polaroid] { --scan-brightness: 1.02; --scan-wash: 0.04; }

/* Business card — a touch more washed-out (lower contrast + saturation) than the rest. */
.piece[data-piece="contact"] { --scan-wash: 0.10; }

.piece:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: 6px; border-radius: 2px; }

/* ---- paper shadows ----
   Directional, like a real light source up and to the LEFT: a tight contact
   line at the edge, plus one short shadow cast DOWN and slightly RIGHT. Offsets
   are larger than the blur so the shadow sits to one side instead of haloing
   all the way around. Thicker stock casts a little farther (postcard, matchbook).
   drop-shadow() follows each scan's true silhouette (incl. torn edges). */

/* one baseline shadow shared by every piece (matchbook/postcard overrides removed) */
.piece {
  filter:
    drop-shadow(1px 1.5px 1px rgba(16,40,46,.34))
    drop-shadow(2.5px 4px 3.5px rgba(16,40,46,.19));
}

/* placeholder pieces — hide the old art, show a solid business-card-coloured card
   so the slot reads as a real card while the final art is chosen */
/* keep the same tight contact-shadow filter the real pieces use, so the card
   sits flat on the desk instead of floating */
.piece[data-placeholder] img { opacity: 0; }
.piece[data-placeholder]::after {
  content: attr(data-label);
  position: absolute; inset: 0;
  display: grid; place-items: center; text-align: center; padding: 6px;
  border-radius: var(--r-media);
  background: #f1e4c0;                              /* aged-cream card stock — warmer/yellower so it reads apart from the paper behind it */
  color: var(--ink);
  /* no box-shadow here — the contact shadow now comes from the shared .piece
     drop-shadow filter, matching every other piece on the desk */
  font-family: var(--font-sans); font-weight: 600; font-size: 12.5px;
  letter-spacing: .06em;
}

/* piece widths are computed in dwell.js from each piece's data-w as a
   fraction of the scaling scene, so the whole arrangement keeps the
   reference proportions at any window size. */

/* ===================== OVERLAYS ===================== */
/* Each pop-up reads like a printed paper card lifted onto the desk: warm
   stock, a hairline printed edge, light raking from the upper-left (matching
   the .piece shadows), and a gentle "place-down" entrance. */
.overlay {
  position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 28px;
  opacity: 0; visibility: hidden;
  transition: opacity .28s ease, visibility 0s linear .28s;
}
/* the dark backdrop lives on an ABSOLUTE child, never on the fixed .overlay
   itself — otherwise iOS 26 Safari samples this (closed but only visibility:hidden)
   overlay and tints the status/toolbar bars ("Liquid Glass" bug). Same bug:
   the veil color is only PAINTED while the overlay is .open — a closed overlay's
   veil is still sampled by the chrome tinter even at visibility:hidden. */
.overlay::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: transparent;
  transition: background-color .28s ease;
}
.overlay.open::before {
  background: rgba(6,26,30,.40);   /* darken only — no backdrop blur (it janked/lagged on open) */
}
.overlay.open { opacity: 1; visibility: visible; transition: opacity .28s ease, visibility 0s linear 0s; }
/* briefly swallow input right after opening so the opening tap can't leak
   through onto the backdrop (closing it) or an input (popping the keyboard) */
.overlay.opening { pointer-events: none; }

.sheet {
  position: relative; z-index: 1; width: min(560px, 94%); max-height: 86vh; overflow: auto;
  color: var(--ink);
  /* warm paper stock: light catching the upper-left corner, settling deeper
     toward the lower-right, over the base paper tone */
  background-color: var(--paper);
  background-image:
    radial-gradient(135% 95% at 18% 6%, var(--paper-hi), rgba(255,255,255,0) 56%);
  padding: 34px 48px 44px; border-radius: var(--r-card);
  border: 1px solid rgba(14,61,69,.22);
  /* tight contact line + one short cast down-right, like the desk pieces;
     plus a faint highlight along the top edge so the card reads as lifted */
  box-shadow: inset 0 1px 0 rgba(255,255,255,.55), var(--shadow-pop);
  /* "Bloom": the card scales up from center with a soft fade, no tilt or
     travel — it simply resolves into place. ease-out-expo settle. */
  transform: scale(.93);
  opacity: 0;
  transition: transform .5s cubic-bezier(.16,1,.3,1), opacity .34s ease;
}
.overlay.open .sheet { transform: none; opacity: 1; }
.sheet:focus, .sheet:focus-visible { outline: none; }

.sheet::-webkit-scrollbar { width: 8px; }
.sheet::-webkit-scrollbar-thumb { background: var(--line); border-radius: 8px; }

.sheet .x {
  position: absolute; top: 16px; right: 16px; width: 44px; height: 44px; padding: 0;
  border: 1.5px solid var(--ink); background: var(--paper); border-radius: 50%;
  font-size: 0; color: transparent; cursor: pointer; z-index: 3;
  transition: background .15s; outline: none; -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;   /* whole 44x44 circle is a reliable tap target */
}
/* the ✕ is two lines, each centred at the button's exact middle and rotated
   about that point — centred reliably on every browser (no grid/flex/glyph) */
.sheet .x::before, .sheet .x::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 16px; height: 1.7px; background: var(--ink); border-radius: 1px;
}
.sheet .x::before { transform: translate(-50%, -50%) rotate(45deg); }
.sheet .x::after  { transform: translate(-50%, -50%) rotate(-45deg); }
/* hover / keyboard focus: brighten to a SOLID paper fill (never translucent) so the
   button stays clean over a photo on the project/about pop-ups, not janky. */
.sheet .x:focus-visible { background: var(--paper-hi); }
@media (hover: hover) and (pointer: fine) { .sheet .x:hover { background: var(--paper-hi); } }

.sheet-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 18px; margin-bottom: 26px;
}
.sheet-head h2 { flex: 1; }
/* the close button rides in the header row, so it lines up with the title's
   first line at any size (instead of floating in the corner) */
.sheet-head .x { position: relative; top: auto; right: auto; flex: none; margin-top: -2px; }
/* Label role — small uppercase eyebrows/mini-labels (wider +0.04em tracking
   than the big uppercase headings). See DESIGN.md §2.1. */
.eyebrow {
  font-family: var(--font); font-weight: var(--w-semibold);
  font-size: var(--t-label-size); letter-spacing: var(--t-label-track); line-height: 1.2; text-transform: uppercase;
  color: var(--ink-soft); margin-bottom: var(--space-3);
}
.eyebrow.accent, .accent { color: var(--accent-deep); }
/* pop-up title — Title role (uppercase via the heading-case token; never
   accent-colored, that's reserved for links). */
.sheet h2 {
  margin: 0; font-family: var(--font); font-weight: var(--w-heading);
  font-size: var(--t-title-size); line-height: var(--lh-tight); letter-spacing: var(--t-title-track);
  text-transform: var(--t-heading-case, none); color: var(--ink);
}
.sheet p { font-size: var(--t-body-size); font-weight: var(--w-regular); line-height: var(--lh-body); color: var(--ink-soft); max-width: 65ch; }
.sheet .lede { margin: 14px 0 0; font-size: var(--t-lede-size); color: var(--ink); }
.sheet a { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* skip-to-content: clip-hidden until keyboard-focused, then a paper chip above
   the nav. Hidden state carries NO background-color and no off-screen transform:
   iOS 26 Safari samples background-color from fixed elements near the top edge
   (even invisible ones) to tint its chrome, so the cream must only exist on focus. */
.skip-link {
  position: fixed; left: 16px; top: 12px; z-index: 10000;
  width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  font-family: var(--font); font-weight: var(--w-semibold); font-size: var(--t-nav-size);
  color: var(--ink); text-decoration: none;
}
.skip-link:focus-visible {
  width: auto; height: auto; overflow: visible; clip-path: none;
  background: var(--paper-hi);
  padding: 10px 16px; border-radius: var(--r-btn);
  border: 1px solid rgba(14,61,69,.25); box-shadow: var(--shadow-card);
  outline: 2px solid var(--accent-deep); outline-offset: 2px;
}

/* ===================== Shared CTA buttons =====================
   One system everywhere except the nav pill: solid ink primary + cream "paper"
   secondary, 8px corners, colour-only hover (no lift). */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  font-family: var(--font); font-size: var(--t-button-size); font-weight: var(--w-btn); line-height: 16px;
  letter-spacing: var(--t-button-track, 0); text-transform: var(--t-button-case, none);
  word-spacing: var(--t-caps-wordgap, 0);
  border: 1px solid transparent; border-radius: var(--r-btn); padding: 13px 22px; margin: 0;
  -webkit-appearance: none; appearance: none; vertical-align: middle;
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: background .13s ease, color .13s ease, border-color .13s ease, transform .12s ease;
}
.btn .arw { width: 15px; height: 15px; stroke: currentColor; stroke-width: 1.8; fill: none; flex: none; }
/* .btn.btn--* specificity (and a.btn) beats the base .sheet a { color; underline } rule */
a.btn, a.btn:hover { text-decoration: none; }
.btn.btn--solid { background: var(--ink); color: var(--paper); border-color: var(--ink); }
@media (hover: hover) and (pointer: fine) { .btn.btn--solid:hover { background: var(--accent-deep); border-color: var(--accent-deep); color: var(--paper); } }
/* secondary CTA = ghost button: transparent, fills warm-white on hover.
   1px border + global box-sizing:border-box keeps it the exact same height as .btn--solid. */
.btn.btn--paper { background: transparent; color: var(--ink); border-color: rgba(14,61,69,.35); font-weight: 500; }
@media (hover: hover) and (pointer: fine) { .btn.btn--paper:hover { background: var(--paper-hi); border-color: var(--ink); color: var(--ink); } }
.btn:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: 2px; }
/* TOUCH PRESS FEEDBACK — :active fires on touch AND pointer, so tapping a primary
   action still feels responsive now that hover affordances are gated to fine pointers.
   A subtle push-in; not gated (must work on touch). Buttons/CTAs where it matters. */
.btn:active, .gn-cta:active, .pj-foot .btn-pill:active,
.sheet .x:active { transform: scale(.97); }
/* .pj-arr is vertically centred with translateY(-50%) — COMPOSE the press scale with
   it (don't overwrite, or the arrow jumps down half its height on press). */
.pj-arr:active { transform: translateY(-50%) scale(.97); }

/* ===================== Project preview card (large gallery pop-up) =====================
   Same warm-paper sheet, opened to ~82%: big image on the left, filled thumbnail
   grid on the right, a story-style progress pill, and site-matched actions. */
.sheet--project {
  width: min(1120px, 95vw); max-width: none;
  height: min(620px, 90vh); max-height: 90vh;
  padding: 0; overflow: hidden; border-radius: var(--r-media);
  display: flex; flex-direction: row;
  /* paper edge: soft shadow only, NO border and NO white inset rim */
  border: 0;
  box-shadow: var(--shadow-pop);
}
.sheet--project .x { top: 20px; right: 20px; z-index: 8; }

/* media (left) — rounds its OWN left corners to match the sheet and rides on its
   own compositor layer (translateZ), so Safari/Chrome never leave a faint cream
   arc where the sheet's rounded corner clips the square photo */
.pj-media { position: relative; flex: 1.5; min-width: 0; background: #dce2e0; overflow: hidden;
  border-radius: var(--r-media) 0 0 var(--r-media); transform: translateZ(0); }
/* crossfade + slow settle-in scale (Apple / Google Photos "Memories" feel) */
.pj-slide {
  position: absolute; inset: 0; opacity: 0; z-index: 0;
  transform: scale(1.045); transform-origin: center;
  transition: opacity .7s ease, transform 4600ms cubic-bezier(.22,1,.36,1);
}
.pj-slide.on { opacity: 1; z-index: 1; transform: scale(1); }
.pj-slide img { width: 100%; height: 100%; object-fit: cover; display: block; }

.pj-arr {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 4;
  width: 42px; height: 42px; padding: 0; border-radius: 50%; cursor: pointer;
  background: rgba(245,243,232,.86); border: 1px solid rgba(14,61,69,.14);
  box-shadow: 0 3px 10px -4px rgba(16,40,46,.5);
  display: flex; align-items: center; justify-content: center;
}
@media (hover: hover) and (pointer: fine) { .pj-arr:hover { background: var(--paper-hi); } }
.pj-arr i { display: block; width: 9px; height: 9px; border-right: 1.7px solid var(--ink); border-bottom: 1.7px solid var(--ink); }
.pj-prev { left: 14px; } .pj-prev i { transform: rotate(135deg); margin-left: 3px; }
.pj-next { right: 14px; } .pj-next i { transform: rotate(-45deg); margin-right: 3px; }

/* position dots with a filling active pill, on a dark blurred capsule so it reads
   over any photo (Instagram-stories pattern). Active pill fills over the photo's time. */
.pj-progress {
  position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 5;
  display: flex; align-items: center; gap: 7px; max-width: calc(100% - 32px);
  padding: 9px 13px; border-radius: 999px; flex-wrap: nowrap; justify-content: center;
  /* solid translucent capsule (no backdrop blur — the blur repaints every frame
     over the moving photo and janks the fill) */
  background: rgba(8,24,28,.58);
  box-shadow: 0 2px 10px -4px rgba(0,0,0,.4);
}
.pj-dot {
  position: relative; width: 6px; height: 6px; border-radius: 999px; background: rgba(255,255,255,.42);
  overflow: hidden; flex: none; transition: width .62s cubic-bezier(.22,1,.36,1), background .5s ease;
}
.pj-dot.on { width: 30px; background: rgba(255,255,255,.34); }
.pj-dfill { position: absolute; inset: 0; background: #fff; border-radius: 999px; transform: scaleX(0); transform-origin: left center; }

/* side (right) */
.pj-side { flex: 1; min-width: 0; display: flex; flex-direction: column; padding: 28px 28px 26px; border-left: 1px solid var(--line); }
.sheet--project .pj-title {
  margin: 0; font-family: var(--font); font-weight: var(--w-heading); text-transform: var(--t-heading-case, none);
  font-size: var(--t-title-size); line-height: var(--lh-tight); letter-spacing: var(--t-title-track); color: var(--ink);
}
.sheet--project .pj-sub { margin: 8px 0 0; font-size: 15px; line-height: 1.55; color: var(--ink-soft); max-width: 44ch; }

.pj-grid {
  /* padding on every side gives the selected thumb's outline room inside the scroll
     clip box; negative margins keep the thumbnails flush where they were */
  margin: 13px -5px 0; flex: 1; min-height: 0; overflow-y: auto; padding: 5px;
  display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: min-content; gap: 8px; align-content: start;
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
.pj-grid::-webkit-scrollbar { width: 6px; }
.pj-grid::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; }
.pj-thumb { aspect-ratio: 3/2; border-radius: var(--r-media); overflow: hidden; padding: 0; border: 0; cursor: pointer; background: #dce2e0; outline: 1.5px solid transparent; outline-offset: 0; opacity: .55; }
.pj-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pj-thumb.on { opacity: 1; outline-color: var(--accent-deep); }
@media (hover: hover) and (pointer: fine) { .pj-thumb:hover { opacity: .85; } }

.pj-foot { margin-top: 20px; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.pj-foot .btn-pill {
  font-family: var(--font); font-weight: var(--w-medium); font-size: var(--t-nav-size);
  letter-spacing: var(--t-button-track, 0); text-transform: var(--t-button-case, none);
  word-spacing: var(--t-caps-wordgap, 0); color: var(--ink-soft);
  background: transparent; border: 1px solid rgba(14,61,69,.35); border-radius: var(--r-pill); padding: 9px 20px; cursor: pointer; white-space: nowrap;
}
@media (hover: hover) and (pointer: fine) { .pj-foot .btn-pill:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); } }
.pj-foot .pj-link {
  font-family: var(--font); font-weight: var(--w-medium); font-size: var(--t-nav-size); color: var(--ink-soft);
  background: none; border: 0; border-bottom: 1px solid var(--line); padding: 0 0 2px; cursor: pointer; white-space: nowrap; text-decoration: none;
}
@media (hover: hover) and (pointer: fine) { .pj-foot .pj-link:hover { color: var(--ink); border-color: var(--ink); } }

@media (prefers-reduced-motion: reduce) {
  .pj-slide { transition: none; }
}

/* ===================== About pop-up (Option A) =====================
   Same warm paper as the project card: framed portrait on the left, the
   about copy on the right. No border, no divider lines. */
.sheet--about {
  width: min(1000px, 95vw); max-width: none;
  height: auto; max-height: 90vh;   /* grow to fit the bio; cap + scroll on short screens */
  padding: 0; overflow: hidden;
  display: flex; flex-direction: row;
  border: 0;
  box-shadow: var(--shadow-pop);
}
.sheet--about .x { top: 20px; right: 20px; z-index: 8; }

/* full-bleed portrait: flush to the card's left / top / bottom edges (no gap or
   seam), cropped to fill its column so it always reads as a clean solid panel */
.ab-plate { flex: 0 0 42%; align-self: stretch; min-width: 0; overflow: hidden;
  /* own left-corner radius on its own layer — same anti-seam trick as .pj-media */
  border-radius: var(--r-card) 0 0 var(--r-card); transform: translateZ(0); }
.ab-plate img {
  display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 30%;
}

.ab-about { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; justify-content: flex-start; overflow-y: auto; padding: 44px 46px 40px; }
.sheet--about .ab-name { margin: 0; font-family: var(--font); font-weight: var(--w-heading); text-transform: var(--t-heading-case, none);
  font-size: var(--t-title-size); line-height: var(--lh-tight); letter-spacing: var(--t-title-track); color: var(--ink); }
/* Role + bio share ONE cohesive pop-up subtext size (15px) with the project +
   contact pop-ups. Scoped to .sheet--about so they beat the generic `.sheet p`
   rule (which would otherwise blow the eyebrow up with too-wide tracking).
   Applies on desktop AND mobile (no breakpoint override touches these). */
.sheet--about .ab-role { margin: 8px 0 0; font-size: 15px; font-weight: var(--w-semibold); letter-spacing: normal; color: var(--ink-soft); }
.sheet--about .ab-bio { margin: 16px 0 0; font-size: 15px; line-height: 1.55; color: var(--ink); max-width: 62ch; }
.ab-bio + .ab-bio { margin-top: 12px; }
/* meta reads as a tidy labelled row split by a hairline — sentence case, no eyebrows */
.ab-meta { margin-top: 26px; display: flex; gap: 22px; align-items: center; }
.sheet--about .ab-mh { margin: 0; font-size: var(--t-label-size); font-weight: var(--w-semibold); letter-spacing: var(--t-label-track); text-transform: uppercase; color: var(--ink-soft); }
.sheet--about .ab-mv { margin: 5px 0 0; font-size: var(--t-value-size); font-weight: var(--w-medium); color: var(--ink-soft); }
.ab-mdiv { width: 1px; align-self: stretch; background: rgba(14,61,69,.16); }

.ab-foot { margin-top: 30px; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
/* TABLET (769-1024): the caps button labels are wider, so the About pop-up's two
   CTAs were wrapping to a second row and growing the card. Keep them on ONE row:
   share the width, trim the side padding, never wrap. */
@media (min-width: 769px) and (max-width: 1024px) {
  .sheet--about .ab-foot { flex-wrap: nowrap; gap: 10px; }
  .sheet--about .ab-foot .btn { flex: 1 1 0; min-width: 0; padding-left: 12px; padding-right: 12px; white-space: nowrap; }
}

/* ===================== Contact pop-up (business card / nav) =====================
   Same warm paper: an invitation with the Get-a-quote action on the left, plain
   contact details on the right. No border, no divider lines. */
.sheet--contact {
  width: min(620px, 92vw); max-width: none; height: auto; max-height: 90vh;
  padding: 0; overflow: hidden; display: flex; border: 0;
  box-shadow: var(--shadow-pop);
}
.sheet--contact .x { top: 16px; right: 16px; z-index: 8; }
.sheet--contact .ct-invite { flex: 1; min-width: 0; padding: 38px 30px; display: flex; flex-direction: column; justify-content: center; }
.sheet--contact .ct-title { margin: 0 0 14px; font-family: var(--font); font-weight: var(--w-heading); text-transform: var(--t-heading-case, none);
  font-size: var(--t-title-size); line-height: var(--lh-tight); letter-spacing: var(--t-title-track); color: var(--ink); }
.sheet--contact .ct-sub { margin: 0 0 26px; font-size: 15px; font-weight: var(--w-regular); color: var(--ink-soft); max-width: 40ch; line-height: 1.55; }
.sheet--contact .ct-invite .btn { align-self: flex-start; }

/* Pop-up titles clear the absolutely-positioned ✕ (top-right) so a long title
   wraps to a second line instead of running under it (e.g. "THE LODGE AT THE
   RANCH"). Desktop/tablet only: on mobile (≤768) the ✕ sits over the media/photo
   ABOVE the title, so there's no overlap and the title keeps its full width. */
@media (min-width: 769px) {
  .sheet--project .pj-title,
  .sheet--about .ab-name,
  .sheet--contact .ct-title { padding-right: 52px; }
}
.sheet--contact .ct-details { flex: .82; min-width: 0; padding: 38px 28px; display: flex; flex-direction: column; justify-content: center; gap: 20px; border-left: 1px solid var(--line); }
.sheet--contact .ct-lbl { margin: 0 0 5px; font-family: var(--font); font-weight: var(--w-semibold); font-size: var(--t-label-size); letter-spacing: var(--t-label-track); line-height: 1.2; text-transform: uppercase; color: var(--ink-soft); }
.sheet--contact .ct-val { margin: 0; font-family: var(--font); font-weight: var(--w-regular); font-size: var(--t-body-size); line-height: 1.5; color: var(--ink); text-decoration: none; }
.sheet--contact a.ct-val { cursor: pointer; transition: color .25s ease; }
@media (hover: hover) and (pointer: fine) { .sheet--contact a.ct-val:hover { color: var(--accent-deep); text-decoration: underline; text-underline-offset: 3px; } }

/* ---- Copy-to-clipboard email — shared vertical-swap component ------------------
   ONE definition, reused on BOTH paper surfaces: the /contact page email row AND
   the home contact pop-up (each composes it with .ct-val, which supplies the
   surface font-size/weight/colour). It owns only the button reset + the one-line
   "roll-up" window: on .copied the track slides up to reveal the "Email copied!"
   face; footer-copy.js (the single copy handler) toggles .copied for COPIED_MS and
   announces via aria-live. The footer's .sf-email uses the same mechanism + handler. */
.copy-email {
  appearance: none; -webkit-appearance: none; border: 0; background: none; padding: 0;
  font-family: inherit; color: inherit; cursor: pointer; text-align: left;
  display: inline-block; height: 1.5em; overflow: hidden; vertical-align: bottom;
}
.copy-email-track { display: flex; flex-direction: column;
  transition: transform .45s cubic-bezier(.22, 1, .36, 1); }
.copy-email-face { height: 1.5em; line-height: 1.5; white-space: nowrap; }
.copy-email.copied .copy-email-track { transform: translateY(-50%); }
@media (prefers-reduced-motion: reduce) { .copy-email-track { transition-duration: .001s; } }

/* ---------------- Responsive: pop-up sheets ----------------
   These overlays are shared by the desk (home) and don't affect piece layout.
   TABLET (769–1024): the two-column sheets keep their row layout (they read
   fine down to ~769) but shave their max heights so they never feel tight on a
   short landscape tablet. MOBILE (≤768): columns stack. */
@media (min-width: 769px) and (max-width: 1024px) {
  .sheet--project { width: min(1120px, 94vw); height: min(600px, 86vh); }
  .sheet--about   { width: min(1000px, 94vw); height: auto; max-height: 86vh; }
  .sheet--contact { width: min(620px, 92vw); }
  /* nudge the nav pill up 5px on tablet (desktop keeps the base top) */
  .glassnav { top: calc(clamp(14px, 1.8vh, 22px) + env(safe-area-inset-top, 0px) - 5px); }
  /* tablet project pop-ups: the narrow side column can't fit both CTAs on one
     row, so they stack — give each 100% basis so they stack at equal width
     instead of each sizing to its own label */
  .sheet--project .pj-foot .btn { flex: 1 1 100%; }
}

@media (max-width: 768px) {
  .navlink { font-size: 13px; }
  /* give the sheet more of the small screen + clear the notch / home indicator */
  .overlay { padding: 16px; padding-top: calc(16px + env(safe-area-inset-top, 0px)); padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }
  .sheet { padding: 30px 26px 40px; }
  /* cohesive close button across every mobile pop-up: a solid paper-filled circle
     (not translucent) so it reads cleanly even when it sits over a photo */
  .sheet .x { background: var(--paper); }
  /* Pop-ups stay scrollable on short screens, but NEVER show a scrollbar. On iOS the
     styled 8px bar (from .sheet::-webkit-scrollbar) flashed in on any touch-drag —
     even when the content fits and nothing actually scrolls — which read as janky.
     Hiding the bar (scroll still works) makes the card look locked. Covers the
     about/contact sheets (they carry .sheet) and the project pop-up's scrolling side. */
  .sheet, .sheet--project .pj-side { scrollbar-width: none; -ms-overflow-style: none;
    overscroll-behavior: contain; }   /* a drag past the end doesn't chain to the page */
  .sheet::-webkit-scrollbar, .sheet--project .pj-side::-webkit-scrollbar { display: none; }
  /* PROJECT pop-up on mobile: the sheet itself never scrolls, so the ✕ stays locked
     in the corner and reachable at all times. The slideshow is a fixed band; only the
     text side scrolls. The full thumbnail gallery is dropped — slideshow only.
     width:100% (not 94vw) so it sits inside the overlay's 16px padding, centred. */
  .sheet--project { flex-direction: column; width: 100%; height: auto; max-height: 88vh; padding: 0; overflow: hidden; }
  /* image is a shorter 3:2 RECTANGLE (was 42vh, near-square) so the card is less tall.
     transform:none drops the compositor layer that left a faint white hairline on the
     edges; the media clips its own rounded top corners (var(--r-media) = design-system radius). */
  .sheet--project .pj-media { flex: none; height: auto; aspect-ratio: 3 / 2; transform: none;
    border-radius: var(--r-media) var(--r-media) 0 0; }
  /* slides overfill the media by 1px on every side so the photo definitely reaches
     past the rounded corner — kills the faint white arc where the corner was clipping
     the image short and letting the card background peek through */
  .sheet--project .pj-slide { inset: -1px; }
  .sheet--project .pj-slide img { width: 100%; height: 100%; }
  .sheet--project .pj-arr { display: none; }         /* no arrows on mobile — swipe instead */
  .sheet--project .pj-side { flex: 0 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
    border-left: 0; border-top: 1px solid var(--line); padding: 20px 20px 22px; }
  .sheet--project .pj-grid { display: none; }        /* no gallery on mobile — slideshow carries it */
  .sheet--project .x { z-index: 10; }                /* locked over the media, always tappable */
  /* de-crowd the story progress capsule when a project has many photos */
  .sheet--project .pj-progress { gap: 5px; padding: 8px 11px; }
  .sheet--project .pj-dot.on { width: 22px; }
  /* both actions on ONE row, sized to share the width without overflowing */
  .sheet--project .pj-foot { flex-wrap: nowrap; gap: 10px; }
  .sheet--project .pj-foot .btn { flex: 1 1 0; min-width: 0; padding-left: 12px; padding-right: 12px; font-size: 14px; white-space: nowrap; }

  /* ABOUT — mobile profile card. The card carries a uniform frame (padding) so the
     framed horizontal portrait and the text/CTA block share EXACTLY one width. The ✕
     lives in the paper corner above the image (never over the photo). The Based-in /
     Focus meta is dropped on mobile, so the card is shorter and the CTAs sit right
     under the bio. Adjust the crop with --ab-img-pos (x% y%): lower y shows more of
     the top of the frame, higher y shows more of the bottom. */
  .sheet--about { flex-direction: column; width: 100%; height: auto; max-height: 90vh;
    overflow: hidden; padding: 12px 18px 22px; }
  /* the SHEET itself no longer scrolls on mobile — only the text column (.ab-about)
     does. That keeps the ✕ and the photo LOCKED in the non-scrolling sheet, so an
     elastic overscroll (rubber-band at the top) can never drag the header/image down
     with it. The ✕ stays in flow, top-right; its negative bottom margin cancels its
     flow height so the photo tucks up beneath it. */
  .sheet--about .x { flex: none; align-self: flex-end;
    margin: 0 0 -44px 0; z-index: 10; }
  /* photo sits right at the top (no header gap → shorter card); the paper-filled ✕
     overlaps its top-right corner. --ab-img-pos y=8% gives generous headroom above
     her head — raise the y number to crop lower, lower it to show even more headroom. */
  /* aspect 5/4 (was 3/2) — a taller, closer-to-square frame that shows more of the
     portrait, while still reading as a landscape-ish rectangle. */
  .sheet--about .ab-plate { flex: none; width: 100%; aspect-ratio: 5 / 4; margin-top: 0; padding: 0;
    border-radius: var(--r-media); overflow: hidden; }
  .sheet--about .ab-plate img { object-position: var(--ab-img-pos, 50% 8%); }
  /* the text column is the mobile scroller: it fills the space under the locked photo
     and scrolls internally (overscroll-behavior:contain stops it chaining to the page).
     padding 0 on the sides → text edges = image edges. */
  .sheet--about .ab-about { flex: 1 1 auto; min-height: 0; overflow-y: auto;
    -webkit-overflow-scrolling: touch; overscroll-behavior: contain; justify-content: flex-start;
    padding: 18px 0 2px; }
  .sheet--about .ab-role { margin-top: 2.16px; line-height: 1.3; }   /* tighter name→role gap on mobile */
  .sheet--about .ab-bio { margin-top: 14px; }
  .sheet--about .ab-meta { display: none; }   /* drop Based-in / Focus on mobile */
  .sheet--about .ab-foot { margin-top: 22px; gap: 12px; flex-wrap: nowrap; }
  .sheet--about .ab-foot .btn { flex: 1 1 0; min-width: 0; padding-left: 12px; padding-right: 12px; }

  /* CONTACT — mobile: centred invitation (title · sub · full-width Book-a-call),
     then a left-aligned block of labelled details below a hairline (Ubank / Opendoor
     contact-sheet pattern). Content-height, scrolls only if it must. */
  /* CONTACT — mobile: pulled up so the "Contact us" title shares the top row with the
     ✕ (title left, ✕ right, vertically centred on each other). flex:none on both
     sections stops the desktop flex-grow from stretching the card, so it hugs its
     content — no dead space at the bottom. */
  /* padding:0 overrides the base mobile .sheet 30/40px padding (which otherwise pushes
     the title down and pads the bottom); ct-invite/ct-details own the spacing instead */
  .sheet--contact { flex-direction: column; width: 100%; height: auto; max-height: 90vh; overflow: auto; padding: 0; }
  .sheet--contact .x { top: 16px; right: 18px; }
  .sheet--contact .ct-invite { flex: none; padding: 16px 26px 22px; text-align: center; align-items: center; }
  .sheet--contact .ct-title { margin-bottom: 10px; }
  .sheet--contact .ct-sub { max-width: 30ch; margin-bottom: 20px; }
  .sheet--contact .ct-invite .btn { align-self: stretch; width: 100%; }
  .sheet--contact .ct-details { flex: none; border-left: 0; border-top: 1px solid var(--line); padding: 20px 26px 24px; gap: 16px; }
  .sheet-head .x { margin-top: -5px; }      /* smaller title → nudge X up more */
}

@media (prefers-reduced-motion: reduce) {
  .overlay, .sheet, .piece { transition: none; }
}

/* ---------------- Mobile-layout editor (mobile-edit.html) ----------------
   Renders the whole composition inside a fixed 390px "phone" on a desktop
   window, so the mobile arrangement can be edited at its true breakpoint width.
   Everything that is normally fixed to the window becomes positioned relative
   to the #phone frame instead. */
body.medit {
  height: auto; min-height: 100%; overflow: auto;
  background: #cdd2c8;
  display: flex; flex-direction: column; align-items: center;
}
body.medit::after { content: none; }            /* drop the window vignette */
.medit-cap {
  margin: 22px 20px 4px; max-width: 640px; text-align: center;
  font-family: var(--font-sans); font-size: 12.5px; line-height: 1.6; color: var(--ink-soft);
}
.medit-cap code {
  font-family: var(--font); font-size: 12px;
  background: rgba(14,61,69,.08); padding: 1px 5px; border-radius: var(--r-xs); color: var(--ink);
}
#phone {
  position: relative; flex: none;
  width: 390px; height: min(844px, 86vh); margin: 14px auto 40px;
  background: var(--bg); border-radius: 44px; overflow: hidden;
  box-shadow: 0 0 0 2px #0c0c0c, 0 0 0 12px #17211c, 0 0 0 14px #313131,
              0 30px 70px -20px rgba(0,0,0,.5);
}
/* inside the frame, the window-fixed layers anchor to #phone instead */
body.medit .desk { position: absolute; inset: 0; }
body.medit .logo,
body.medit .navlink,
body.medit .overlay { position: absolute; }
