/* ==========================================================================
   Lava New Media, 2026 design: home page

   Sections in the order of SPEC.md section 3. Desktop values are the 1440
   board, phone values the 390 board; the widths between follow section 6.
   Shared tokens, fonts, masthead, buttons, links and footer are in lava.css.
   ========================================================================== */

/* The content column's left edge: the margin, or centred above 1440. Used
   where a block cannot simply sit inside .shell. */
:root { --edge: max(var(--margin), (100% - var(--content)) / 2); --col: min(var(--content), 100% - 2 * var(--margin)); }

/* Half the 02 object's height: how far it breaks into the red act. --break
   is half the image's ratio (desktop 952/1440/2, phone 906/1440/2; the
   ratios are under staged objects); the image is the object's content box,
   2px narrower than the column, and the object adds its two 1px hairlines,
   so --half is (column - 2px) x half-ratio + 1px, exactly. */
:root { --break: 0.330556; --half: calc((var(--col) - 2px) * var(--break) + 1px); }
/* Rounded to a whole CSS pixel where round() exists (the line above is the
   fallback): browsers paint on whole pixels anyway, and a section that
   starts on a whole pixel keeps its links' hover underlines on whole device
   pixels too (lava.css, the underline notes). Within half a pixel of exact. */
:root { --half: round(nearest, calc((var(--col) - 2px) * var(--break) + 1px), 1px); }
@media (max-width: 767px) { :root { --break: 0.314583; } }

/* ------------------------------------------------------- staged objects -- */
/* A screenshot: rounded rectangle, hairline edge, the whole thing a link.
   object-fit keeps the board's ratio whatever the file's height. On the dark
   grounds the object is its hairline and nothing else: no shadow and no glow
   behind it (SPEC.md section 1; a black blur or a faint gradient on
   near-black posterizes into rings on an OLED). Ward, on white, keeps its
   light-ground shadow. */
.shot {
  display: block; position: relative; overflow: hidden;
  border-radius: var(--r-object);
  border: 1px solid var(--edge-dark);
  background: var(--near-black);
}
.shot img { width: 100%; height: auto; aspect-ratio: var(--ar); object-fit: cover; object-position: top; }

.shot--cronimet  { --ar: 1000 / 560; }
/* Opera, desktop: rows 124-1076 of the 1440-wide capture (hero, black band,
   the three cards, black), so the half that breaks into the red is the band
   and the cards and the seam is black on red. 952 rows at 1440 is 841 tall
   at 1272; the site's hero cannot be shown above the midline in anything
   shorter (SPEC.md section 3). */
.shot--opera     { --ar: 1440 / 952; }
.shot--wardcedar {
  --ar: 760 / 471; border-color: var(--edge-light);
  --shadow: 0 40px 80px rgba(15,15,15,0.16), 0 10px 24px rgba(15,15,15,0.08);
  box-shadow: var(--shadow);
}
@media (max-width: 767px) {
  /* Phone: Cronimet and Opera share one ratio, so they are exactly the same
     height at every phone width. 906 rows of a 1440-wide capture: Cronimet's
     crop then ends in its off-white strip (rows 900-939), and the Opera phone
     crop (its own file, from the taller capture) ends inside the three card
     photos (rows 713-913), above their titles. */
  .shot--cronimet, .shot--opera { --ar: 1440 / 906; }
  .shot--wardcedar { --shadow: 0 24px 48px rgba(15,15,15,0.16), 0 6px 14px rgba(15,15,15,0.08); border-radius: 14px; }
}
/* Phone: a small shadow under the object, exact values from Ed (2026-09-24),
   nothing larger; the link sits 16px under the object like the captions. */
@media (max-width: 430px) {
  .shot--wardcedar { --shadow: 0 10px 20px rgba(15,15,15,0.12), 0 2px 6px rgba(15,15,15,0.06); }
}

/* Captions under the objects: numeral, client, one line, then the link. */
.caption { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.caption__row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 16px; }
.caption__num  { font: 600 14px/1.5 var(--body); color: var(--gold); }
.caption__name { font: 600 17px/1.5 var(--display); color: var(--white); }
.caption__desc { font: 400 14px/1.5 var(--body); color: var(--gray-3); }
.caption__link { font-size: 14px; }
.is-red .caption__num, .is-red .caption__desc { color: var(--white); }
/* Phone: the caption sits 16px under its own object with at least 64px to
   whatever follows, so it belongs to the image above it. Inside: 4px from
   the name line to the description, 6px from the description to the link,
   description line-height 1.4. (The 16px and 64px are set where each
   caption sits: .hero__caption, .hero__break and .act below.) */
@media (max-width: 767px) {
  .caption { gap: 6px; }
  .caption__row { gap: 4px 12px; }
  .caption__num, .caption__desc, .caption__link { font-size: 13px; }
  .caption__name { font-size: 15px; }
  .caption__desc { flex-basis: 100%; line-height: 1.4; }
}

/* Button plus text link, side by side; stacked and full width on the phone. */
.actions { display: flex; align-items: center; gap: 28px; margin-top: 8px; }
@media (max-width: 767px) {
  .actions { flex-direction: column; align-items: stretch; gap: 14px; margin-top: 4px; }
  .actions .link { text-align: center; padding: 8px; }
}

/* ----------------------------------------------------------------- hero -- */
/* Dark. Text left on the margin, the Cronimet object to the right leaving
   the page (30% of its width past the edge at 1440), the 01 caption under
   it on its left edge, then the MBA object breaking down into the red act by
   half its height. The masthead above paints its own dark ground. */
.masthead { background: var(--near-black); }

.hero {
  position: relative; z-index: 2;
  background: var(--near-black); color: var(--white);
  overflow-x: clip;
  /* flow-root: the MBA object's negative bottom margin must shorten the hero
     (so the object hangs over the red) rather than collapse through the
     hero's bottom edge and pull the red act up under the hero's own ground. */
  display: flow-root;
  /* Object width scales with the viewport (1000 at 1440), capped at 1000.
     Its left edge is 70% of its width in from the right, but never closer
     than 611px so the headline at its smallest step keeps ~40px of air. */
  --obj-w: min(1000px, 69.44%);
  --obj-left: max(100% - var(--obj-w) * 0.7, 611px);
}
/* Above 1920 the object anchors to the content column, not the viewport:
   its left edge keeps the offset it has at 1920 (896px inside the column's
   left edge, which is half the viewport plus 260px), and it grows with the
   viewport so the same 70% of it is on screen. Continuous at 1920 (1220 and
   1000 either way). SPEC.md section 5, the hero object crop rule. */
@media (min-width: 1921px) {
  .hero { --obj-left: calc(50% + 260px); --obj-w: calc((50% - 260px) / 0.7); }
}

.hero__stage { display: grid; grid-template-columns: 100%; padding-top: 114px; }

.hero__text {
  /* Shares the cell with the object; start-justified so its box is only as
     wide as the text and never sits over the object's link. */
  grid-area: 1 / 1; align-self: center; justify-self: start;
  position: relative; z-index: 2; top: -6px;   /* the board sits the block 6px above true centre */
  display: flex; flex-direction: column; gap: 28px;
  padding-left: var(--edge);
}
.hero h1 {
  max-width: 9em;
  font: 700 76px/1.06 var(--display); letter-spacing: -2px;
}
.hero__red { color: var(--red); }
.hero__sub { max-width: 26em; font: 400 19px/1.6 var(--body); color: var(--gray-4); }

.hero__object {
  grid-area: 1 / 1; position: relative; z-index: 1;
  width: var(--obj-w); margin-left: var(--obj-left);
}
/* The 01 caption: its left edge on the object's left edge, its right edge
   on the content column's right edge (the edge the Opera object's right
   edge sits on below it), so it stops where the column stops even though
   the object above it runs off the page. Above 1920 the object's left
   edge moves with the column (above), so the caption stays 376px wide. */
.hero__caption {
  grid-area: 2 / 1; justify-self: end;
  margin-top: 28px; margin-right: var(--edge);
  width: calc(100% - var(--obj-left) - var(--edge));
}

/* 02 object (Opera): content width, then pulled down over the red act by
   exactly half its height (--half, above). */
.hero__break {
  position: relative; z-index: 3;
  margin: 112px var(--edge) calc(-1 * var(--half));
}

/* Headline steps down before it can touch the object (SPEC.md section 6). */
@media (max-width: 1439px) { .hero h1 { font-size: 68px; } }
@media (max-width: 1310px) { .hero h1 { font-size: 60px; } }

/* 768 to 1023: stacked. Text, then the object at content width, no crop. */
@media (max-width: 1023px) {
  .hero__stage { padding-top: 72px; }
  .hero__text { top: 0; padding-inline: var(--margin); }
  .hero__object { grid-area: 2 / 1; width: auto; margin: 36px var(--margin) 0; }
  .hero__caption { grid-area: 3 / 1; justify-self: auto; width: auto; margin: 28px var(--margin) 0; }
  .hero__break { margin-top: 64px; }
}
@media (max-width: 767px) {
  /* Masthead is 62 tall here (18 + the 44px toggle); text starts at 120. */
  .hero__stage { padding-top: 58px; }
  .hero__text { gap: 20px; }
  .hero h1 { font-size: 46px; line-height: 1.08; letter-spacing: -1px; }
  .hero__sub { font-size: 16px; }
  .hero__caption { margin-top: 16px; }   /* caption 16 under its object */
  .hero__break { margin-top: 64px; }     /* and 64 before the next object */
}
/* Below 375 "Hard problems." no longer fits on one line at 46px. */
@media (max-width: 374px) {
  .hero h1 { font-size: 42px; }
}

/* -------------------------------------------------------------- red act -- */
.act {
  position: relative; z-index: 1;
  background: var(--lava-red-band); color: var(--white);
  /* Room for the 02 object's lower half, then the caption's 28px. */
  padding-top: calc(var(--half) + 28px);
  padding-bottom: 150px;
}
.act__inner { display: flex; flex-direction: column; align-items: flex-start; }
.act__line { margin-top: 150px; font: 600 52px/1.15 var(--display); letter-spacing: -1px; }
.act__link { margin-top: 30px; font-size: 16px; }
@media (max-width: 1023px) {
  .act { padding-bottom: 110px; }
  .act__line { margin-top: 100px; font-size: 40px; }
}
@media (max-width: 767px) {
  .act { padding-top: calc(var(--half) + 16px); padding-bottom: 72px; }   /* 02 caption 16 under its object */
  .act__line { margin-top: 64px; font-size: 30px; line-height: 1.25; letter-spacing: -0.5px; }
  .act__link { margin-top: 22px; }
}

/* ----------------------------------------------------------- pull quote -- */
/* White (.is-light paints it; the page under it is near-black, so the 150px
   of white above the mark is the section's own padding, not a margin). Big
   red opening mark, Ron's words, attribution, the red link; the Ward object
   on the right, whole, inside the content column with its right edge on the
   column's right edge. Nothing here leaves the page: the hero is the one
   place an object is cropped (SPEC.md, hero object crop rule). The object
   is 500px wide at 1440 and 0.3931 of the column below that, so the words
   keep the larger share of the column and read first; 80px of air between
   the text and the object. Two grid columns: the text column (the mark,
   the words, the link) and the object, which fills its --ward-w column,
   spans the text column's rows and is centered on them. */
.quote {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1fr) var(--ward-w); column-gap: 80px;
  align-content: start; align-items: start; justify-items: start;
  padding: 138px var(--edge) var(--below-baseline); min-height: 660px;   /* 150 + the board's 510 */
  /* 100% here is the grid's content box, which is the content column. */
  --ward-w: min(500px, 39.31%);
  /* The text column runs from the top of the mark's box to the baseline of
     the link. The link's line box goes on 6.5px below its baseline (Inter
     at 15px/1.5), so the link gives that back and the section keeps it as
     padding, and the roster stays where it was. */
  --below-baseline: 6.5px;
}
/* The mark's box top is the section's padding edge, the top of the text
   column. Raleway 700's opening quotation mark has a left side bearing of
   exactly 0.046875em, so the box is pulled left by that much and the ink
   itself sits on the margin, the same edge as the quote text. */
.quote__mark {
  position: absolute; left: calc(var(--edge) - 0.046875em); top: 138px;
  font: 700 260px/1 var(--display); color: var(--red);
  user-select: none; pointer-events: none;
}
.quote__body {
  grid-area: 1 / 1; position: relative; z-index: 1;
  display: flex; flex-direction: column;
  padding: 142px 0 0;
}
.quote__lead { font: 500 56px/1.12 var(--display); letter-spacing: -1.2px; color: var(--ink); text-wrap: balance; }
.quote__rest { margin-top: 26px; font: 500 26px/1.5 var(--display); color: var(--gray-1); }
.quote__who { display: flex; align-items: center; gap: 28px; margin-top: 44px; }
/* The mark holds its width: the name and title beside it are the ones that
   wrap (text-wrap: pretty on figcaption lets their line boxes ask for more
   room, which would otherwise shrink the logo on the phone). */
.quote__who img { width: 96px; height: auto; flex-shrink: 0; }
.quote__who-rule { width: 1px; height: 40px; background: var(--hairline-light); }
.quote__name { display: block; font: 600 16px/1.3 var(--body); color: var(--ink); }
.quote__title { display: block; margin-top: 4px; font: 400 15px/1.3 var(--body); color: var(--gray-2); }
/* Object is vertically centered on the text column. */
.quote__object { grid-area: 1 / 2 / 3 / 3; align-self: center; justify-self: stretch; }
.quote__link { grid-area: 2 / 1; margin: 40px 0 calc(-1 * var(--below-baseline)); font-size: 15px; }
@media (max-width: 1279px) {
  .quote__lead { font-size: 44px; }
}
/* 768 and below: the object at content width under the attribution. */
@media (max-width: 1023px) {
  .quote { display: flex; flex-direction: column; align-items: flex-start; column-gap: 0; margin-top: 0; min-height: 0; padding: 100px var(--margin) 0; }
  .quote__mark { position: static; font-size: 200px; line-height: 0.62; margin: 0 0 -60px -0.046875em; }
  .quote__body { padding: 18px 0 0; max-width: none; }
  .quote__lead { font-size: 40px; }
  .quote__rest { font-size: 22px; }
  .quote__object { align-self: auto; width: 100%; margin-top: 32px; }
  .quote__link { margin: 20px 0 0; font-size: 14px; }
}
/* Phone: the link sits 16px under the object, the way the 01 and 02
   captions sit under theirs. */
@media (max-width: 430px) {
  .quote__link { margin-top: 16px; }
}
@media (max-width: 767px) {
  .quote { padding-top: 80px; }
  .quote__mark { font-size: 150px; margin-bottom: -46px; }
  .quote__lead { font-size: 28px; line-height: 1.2; letter-spacing: -0.5px; }
  .quote__rest { margin-top: 14px; font-size: 17px; }
  .quote__who { gap: 18px; margin-top: 28px; }
  .quote__who img { width: 76px; }
  .quote__who-rule { height: 36px; }
  .quote__name { font-size: 15px; }
  .quote__title { margin-top: 2px; font-size: 13px; line-height: 1.5; }
}

/* --------------------------------------------------------------- roster -- */
/* Ten client marks in ink, five across and two down between hairlines.
   Sizes are by visual weight, from the boards, in the order of the markup:
   Pittsburgh Opera, Legionaries of Christ, Ward, Cronimet, KidsVoice,
   Make-A-Wish, MBA, Seraphic Mass Association, IDEAL, Estill. */
.roster { padding-top: 140px; --ls: 1; }
.roster__inner { display: flex; flex-direction: column; align-items: flex-start; }
.roster__title { margin-top: 22px; font: 500 34px/1.2 var(--display); letter-spacing: -0.5px; }
.roster__sub { margin-top: 10px; font: 400 17px/1.5 var(--body); color: var(--gray-2); }
.roster__grid {
  width: 100%; margin-top: 44px;
  display: grid; grid-template-columns: repeat(5, 1fr);
  border-top: 1px solid var(--hairline-light); border-bottom: 1px solid var(--hairline-light);
}
.roster__grid li { height: 132px; display: grid; place-items: center; }
.roster__grid img { width: calc(var(--wd) * var(--ls)); height: auto; }
.roster__grid li:nth-child(1)  img { --wd: 107px; --wp: 73px; }
.roster__grid li:nth-child(2)  img { --wd: 180px; --wp: 132px; }
.roster__grid li:nth-child(3)  img { --wd: 96px;  --wp: 66px; }
.roster__grid li:nth-child(4)  img { --wd: 180px; --wp: 123px; }
.roster__grid li:nth-child(5)  img { --wd: 107px; --wp: 73px; }
.roster__grid li:nth-child(6)  img { --wd: 170px; --wp: 116px; }
.roster__grid li:nth-child(7)  img { --wd: 96px;  --wp: 65px; }
.roster__grid li:nth-child(8)  img { --wd: 149px; --wp: 102px; }
.roster__grid li:nth-child(9)  img { --wd: 119px; --wp: 82px; }
.roster__grid li:nth-child(10) img { --wd: 107px; --wp: 73px; }
@media (max-width: 1279px) { .roster { --ls: 0.8; } }
@media (max-width: 1023px) { .roster { padding-top: 100px; --ls: 0.6; } }
@media (max-width: 767px) {
  .roster { padding-top: 72px; }
  .roster__title { margin-top: 18px; font-size: 26px; }
  .roster__sub { margin-top: 8px; font-size: 15px; }
  .roster__grid { margin-top: 32px; grid-template-columns: repeat(2, 1fr); }
  .roster__grid li { height: 88px; }
  .roster__grid img { width: var(--wp); }
}

/* ------------------------------------------------------------- why lava -- */
.why { padding: 140px 0 150px; }
.why__inner { display: flex; flex-direction: column; align-items: flex-start; gap: 18px; }
.why__line { font: 500 30px/1.4 var(--display); }
@media (max-width: 1023px) { .why { padding: 100px 0 110px; } }
@media (max-width: 767px) {
  .why { padding: 72px 0 80px; }
  .why__inner { gap: 14px; }
  .why__line { font-size: 22px; line-height: 1.45; }
}

/* --------------------------------------------------------------- motion -- */
/* The whole vocabulary: the hero entrance (once per session), the scroll
   reveals, and the object hover. SPEC.md section 7.

   Entrance: html.hero-enter is set by an inline script before first paint
   when the session has not seen it and motion is not reduced. The object
   rises 24px and fades over 600ms, the H1 rises 12px over 500ms starting
   80ms later. Fill mode backwards only, so once an animation ends the
   element's own hover transform works again. */
@keyframes hero-rise { from { opacity: 0; transform: translateY(24px); } }
@keyframes hero-lift { from { opacity: 0; transform: translateY(12px); } }
.hero-enter .hero__object .shot { animation: hero-rise 600ms var(--ease) backwards; }
.hero-enter .hero h1 { animation: hero-lift 500ms var(--ease) 80ms backwards; }

/* Reveals: armed only once lava.js adds .js-anim, so a blocked script never
   hides anything. Each section fades from 0.001 and rises 16px when it
   enters the viewport, once. The ten logos stagger by 40ms. */
.js-anim .reveal {
  opacity: 0.001; transform: translateY(16px);
  transition: opacity 500ms var(--ease), transform 500ms var(--ease);
}
.js-anim .reveal.is-visible { opacity: 1; transform: none; }
.js-anim .roster__grid.reveal { opacity: 1; transform: none; }
.js-anim .roster__grid li {
  opacity: 0.001; transform: translateY(16px);
  transition: opacity 500ms var(--ease), transform 500ms var(--ease);
}
.js-anim .roster__grid.is-visible li { opacity: 1; transform: none; }
.roster__grid li:nth-child(2)  { transition-delay: 40ms; }
.roster__grid li:nth-child(3)  { transition-delay: 80ms; }
.roster__grid li:nth-child(4)  { transition-delay: 120ms; }
.roster__grid li:nth-child(5)  { transition-delay: 160ms; }
.roster__grid li:nth-child(6)  { transition-delay: 200ms; }
.roster__grid li:nth-child(7)  { transition-delay: 240ms; }
.roster__grid li:nth-child(8)  { transition-delay: 280ms; }
.roster__grid li:nth-child(9)  { transition-delay: 320ms; }
.roster__grid li:nth-child(10) { transition-delay: 360ms; }

/* Objects lift 4px. On dark grounds the hairline brightens; on white the
   shadow deepens by about a fifth. */
.shot { transition: transform 240ms var(--ease), border-color 240ms var(--ease), box-shadow 240ms var(--ease); }
.shot:hover, .shot:focus-visible { transform: translateY(-4px); }
.shot--cronimet:hover, .shot--cronimet:focus-visible,
.shot--opera:hover, .shot--opera:focus-visible { border-color: rgba(255, 255, 255, 0.18); }
.shot--wardcedar { --shadow-hover: 0 40px 80px rgba(15,15,15,0.19), 0 10px 24px rgba(15,15,15,0.10); }
.shot--wardcedar:hover, .shot--wardcedar:focus-visible { box-shadow: var(--shadow-hover); }
@media (max-width: 767px) {
  .shot--wardcedar { --shadow-hover: 0 24px 48px rgba(15,15,15,0.19), 0 6px 14px rgba(15,15,15,0.10); }
}
@media (max-width: 430px) {
  /* Same geometry on hover, a fifth deeper, as on desktop. */
  .shot--wardcedar { --shadow-hover: 0 10px 20px rgba(15,15,15,0.14), 0 2px 6px rgba(15,15,15,0.07); }
}

/* Reduced motion: no entrance, no rise, no lift; opacity is instant. */
@media (prefers-reduced-motion: reduce) {
  .hero-enter .hero__object .shot, .hero-enter .hero h1 { animation: none; }
  .js-anim .reveal, .js-anim .roster__grid li { opacity: 1; transform: none; transition: none; }
  .shot { transition: none; }
  .shot:hover, .shot:focus-visible { transform: none; }
}

/* ---------------------------------------------------------------- close -- */
/* Near-black, flat, continuous with the footer; the footer's hairline is the
   only seam between them. 174 above the headline (the board's 150 plus 24). */
.close { position: relative; background: var(--near-black); color: var(--white); }
.close__inner {
  position: relative;
  display: flex; flex-direction: column; align-items: flex-start; gap: 28px;
  padding-top: 174px; padding-bottom: 150px;
}
.close h2 { font: 700 64px/1.08 var(--display); letter-spacing: -2px; }
.close__sub { font: 400 19px/1.6 var(--body); color: var(--gray-4); }
.close .actions { align-self: stretch; }
@media (max-width: 1023px) {
  .close__inner { padding-top: 134px; padding-bottom: 110px; }
  .close h2 { font-size: 48px; }
}
@media (max-width: 767px) {
  .close__inner { gap: 20px; padding-top: 104px; padding-bottom: 80px; }
  .close h2 { font-size: 35px; line-height: 1.1; letter-spacing: -1.2px; }
  .close__sub { font-size: 16px; }
}
