/* ==========================================================================
   MOONLIT ICE — Stylesheet
   Plain CSS, no build step. Organized in sections:
   1. Reset & base
   2. Typography
   3. Persistent navigation (wordmark, tabs, toggle)
   4. Overlay menu
   5. Homepage
   6. Editorial page (asymmetric archive + lightbox)
   7. Commission page (grid) + individual project page
   8. Bio page
   9. Responsive (mobile) adjustments
   ========================================================================== */

/* -----------------------------
   1. Reset & base
   ----------------------------- */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  height: 100%;
}

body {
  background: #ffffff;
  color: #000000;
  font-family: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, video {
  max-width: 100%;
  display: block;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font-family: inherit;
  background: none;
  border: none;
  cursor: pointer;
  color: inherit;
}

ul {
  list-style: none;
}

/* -----------------------------
   2. Typography
   ----------------------------- */
.wordmark {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.nav-tab {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.nav-tab.is-active {
  font-style: italic;
}

/* -----------------------------
   3. Persistent navigation
   ----------------------------- */
.site-nav {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 100;
}

.site-nav a,
.site-nav button {
  pointer-events: auto;
}

/* Translucent glass panel behind the bottom bar (wordmark + tabs + the rule
   line above them) -- the bar itself has no other background, so without
   this the text just floats directly over whatever page content scrolls
   underneath it. Height is set so the panel's TOP EDGE lines up exactly
   with the top of .nav-line (60px bottom + 2px height = 62px) -- it must
   stop there, not extend past it, or the blur bleeds upward into the photo
   above the line instead of staying contained to the bar. Sits first in
   .site-nav's DOM order so it paints behind everything else in the nav
   without needing z-index. -webkit- prefix is required for Safari (iOS in
   particular) to actually blur -- plain `background` is the fallback for
   any browser that ignores backdrop-filter entirely. */
.nav-glass {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 62px; /* = .nav-line's bottom (60px) + height (2px) -- keep in sync */
  background: rgba(255, 255, 255, 0.5); /* -5% opacity per request, was 0.55 */
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

.nav-wordmark {
  position: absolute;
  left: 24px;
  bottom: 24px;
}

.nav-tabs {
  position: absolute;
  right: 24px;
  bottom: 24px;
  display: flex;
  gap: 20px;
}

/* Thin rule above the bottom bar, spanning the same 24px gutter as the
   wordmark/tabs below it — matches the reference's line-above-caption
   treatment. Sits on the persistent nav layer so it shows on every page. */
.nav-line {
  position: absolute;
  left: 0; /* was 24px -- now spans full screen width edge-to-edge per request */
  right: 0;
  bottom: 60px;
  height: 2px;
  background: #000;
}

.nav-toggle {
  position: absolute;
  right: 24px;
  top: 24px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  width: 26px;
  padding: 6px 2px;
}

.nav-toggle span {
  display: block;
  height: 2px; /* was 1px — bolder per request */
  width: 100%;
  background: #000;
  transition: transform 0.3s ease, opacity 0.3s ease;
}

.nav-toggle[aria-expanded="true"] span:nth-child(1) {
  transform: translateY(6px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] span:nth-child(2) {
  opacity: 0;
}
.nav-toggle[aria-expanded="true"] span:nth-child(3) {
  transform: translateY(-6px) rotate(-45deg);
}

/* Hide the small persistent tabs while the overlay is open, so the same
   three links aren't shown twice at once (large, centered in the overlay,
   and small, bottom-right). The wordmark stays visible as a home anchor. */
body.nav-open .nav-tabs {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}

/* -----------------------------
   4. Overlay menu
   ----------------------------- */
.overlay-menu {
  position: fixed;
  inset: 0;
  background: rgba(255, 255, 255, 0.62); /* more transparent per request — was 0.82, see .lightbox below (matched) */
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  z-index: 90;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  transition: opacity 0.35s ease, transform 0.35s ease, visibility 0.35s;
}

.overlay-menu.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.overlay-links {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}

/* Reverted to the original weight per follow-up feedback — a prior pass
   tried font-weight 700 for "darker", but that read as too bold. Back to
   500 (the original); color stays explicit black. */
.overlay-links a {
  font-size: clamp(28px, 6vw, 56px);
  font-weight: 500;
  color: #000;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  transition: opacity 0.2s ease;
}

.overlay-links a:hover {
  opacity: 0.5;
}

/* Socials sit directly under the links (as their own tight cluster) instead
   of being pinned independently to the viewport bottom — previously that
   put them right at the same height as .nav-line (the persistent thin rule
   above the bottom nav bar, which stays on top at z-index 100 even while
   the overlay is open), so they visually touched/overlapped it. Now they
   flow naturally after "Information" with a close, deliberately tightened
   gap (less than a fresh section's worth of space) rather than being
   anchored to the bottom of the screen. */
.overlay-socials {
  margin-top: 32px;
  display: flex;
  gap: 24px;
}

.overlay-socials a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px; /* was 22px — "a notch bigger" per request */
  height: 28px;
}

.overlay-socials svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: #000;
  stroke-width: 1.4;
}

/* -----------------------------
   5. Homepage
   ----------------------------- */
/* Full-bleed, no borders/gaps, per explicit request (the abbotts.dk-style
   division gap + left/top border gaps "wasn't working visually" -- dropped
   entirely rather than re-tuned). Back to plain full-bleed: the two halves
   touch directly, edge to edge, with nothing between them and nothing
   inset from the viewport's own edges.

   Round 2026-08-28 (v2): crop amount is now driven by the SMALLEST photo in
   the swap set, not by the viewport/nav -- per her explicit "don't crop as
   per the nav, crop as per the height of the smallest image in the swap."
   All 18 hero photos (left + right sets combined) are one of two aspect
   ratios: 1024x1280 or 1080x1350 (both exactly 4:5 = 0.8 width/height --
   hero-left-1/3/5/8 and hero-right-4/7), or 960x1280 (3:4 = 0.75
   width/height -- everything else). At any given rendered width, the 4:5
   photos are the shortest -- they need zero cropping -- so `.hero-half`
   uses `aspect-ratio: 4 / 5` to size itself to exactly that photo's box.
   The 3:4 photos are taller than that box at the same width, so
   object-fit:cover crops them down to match; the 4:5 photos render
   edge-to-edge, uncropped. The box's actual height is therefore whatever
   4/5 of the half's own (viewport-driven) width comes out to -- nothing
   here is keyed to the nav or to 100vh directly. */
.hero {
  display: flex;
  box-sizing: border-box;
  gap: 0;
  padding-bottom: 60px; /* reserves exactly the .nav-line's own distance from
    the viewport bottom (see section 3), so the image ends right above the
    line rather than running underneath it -- unrelated to the design
    borders/gaps removed above, kept per her separate "should end right
    above the nav line" request. */
  background: #fff;
}

.hero-half {
  position: relative; /* positioning context for the absolutely-stacked 2nd img below */
  flex: 1 1 50%;
  aspect-ratio: 4 / 5; /* = the smallest photo in the swap (1024x1280 /
    1080x1350); every taller photo crops down to fit this box, this one
    doesn't crop at all */
  cursor: pointer;
  overflow: hidden; /* clips the object-fit:cover crop to the half's own box */
}

/* The first (in-flow) <img> of each pair fills the half's box exactly --
   width:100%, height:100% of the aspect-ratio-sized .hero-half -- and
   object-fit:cover scales+crops so every photo, regardless of its own
   aspect ratio, ends up the same height with no distortion. */
.hero-half img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity 0.133s linear; /* must match FADE_MS in js/main.js exactly */
}

/* The second <img> is the crossfade layer: absolutely stacked exactly on
   top of the first. With no border gap on either half anymore, inset:0
   lines it up with the first image's box exactly -- no calc() offsets
   needed. See initHeroHalf() in js/main.js for the role-swapping opacity
   logic that makes this a true image-to-image dissolve with no white
   flash. */
.hero-half img:nth-of-type(2) {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* -----------------------------
   6. Editorial page
   ----------------------------- */
/* Shared row-archive system — used by BOTH Editorial and Commission.
   Ref: https://abbotts.dk/work/ — one horizontal row per project: a label
   column on the left (title + subtitle/season), a filmstrip of same-height
   images/videos to the right. Rows are separated by a thin divider line
   with a small corner tick, matching the reference.

   Deviations from the literal reference, per explicit request:
   - The reference butts filmstrip images edge-to-edge with NO gap; here a
     visible gap is added between every image/video (--row-item-gap). */
.archive-page {
  padding: 48px 0 100px;
}

.archive-row {
  display: flex;
  align-items: flex-start;
  gap: 48px;
  padding: 28px 24px; /* was 56px — shrunk so 4-5 projects fit on one screen (see .row-strip) */
}

.row-label {
  flex: 0 0 220px;
  padding-top: 6px;
}

.row-label .title {
  display: block;
  font-weight: 600;
}

.row-label .subtitle {
  display: block;
  color: #666;
  margin-top: 4px;
}

.row-strip {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: stretch;
  gap: 12px; /* was 20px — scaled down to match the smaller thumbnails below */
  height: 18vh; /* was 46vh — shrunk so 4-5 projects fit on one screen, per request.
    Width shrinks along with it automatically: thumbnails are sized by
    height + their own natural aspect ratio (see .row-strip img/video below),
    so there's no separate "width" knob to turn — this one change handles both. */
  min-height: 150px;
  max-height: 220px;
  overflow-x: auto; /* images keep their own natural width instead of being
    stretched, so a row can end up wider than the available space; scroll
    rather than crop or clip anything out of view. */
  overflow-y: hidden;
}

/* Each image/video keeps its OWN natural aspect ratio at the row's shared
   height — no longer force-stretched to equally divide the row's width
   (that was flex:1 1 0 + width:100% + object-fit:cover, which cropped
   anything that didn't match its slot). Per explicit request: never crop,
   and don't try to fill the entire row — a project with fewer/narrower
   images simply leaves empty space at the end of its strip; a project
   whose images add up to more than the available width scrolls
   horizontally instead (see overflow-x above). */
.row-strip img,
.row-strip video {
  flex: 0 0 auto;
  height: 100%;
  width: auto;
  object-fit: contain;
  cursor: pointer;
}

/* No hover dimming — a prior opacity-on-hover effect blended the image with
   the white page background behind it (there's nothing else to blend with
   at less than full opacity), which read as an unwanted white tint over the
   thumbnail. Removed per request; cursor:pointer alone signals click-ability,
   matching the reference. */

/* Lightbox-only extras: images/videos that should be reachable when paging
   through a project's lightbox, but should NOT appear in the filmstrip
   preview on this page (e.g. additional shots that would clutter or
   over-lengthen the row). Add them as extra <img>/<video class="lightbox-extra">
   children at the END of a .row-strip (after the visible ones — see the
   HTML comments in editorial.html/commission.html for why the ordering
   matters on mobile). They never render or take up row space here, on
   either desktop or mobile, but js/main.js's lightbox builder reads every
   IMG/VIDEO child of .row-strip regardless of visibility, so they still
   show up when scrolling through that project's lightbox. */
.row-strip .lightbox-extra {
  display: none;
}

/* Auto-fill (js/main.js): when a row's visible items leave leftover space
   at the row's shared height (natural aspect ratios rarely add up to
   exactly the available width — see "no cropping" above), JS reveals
   lightbox-only extras, in order, until the space is used up. This class
   is added/removed entirely by JS — never add it by hand in the HTML.
   Three classes beats the plain ".lightbox-extra" hide rule above by
   specificity, so this correctly overrides it on desktop. A
   "lightbox-extra-permanent" extra (see HTML) is deliberately excluded by
   the JS and never receives this class, so it stays lightbox-only always. */
.row-strip .lightbox-extra.is-shown {
  display: block;
}

/* Divider between rows: line + small corner tick, matching the reference */
.row-divider {
  position: relative;
  border: none;
  border-top: 2px solid #000;
  margin: 0 24px;
}

.row-divider::after {
  content: "";
  position: absolute;
  right: 0;
  top: -2px;
  width: 2px;
  height: 14px;
  background: #000;
}

/* Editorial and Commission now behave identically on click: every image/
   video in a row opens the shared lightbox below, with left/right paging
   through that SAME row's items — there is no more per-page click
   difference and Commission no longer links out to a separate project
   page (see js/main.js initLightbox for how items are grouped per row). */

/* Lightbox — translucent (frosted) overlay with prev/next gallery paging.
   Clicking any image/video in a row opens it here; left/right arrows page
   through every item in that same project; the close cross returns to
   the underlying Editorial/Commission page (nothing navigates away). */
.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(255, 255, 255, 0.62); /* more transparent per request — was 0.82 */
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, visibility 0.25s;
}

.lightbox.is-open {
  opacity: 1;
  visibility: visible;
}

.lightbox-media {
  max-height: 88vh;
  max-width: 82vw;
  display: flex;
  align-items: center;
  justify-content: center;
}

.lightbox-media img,
.lightbox-media video {
  max-height: 88vh;
  max-width: 82vw;
  width: auto;
  height: auto;
  object-fit: contain;
}

.lightbox-close {
  position: absolute;
  top: 24px;
  right: 24px;
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  z-index: 1;
}

.lightbox-prev,
.lightbox-next {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 32px;
  line-height: 1;
  padding: 12px 18px;
  z-index: 1;
}

.lightbox-prev {
  left: 12px;
}

.lightbox-next {
  right: 12px;
}

.lightbox-prev:hover,
.lightbox-next:hover,
.lightbox-close:hover {
  opacity: 0.6;
}

.lightbox-counter {
  position: absolute;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 13px;
  letter-spacing: 0.04em;
}

/* -----------------------------
   7. Commission page
   ----------------------------- */
/* Commission's row/label/filmstrip/lightbox rules are entirely the shared
   .archive-row / .lightbox system above (section 6) — nothing page-specific
   needed here. Commission has no dedicated per-project pages (confirmed) —
   every row opens in the same in-page lightbox gallery as Editorial. */

/* -----------------------------
   8. Bio page
   ----------------------------- */
/* Round 4 (2026-08-28): full layout swap to the typographic "poster"
   reference she supplied, mapped line by line onto her content. No photo
   in this version -- the reference is pure type and neither the portrait
   nor the filmstrip built in round 3 got mapped anywhere in her spec
   (assets are still on disk under assets/bio-*.jpg, just unused here).
   Vertical stack: name+role at top, "follow for more" + the 3 bio
   paragraphs vertically centered in the middle, a 4-column credit-style
   footer at the bottom mirroring the reference's Abdul/Create/Objects/date
   row exactly. */
.bio-page {
  display: flex;
  flex-direction: column;
  height: 100vh; /* fixed, not min-height -- same one-screen treatment as
    every other full-height page on the site */
  box-sizing: border-box;
  padding: 40px 40px 84px; /* 84 on the bottom -- .nav-line itself sits 60px
    up from the viewport edge (see section 3), but for text content
    (unlike a full-bleed photo) landing exactly on that line reads as
    clipped rather than intentional, so this adds 24px of real breathing
    room between the footer and the line. */
  overflow: hidden;
  /* Round 2026-09-18: beige/off-white background (#f1efe9) removed per her
     explicit request -- page now falls back to the site's default white
     background (see body { background: #ffffff } above).
     Round 2026-08-28: textured grain removed per her explicit request --
     flat color only. (Went through two versions first: an inline SVG
     feTurbulence filter, then a rasterized PNG tile at assets/bio-noise.png
     once the SVG version proved inconsistent across browsers -- that PNG
     asset is unused now but left on disk rather than deleted.) */
}

.bio-top {
  flex: 0 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: flex-start; /* the image no longer needs to match the text
    block's height (per her explicit "don't have to be aligned" request)
    -- just top-aligned like any normal two-column row */
  gap: 40px;
}

.bio-top-text {
  flex: 0 0 auto;
}

.bio-top-image {
  flex: 0 0 auto;
  width: clamp(160px, 18vw, 280px);
  height: auto;
  max-height: 340px; /* keeps its own natural aspect ratio, just capped
    so it can't grow arbitrarily tall now that nothing else constrains it */
  margin-right: 56px; /* pushed left of the fixed nav-toggle (right: 24px,
    width: 26px -- left edge sits ~50px from the viewport edge) so the two
    never overlap, with real breathing room between them */
  object-fit: cover;
  display: block;
}

/* Her name in place of the reference's "Forms born of observation." --
   same two-line-via-<br> technique as the previous round's bottom
   statement, just relocated to the top of the page. Uppercase per her
   request (the reference's headline is caps too). */
.bio-name {
  font-size: clamp(40px, 6vw, 84px);
  font-weight: 700; /* name headline back to bold per her request; rest
    of the page stays regular weight */
  text-transform: uppercase;
  line-height: 0.95;
  letter-spacing: -0.01em;
  margin-bottom: 20px;
}

/* In place of the reference's "FW Designs" -- same weight/role in the
   hierarchy (a subtitle directly under the big name), her actual
   text as given rather than forced uppercase. */
.bio-subtitle {
  font-size: 22px; /* +2px per explicit request */
  font-weight: 400; /* bold removed per her request */
}

/* The reference's "Follow for more" + pull-quote row. This CSS centers
   the pair within .bio-middle's leftover space (top-block-and-footer
   subtracted) as a safe default that can never overlap either -- but
   when .bio-top is tall (the portrait image makes it so), that leftover
   space starts well below the page's true vertical midpoint, so the pair
   visually reads as sitting closer to the footer/nav than centered.
   main.js's centerBioPair() nudges it toward the page's true center with
   a transform, clamped so it never gets closer than a fixed margin to
   either .bio-top or .bio-footer -- see that function for the full
   reasoning. This CSS rule is what it clamps against. */
.bio-middle {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
}

/* align-items: flex-end bottom-aligns "Follow for more" to the last line
   of the paragraph text: because this row's height equals .bio-quote's
   natural height (the taller of the two children), the flex-end
   alignment lands .bio-follow's bottom exactly on .bio-quote's bottom
   without requiring .bio-quote to move. */
.bio-pair {
  display: flex;
  align-items: flex-end;
  width: 100%;
  gap: 88px; /* minimum gap (bumped up slightly from 64px per her
    request) -- the actual gap is usually larger, since main.js nudges
    .bio-quote to align under "Brand Consultant" in the subtitle line
    above; this is the floor it can't get closer than */
}

.bio-follow {
  flex: 0 0 auto;
  /* left-aligned flush with "Sakina" and "Contact" above/below, per her
     explicit request -- no left offset anymore */
  font-size: 29px; /* dialed back from 30px per her request */
  font-weight: 700; /* back to bold per her request */
  text-transform: uppercase;
  line-height: 1.3;
}

/* Same role as the reference's big pull-quote, but sentence case (not
   forced caps) per her standing preference from the last round.
   margin: 0 auto centers this block in whatever space is left over after
   .bio-follow's fixed width in the flex row -- with max-width capped well
   below the available width, that leaves generous, even space on both
   sides, per her "enough negative spacing on both sides" request. */
.bio-quote {
  max-width: 480px;
  margin-right: auto; /* only the right side auto-margins now (left gap is
    the fixed .bio-pair gap above), so the block sits close to "Follow for
    more" rather than centered with equal space on both sides */
}

.bio-quote p {
  font-size: 16px;
  font-weight: 400; /* bold removed per her request */
  line-height: 1.5;
  margin-bottom: 18px;
}

.bio-quote p:last-child {
  margin-bottom: 0;
}

/* Footer -- 4 columns mapped 1:1 onto the reference's credit row (Abdul/
   Haseeb/Here, Create/Destroy/Repeat, Objects are a language.../..., the
   date), so each column keeps the same top-aligned baseline the reference
   had. Round 2026-08-28: rebuilt as a CSS Grid with named areas (was
   nested flex "column" divs) so the same flat, DOM-order-independent
   markup can also power the mobile stacked/grouped layout below without
   duplicating any content -- see the HTML comment above .bio-footer.
   justify-content: space-between fans the 4 columns out the same way the
   reference's roughly-even spacing did, with the last one landing at the
   far right edge in place of the date; the 4th column's top row (where
   the old markup used an empty &nbsp; spacer) is just left unoccupied --
   Grid doesn't need a placeholder element the way the flex version did. */
.bio-footer {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: repeat(4, auto);
  grid-template-areas:
    "contact  phone  email  .       "
    "social   insta  vimeo  behance ";
  justify-content: space-between;
  align-items: start;
  column-gap: 24px;
  row-gap: 6px;
}

.bio-f-contact-label { grid-area: contact; }
.bio-f-social-label  { grid-area: social; }
.bio-f-phone         { grid-area: phone; }
.bio-f-email         { grid-area: email; }
.bio-f-instagram     { grid-area: insta; }
.bio-f-vimeo         { grid-area: vimeo; }
.bio-f-behance       { grid-area: behance; justify-self: end; }

/* Contact / Social labels -- font-size matched to the links below them
   per her explicit request that the two footer rows read as one
   consistent size. Not bold. */
.bio-footer-label {
  display: block;
  font-size: 14px;
  letter-spacing: 0.05em; /* kept -- was paired with uppercase for a
    tracked-label feel; still reads fine in sentence case */
  font-weight: 400;
}

/* Phone/Email/Instagram/Vimeo/Behance -- left in their natural case
   (an all-caps email address or handle reads as a mistake, not a style)
   and, per her explicit note from an earlier round, no italics anywhere.
   Normal (non-bold) weight per her request. */
.bio-footer a {
  display: block;
  font-size: 14px;
  font-weight: 400;
}

/* -----------------------------
   9. Responsive
   ----------------------------- */
/* Hero: single full-bleed column instead of the two-column split whenever
   the viewport is portrait-oriented (taller than wide), regardless of exact
   width -- not just narrow phones. The two-column split only reads well on
   wide/short viewports: at half-width, a 3:4 photo falls far short of a
   tall viewport's height. Landscape tablets and laptops stay on the
   two-column split, which fills correctly there. The plain
   (max-width: 800px) branch still covers narrow *landscape* phones, which
   wouldn't otherwise match the orientation check.

   ONE holder, not two (per explicit request): only the RIGHT sequence is
   used -- the left half is hidden entirely (not just deferred to) rather
   than shown. Swiping through it is handled in js/main.js (touch
   start/end on #hero-right); autoplay/hover still drives it independently
   on desktop, where touch events simply never fire.

   No crop, no stretch: this reverts the previous round's object-fit:cover
   fix. That crop eliminated the blank-gap problem but traded away "no
   crop" -- superseded 2026-08-28 by the same smallest-image-in-the-swap
   crop applied to the desktop hero above (see that comment for the ratio
   math). The single visible half sizes itself with the same
   `aspect-ratio: 4/5` and crops the taller photos via object-fit:cover, so
   mobile matches desktop -- crop amount keyed to the smallest photo, not
   to 100vh/the nav. */
@media (max-width: 800px), (orientation: portrait) {
  .hero {
    flex-direction: column;
    justify-content: center; /* kept as a defensive fallback -- with
      .hero-half now forced to the same 100dvh below, there's normally no
      leftover space left to center within, but this keeps things safe if
      vh/dvh round differently by a pixel or two in any given browser. */
    gap: 0;
    min-height: 100dvh; /* dvh (not vh) to match .hero-half's own unit
      below exactly -- see that rule's comment */
    padding-bottom: 0; /* the base rule's 60px is a desktop-only nav-line
      clearance for a full-bleed photo that runs edge to edge -- not
      relevant here now that the image fills the full mobile screen */
  }
  .hero-half:first-child {
    display: none;
  }
  .hero-half:last-child {
    flex: 0 0 auto;
    width: 100%;
    height: 100dvh; /* per her explicit request -- fills the full dynamic
      viewport height on mobile (dvh accounts for the browser chrome
      showing/hiding, unlike static vh) rather than sizing off the photo's
      own aspect ratio. Overrides the desktop base rule's aspect-ratio: 4/5
      below (see aspect-ratio: auto) -- with both width and height now
      definite, cropping is driven by the screen's own proportions again,
      not by the smallest photo in the swap. */
    aspect-ratio: auto;
  }
  .hero-half:last-child img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  .hero-half:last-child img:nth-of-type(2) {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

/* -----------------------------
   8b. Bio page — tablet (801–1024px, e.g. iPad portrait)
   ----------------------------- */
/* Stays side-by-side like desktop at this width, but the thin filmstrip is
   dropped rather than squeezed into a third column that would leave the
   text column too narrow to read comfortably. Portrait/text keep the same
   proportions as desktop. */
@media (min-width: 801px) and (max-width: 1024px) {
  /* NOTE (2026-08-28): the round-4 layout swap replaced every class this
     tier used to target (.bio-filmstrip, .bio-content, etc. no longer
     exist in the markup). Tablet wasn't part of this round's brief --
     revisit once desktop is signed off.

     2026-09-18: a tablet-portrait fix (stacking to the mobile layout via
     `, (orientation: portrait)` on the next media query) was tried and
     then reverted at her explicit request -- back to this original
     behavior (tablet, portrait or landscape, uses the desktop row layout
     as-is). */
}

@media (max-width: 800px) {
  .nav-glass {
    height: 48px; /* = mobile .nav-line's bottom (46px) + height (2px) -- keep in sync */
  }
  .nav-wordmark {
    left: 16px;
    bottom: 16px;
    font-size: 13px;
  }
  .nav-tabs {
    right: 16px;
    bottom: 16px;
    gap: 8px;
  }
  .nav-tab {
    font-size: 12px; /* +2px per explicit request */
    letter-spacing: 0.01em;
  }
  .nav-line {
    left: 0; /* was 16px -- full width, matches desktop change */
    right: 0;
    bottom: 46px;
  }
  .nav-toggle {
    right: 16px;
    top: 16px;
  }

  /* Row-archive (Editorial + Commission) on mobile: label stacks above a
     SINGLE cover image (not the full filmstrip) — "one cover image
     clickable" per request. The rest of that project's images/videos stay
     in the DOM (just hidden), so tapping the cover still opens the
     lightbox with the complete set browsable via left/right. */
  .archive-row {
    flex-direction: column;
    gap: 16px;
    padding: 24px 16px;
  }
  .row-label {
    flex: none;
    padding-top: 0;
  }
  .row-strip {
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 5;
    min-height: 0;
    max-height: none;
  }
  .row-strip img:not(:first-child),
  .row-strip video:not(:first-child) {
    display: none;
  }
  .row-strip img:first-child,
  .row-strip video:first-child {
    flex: 1 1 auto;
    width: 100%;
    height: 100%;
    object-fit: cover; /* fills the uniform 4:5 box edge to edge instead of
      the desktop rule's object-fit:contain (which preserves each photo's
      own aspect and would letterbox a differing source, making covers look
      inconsistent size to size). Recommended export: crop cover images to
      an exact 4:5 ratio (e.g. 1080x1350px) beforehand so nothing is
      cropped unpredictably by this rule -- see build-phase.md for the full
      spec she can hand to a photo folder. */
  }

  /* Explicit mobile cover override: normally the first item in the row is
     the mobile cover (rules above). A project can instead designate ANY
     item as the mobile cover — regardless of its desktop position — by
     adding class="mobile-cover" (see generate_rows.py's mobile_cover_idx).
     When one exists in a strip, hide everything (including the first
     child) and show only the designated item. Used by Bare Bombay and
     Fevicol Shoefix, where the desktop row order and the best mobile cover
     image are no longer the same item. */
  .row-strip:has(.mobile-cover) img,
  .row-strip:has(.mobile-cover) video {
    display: none;
  }
  .row-strip .mobile-cover {
    display: block !important;
    flex: 1 1 auto;
    width: 100%;
    height: 100%;
    object-fit: cover; /* same uniform-fill treatment as the default cover rule above */
  }

  .archive-page {
    padding-top: 56px;
  }

  /* Round 2026-08-28 (v2): real mobile layout for the poster bio page, per
     her explicit spec -- name+subtitle, then the bio paragraphs directly
     underneath (no "Follow for more", no portrait in between), then
     Contact/Social stacked with their links grouped underneath each
     label, then the portrait image at the very end of the page.

     .bio-top / .bio-middle / .bio-pair are desktop-only ROW wrappers that
     group content differently than this mobile order needs (e.g. the
     portrait lives inside .bio-top, before the body text, but mobile
     wants it last). Rather than duplicating markup for the two layouts,
     each wrapper gets `display: contents` here -- it stops generating its
     own box and its children are promoted to be direct flex items of
     .bio-page instead, so a single flat `order` on each real content
     piece can freely resequence them regardless of how they're nested in
     the HTML. .bio-page is already `display:flex; flex-direction:column`
     (see the base rule above), so `order` is all that's needed here. */
  .bio-page {
    height: auto;
    min-height: 100vh;
    overflow: visible;
    padding: 56px 20px 72px; /* 72px bottom clears the fixed mobile nav bar
      (48px tall) with room to spare, now that content scrolls under it */
    gap: 40px; /* vertical space between the promoted sections below --
      desktop doesn't use .bio-page's own gap (its 3 rows are spaced via
      .bio-middle's flexible leftover space instead), so this only ever
      applies here */
  }

  .bio-top,
  .bio-middle,
  .bio-pair {
    display: contents;
  }

  .bio-top-text { order: 1; }
  .bio-quote    { order: 2; }
  .bio-footer   { order: 3; }
  .bio-top-image { order: 4; }

  .bio-follow {
    display: none; /* "remove follow for more" per her request -- the
      element stays in the DOM (simpler than editing the HTML for this)
      but is fully removed from the mobile layout */
  }

  .bio-top-image {
    width: min(220px, 55vw);
    max-height: none;
    margin: 0; /* the 56px desktop margin only existed to clear the
      top-right toggle when the image sat beside the text -- moot now
      that the image is stacked at the end of the page */
    align-self: flex-start;
  }

  .bio-subtitle {
    font-size: 16px; /* desktop's 22px wrapped awkwardly at phone widths */
  }

  .bio-quote {
    max-width: none;
    width: 100%;
    margin: 0;
  }

  /* Footer: same flat markup as desktop (see the HTML comment above
     .bio-footer and the Grid rules in section 8 above), but switched from
     a 4-column Grid to a single flex column here. Since the markup's DOM
     order is already Contact-label, Phone, Email, Social-label,
     Instagram, Vimeo, Behance, a plain top-to-bottom flex stack reads
     exactly as "Contact" grouped with its own links, then "Social"
     grouped with its own -- no reordering needed, just a display switch. */
  .bio-footer {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
  }

  .bio-f-social-label {
    margin-top: 18px; /* separates the two groups -- everything else uses
      the tighter 6px rhythm of a label with its own links */
  }

  /* .bio-f-behance's desktop `justify-self: end` (a Grid-only property) is
     simply ignored once .bio-footer switches to flex above -- it already
     sits flush left with everything else via .bio-footer's own
     align-items: flex-start, no override needed. */
}

/* Extra-narrow phones (e.g. 320px-wide devices): the 800px breakpoint's nav
   sizing above is tuned for ~360px+; tighten further here so the bold wordmark
   and tab group never collide at the narrowest common width. */
@media (max-width: 340px) {
  .nav-wordmark {
    font-size: 10px; /* nudged down 1px from 11px -- the +2px tab bump below
      needs the extra room at this narrowest tier to avoid colliding with
      the wordmark (verified down to 320px) */
  }
  .nav-tabs {
    gap: 3px;
  }
  .nav-tab {
    font-size: 11px; /* +2px per explicit request */
    letter-spacing: 0;
  }
}
