@font-face {
  font-family: 'Sinistre';
  src: url('assets/fonts/Sinistre-Regular.woff2') format('woff2'),
       url('assets/fonts/Sinistre-Regular.otf') format('opentype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Sinistre';
  src: url('assets/fonts/Sinistre-Bold.woff2') format('woff2'),
       url('assets/fonts/Sinistre-Bold.otf') format('opentype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Sinistre';
  src: url('assets/fonts/Sinistre-Dark.woff2') format('woff2'),
       url('assets/fonts/Sinistre-Dark.otf') format('opentype');
  font-weight: 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --bg: #f5f5f5;
  --ink: #171b34;
  --ink-soft: #171b3499;
  --line: #171b3440;
  --line-strong: #171b3466;

  --font-wordmark: 'Sinistre', Georgia, 'Times New Roman', serif;
  --font-display: 'aktiv-grotesk', 'Inter', Helvetica, Arial, sans-serif;
  --font-body: 'aktiv-grotesk', 'Inter', Helvetica, Arial, sans-serif;

  --pad: clamp(20px, 2.6vw, 48px);

  /* ~185px on a 13" MacBook Pro's browser viewport (~1434px wide) --
     scales with viewport width like --pad above, clamped so it can't
     shrink to nothing on narrow screens or balloon on huge ones. */
  --mark-intro-height: clamp(100px, 13vw, 240px);
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
}

a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--line);
}
a:hover { border-color: var(--ink); }

/* .mark-bar: sits at its natural position (centered in the header,
   above the wordmark) until script.js pins it -- position: fixed
   can't be scoped by a containing block the way sticky can, so once
   pinned it stays pinned for the rest of the page regardless of
   length, and script.js un-pins it if scrolled back above that point. */

.mark-bar {
  /* Auto rather than a fixed height, so this box always exactly wraps
     the mark's current (intro or settled) height -- it used to be a
     fixed 80px sized for the old static 46px icon, which the ~185px
     intro mark simply overflowed, crashing into the wordmark below
     since the margin-bottom that creates that gap started from the
     stale 80px edge, not the mark's real (much lower) bottom edge. */
  display: flex;
  align-items: center;
  justify-content: center;
  /* ~28px at the 1434px reference viewport (see CLAUDE.md). */
  margin-bottom: clamp(18px, 2vw, 32px);
}

.mark-bar.is-pinned {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  margin-bottom: 0;
  /* Padding brings this bar's total height up to match
     #contactHeading's own (~67px, driven by its font size and
     padding), so the solid background below fully covers it --
     solid, not the fade this used to be, since a partial fade only
     fully covered whichever of the two elements was shorter, letting
     scrolled content show through behind the taller one's remaining
     height. Split evenly top/bottom to center the icon in the bar,
     matching MARK_PINNED_TOP_OFFSET in script.js: markPinScrollY is
     solved to account for this padding, so the icon's top edge still
     lands exactly here (not at 0) the instant this pins, rather than
     snapping down into it from a position solved for a flush-top
     icon. */
  padding: 10.5px 0;
  background: var(--bg);
}

/* .mark shows one frame of a 14-frame running-cycle sprite sheet
   (347x450px per frame, so a 1400% / 14-frame background-size) via a
   *percentage* background-position, stepped by script.js -- see
   MARK_FRAME_COUNT there. Percentage positioning (background-position-x:
   i / (N-1) * 100%, the standard CSS sprite-animation formula) is what
   lets width/height below be sized in plain CSS -- pixel positioning
   would need script.js to know the element's current rendered size
   (and recompute on every resize) to keep frames aligned; percentages
   track it automatically, including mid-transition while it's actively
   resizing.

   height/width here are just the pre-JS fallback, matching the very
   top of the page (scrollY: 0) -- script.js takes over from the first
   update() call onward (on load, before any scroll), continuously
   resizing the mark by an inline style as a function of how far
   scroll has progressed toward the point it pins, between
   --mark-intro-height (responsive, see :root) at the top and 46px
   once pinned. It's also where the mark plays its run-cycle
   continuously, looping, for the very first thing a visitor sees on
   load, until the first scroll hands frame-stepping off to the
   scroll-driven behavior below. */

.mark {
  display: block;
  height: var(--mark-intro-height);
  width: calc(var(--mark-intro-height) * 347 / 450);
  background-image: url('assets/logo/mark-run-sprite.webp');
  background-repeat: no-repeat;
  background-size: 1400% 100%;
  background-position: 0% 0;
}

/* Header */

.site-header {
  max-width: 1920px;
  margin: 0 auto;
  min-height: 100vh;
  /* Redeclared, not replaced: 100vh is the fallback for browsers that
     don't support dvh. On mobile, 100vh is the *largest* possible
     viewport height (address bar collapsed) -- sized to that while
     the address bar is still showing leaves extra blank space below
     the fold that only closes up (with a jump) once the bar
     auto-hides on scroll. 100dvh tracks the *current* visible height
     instead, avoiding that. */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 48px var(--pad);
  text-align: center;
}

.mark-bar-slot {
  display: flow-root;
}

.wordmark {
  margin: 0;
  /* Matches the logomark's own color -- the sprite art is pure black,
     not --ink (a dark navy), so this is set explicitly rather than
     inherited from body. */
  color: #000;
  font-family: var(--font-wordmark);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  font-size: clamp(3.2rem, 13.5vw, 15rem);
  line-height: 1;
}

/* Hero */

.hero {
  position: relative;
  max-width: 1920px;
  margin: 0 auto;
  padding: 0 var(--pad);
}

.hero__img {
  display: block;
  width: 100%;
  aspect-ratio: 745 / 299.7;
  object-fit: cover;
  object-position: center;
}

@media (max-width: 759px) {
  .hero__img {
    /* The desktop crop is a wide banner (~2.5:1) -- cropped to that on
       a narrow phone screen, it's a thin sliver showing only a small
       slice of the painting. Closer to the source painting's own
       proportions (Mantegna's Parnassus is ~1.2:1) shows much more of
       it instead. */
    aspect-ratio: 6 / 5;
  }
}

.hero__scrim {
  position: absolute;
  inset: 0 var(--pad) 0 var(--pad);
  background: radial-gradient(
    ellipse at center,
    rgba(23, 27, 52, 0.6) 0%,
    rgba(23, 27, 52, 0.25) 50%,
    rgba(23, 27, 52, 0) 78%
  );
  pointer-events: none;
}

.hero__title {
  position: absolute;
  left: calc(var(--pad) + clamp(4px, 3vw, 24px));
  right: calc(var(--pad) + clamp(4px, 3vw, 24px));
  top: 50%;
  transform: translateY(-50%);
  margin: 0;
  text-align: center;
  color: #f7f3e8;
  font-family: var(--font-body);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: clamp(1.05rem, 3vw, 1.9rem);
  line-height: 1.3;
}

/* Intro paragraph */

.intro {
  max-width: 1920px;
  margin: 0 auto;
  padding: clamp(28px, 5vw, 48px) var(--pad) 0;
}

.intro p {
  margin: 0;
  font-weight: 400;
  font-size: clamp(1.3rem, 2.1vw, 1.85rem);
  line-height: 1.5;
  max-width: 78ch;
}

/* Rules */

.rule {
  border: none;
  border-top: 1px solid var(--line);
  margin: 0 0 28px;
  width: 100%;
}

/* Details grid */

.details {
  max-width: 1920px;
  margin: 0 auto;
  padding: clamp(40px, 6vw, 64px) var(--pad) clamp(56px, 8vw, 96px);
  display: grid;
  grid-template-columns: 1fr;
  /* Services, then the photo/blurb, then Contact last -- see the
     comment on these three elements in index.html for why they're
     independent grid items rather than two columns' worth of nested
     content. */
  grid-template-areas:
    "services"
    "secondary"
    "contact";
  gap: 56px;
}

.details__services { grid-area: services; }
.details__col--secondary { grid-area: secondary; }
.details__contact { grid-area: contact; }

@media (min-width: 760px) {
  .details {
    grid-template-columns: 1fr 1fr;
    /* secondary spans both rows by occupying that name in both
       template strings, filling the right column's full height while
       services/contact stack in the left one -- the same visual
       layout as before this became three grid items instead of two. */
    grid-template-areas:
      "services  secondary"
      "contact   secondary";
    gap: clamp(32px, 5vw, 96px);
  }
}

/* #contactHeading (in <header>) is always position: fixed -- unlike the
   old two-state version this replaced, it never sits in normal flow,
   so there's no layout to reserve for it. script.js fades it in/out
   by opacity as the page scrolls, handing off to
   #contactSectionHeading (a plain in-flow heading further down,
   styled identically via .section-title) right as that scrolls into
   view -- see the comments on both elements in index.html. */

.contact-heading {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  padding: 10px 0 8px var(--pad);
  margin: 0;
  z-index: 120;
  /* No background of its own: the logomark's fixed bar (z-index: 100,
     below this element's z-index: 120) already provides a full-width
     solid backdrop that both share, sized to match this element's own
     height -- giving this its own opaque background too would cover
     the mark icon whenever both are pinned at once (they overlap for
     part of the scroll range). */
  background: none;
  /* Opacity/pointer-events are set directly by script.js, animated
     over real elapsed time frame by frame -- not a CSS transition. A
     transition only animates if the browser paints the "before" value
     at least once first, but a single ordinary scroll step can jump
     straight past the point where this fade should start, leaving no
     frame to transition from. opacity: 0 here is just the pre-JS
     fallback so it can't flash visible before the first scroll
     handler run. */
  opacity: 0;
  pointer-events: none;
}

.contact-heading__text {
  display: inline-block;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 2.2rem;
  padding-bottom: 8px;
  /* Overrides the base a { border-bottom } rule -- this is a link now
     (opens a new email to Bennie), but the underline was deliberately
     removed from it. */
  border-bottom: none;
}

.eyebrow {
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-size: 2.2rem;
  font-weight: 400;
}

.name {
  margin: 0 0 32px;
  font-family: var(--font-display);
  font-size: 2.2rem;
  font-weight: 400;
}

.contact {
  margin: 0 0 48px;
  font-size: 1.8rem;
  font-weight: 400;
  line-height: 1.7;
}

.section-title {
  margin: 0 0 16px;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 2.2rem;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
}

#contactSectionHeading {
  /* Pre-JS fallback, same reasoning as #contactHeading's: script.js
     fades this in as it scrolls into view, so it shouldn't flash
     visible before the first scroll handler runs. margin-top is set
     live by script.js's measure() -- see there for why: a fixed value
     here would need re-tuning by hand every time either paragraph's
     copy changes. */
  opacity: 0;
}

.services {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 1.3rem;
  font-weight: 400;
  line-height: 2;
  color: var(--ink);
}

.details__col--secondary {
  display: flex;
  flex-direction: column;
}

.secondary-img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 11;
  margin-bottom: 24px;
  object-fit: cover;
  /* The source photo is a 2:3 portrait, much taller/narrower than this
     16:11 crop, so most of its height is necessarily cropped away --
     3.7% keeps the crop anchored near the top of the photo so her full
     face (not just eyes-down) stays in frame, rather than the default
     center position cutting off her hairline. */
  object-position: center 3.7%;
}

@media (max-width: 759px) {
  .secondary-img {
    /* The source photo is a 2:3 portrait -- matching that instead of
       the desktop's landscape 16:11 crop shows the whole photo
       uncropped on mobile, the same reasoning as .hero__img's own
       mobile override above. object-position above becomes a no-op
       at this exact ratio (nothing left to crop, so nothing to
       position), left as is rather than overridden to none. */
    aspect-ratio: 2 / 3;
  }
}

.blurb {
  margin: 0;
  font-weight: 400;
  font-size: clamp(1.3rem, 2.1vw, 1.85rem);
  line-height: 1.5;
}

/* Footer */

.site-footer {
  max-width: 1920px;
  margin: 0 auto;
  padding: 0 var(--pad) clamp(32px, 5vw, 48px);
}

.site-footer p {
  margin: 0;
  font-size: 0.8rem;
  color: var(--ink-soft);
}
