/* Franklin Park - hand-written overrides.
   Loads after theme.css, so anything here wins. Add future tweaks below. */

/* Prevent the hero video's scroll-driven zoom effect from creating a
   page-wide horizontal scrollbar. The hero background uses a CSS `scale`
   transform tied to a named view-timeline (an entrance "settle" zoom, plus
   a parallax zoom-out as the hero scrolls past) - in some browsers this
   scale can get stuck above 1 (observed: 1.045) instead of settling back
   to 1, which makes the video's rendered box ~30-65px wider than the
   viewport at typical desktop widths. Because `scale` (like `transform`)
   still counts toward a scrolling ancestor's overflow even though the
   video itself is absolutely positioned, that overshoot was creating
   real horizontal scroll on the whole page - visible as a gray strip
   beside the hero video, a bled-off 4th column in the process-card grid
   below it, and off-center content further down. Clipping horizontal
   overflow at the page root contains any such overshoot without touching
   the (otherwise working) zoom effect itself. */
html, body {
  /* clip, NOT hidden: overflow-x:hidden quietly turns body into a scroll
     container, and every scroll-driven fade-in (animation-timeline:view())
     then attaches to that never-moving scroller and jumps straight to its
     finished state - which killed all the on-scroll animations sitewide.
     overflow:clip clips identically but never creates a scroll container. */
  overflow-x: clip;
}

/* Belt-and-suspenders for the same class of bug: clip the hero's zooming
   background inside the hero section itself, so even a stuck/overshooting
   scale can never push other content around or poke outside the hero. */
.grid-hero-a {
  overflow: clip; /* same reasoning as above - the hero runs its own
     view-timeline animations (title reveal, background zoom) */
}

/* ============================================================
   RESPONSIVE HARDENING (2026-07-26)
   Client requirement: column layouts must step down deliberately
   (N across -> fewer across -> single stack), never wrap into
   ragged/orphan rows, and never overflow the viewport - in every
   browser. Two structural rules applied here and in theme.css:
   1. Every grid track is minmax(0,1fr) (done globally in
      theme.css + the extractor): tracks can then never exceed
      their container, making grid blowout impossible.
   2. No `repeat(auto-fit, minmax(NNNpx,1fr))` wrapping for
      item grids - those wrap "whatever fits" and happily produce
      3+1 or 5+1 orphan rows. Each item grid below gets explicit
      column counts chosen so rows always divide evenly.
   ============================================================ */

/* --- Home "Our Business Approach" (4 items): 4 -> 2x2 -> 1.
   Was repeat(auto-fit,minmax(200px,1fr)), which allowed 3+1. */
.grid-mm.variant-a .ghost-grid {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media screen and (width<=1080px) {
  .grid-mm.variant-a .ghost-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    row-gap: 60px;
  }
}
@media screen and (width<=620px) {
  .grid-mm.variant-a .ghost-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- Home "A Diverse Portfolio" (6 images): 3x2 -> 2x3 -> 1.
   Theme dropped to auto-fit minmax(295px,1fr) under 1530px (allows
   4+2 / 5+1 rows). 6 divides evenly by 3, 2, and 1. */
.grid-k.variant-a .ghost-grid,
.grid-k.variant-a .ghost-grid[class] {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media screen and (width<=900px) {
  .grid-k.variant-a .ghost-grid,
  .grid-k.variant-a .ghost-grid[class] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media screen and (width<=550px) {
  .grid-k.variant-a .ghost-grid,
  .grid-k.variant-a .ghost-grid[class] {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- About partner-logo grid (12 items): 4 -> 3 -> 2 -> 1.
   Was auto-fit/auto-fill minmax() wrapping. 12 divides evenly by
   every step, so the last row is always full. */
.grid-a.variant-b .products-items,
.grid-a.variant-b .products-items[class] {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media screen and (width<=1080px) {
  .grid-a.variant-b .products-items,
  .grid-a.variant-b .products-items[class] {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
@media screen and (width<=780px) {
  .grid-a.variant-b .products-items,
  .grid-a.variant-b .products-items[class] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media screen and (width<=480px) {
  .grid-a.variant-b .products-items,
  .grid-a.variant-b .products-items[class] {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- About "Areas of Expertise": three deliberate states.
   Wide: intro column (grid 2/6) + two feature columns (7/-2), per theme.
   <=1100px: features drop BELOW the intro as two equal columns
   (theme kept them squeezed beside the intro all the way to 768px).
   <=640px: single column. Text measure caps removed once stacked so
   columns fill evenly instead of leaving ragged empty space. */
@media screen and (width<=1100px) {
  .grid-jj.variant-a .wf-section-title-container {
    grid-column: 1/-1;
    padding-block: 0 24px;
  }
  .grid-jj.variant-a .wf-layout-container {
    grid-column: 1/-1;
    padding-block: 0 35px;
    border-top: none;
  }
  .grid-jj.variant-a .ghost-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 40px;
  }
  .grid-jj.variant-a p.wf,
  .grid-jj.variant-a .item-heading,
  .grid-jj.variant-a .item-subheading {
    max-width: none;
  }
}
@media screen and (width<=640px) {
  .grid-jj.variant-a .ghost-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}
/* Globe icon + text: lock each feature item to a consistent two-column
   grid (fixed icon column, flexible text column) with the icon aligned
   to the top of its text, at every viewport width - so the pairing is
   unambiguous and all rows sit on the same internal grid. */
.grid-jj.variant-a .wf-item {
  display: grid;
  grid-template-columns: 45px minmax(0, 1fr);
  column-gap: 20px;
  align-items: start;
}
.grid-jj.variant-a .wf-item .wf-img {
  margin-top: 2px;
}

/* Header logo: fixed display size matching the original live site
   (image file is 2x for retina screens). The export bug rendered the
   full-size PNG at 282x80; the correct size is 140x39. */
.top-nav .logo-wrap .logo-img {
  width: 140px;
  height: 39px;
  max-height: none;
}

/* Cookie consent banner (styled with the design-system cookie tokens). */
.cookie-banner {
  position: fixed;
  z-index: 998;
  inset-inline: 16px;
  bottom: 16px;
  max-width: 440px;
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  gap: 12px 18px;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  background: var(--ps-cookie-background-color, #151515);
  border: 1px solid var(--ps-cookie-border-color, #1f2228);
  color: var(--ps-cookie-text-color, #fff);
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.5;
}
.cookie-banner[hidden] { display: none; }
.cookie-banner-text { margin: 0; color: var(--ps-cookie-muted-text-color, #999da3); }
.cookie-banner-actions { display: flex; gap: 10px; margin-left: auto; }
.cookie-banner-btn {
  cursor: pointer;
  padding: 8px 14px;
  border: 1px solid var(--ps-cookie-border-color, #1f2228);
  background: transparent;
  color: var(--ps-cookie-link-text-color, #ffffffcc);
  font: inherit;
}
.cookie-banner-btn.accept {
  background: var(--ps-primary-button-background-color, #ffffffcc);
  color: var(--ps-primary-button-text-color, #000);
  border-color: transparent;
}
.cookie-banner-btn:hover { color: var(--ps-cookie-link-hover-text-color, #fff); }
.cookie-banner-btn.accept:hover {
  background: var(--ps-primary-button-hover-background-color, #fff);
  color: var(--ps-primary-button-text-color, #000);
}

/* Smallest-text audit (client request, 2026-07-23): the tiniest type-scale
   steps read too small and too low-contrast (captions, disclaimers, footer
   fine print, card body copy). Bump each smallest step up 1px as a first
   pass; low-contrast greys were fixed at the token level (tokens.css). */
:root {
  --step--5: 12px;
  --step--4: 13px;
  --step--3: 15px;
}
@media screen and (width<=830px) {
  :root { --step--4: 13px; --step--3: 15px; --step--2: 15px; }
}
@media screen and (width<=550px) {
  :root { --step--3: 13px; }
}

/* Home: "Franklin Park provides and improves..." intro statement was
   constrained to a narrow ~1/3-width column (a two-column heading+body
   layout applied to a heading-only section) - widen it on desktop so it
   isn't dwarfed by the full-width sections around it. Match it to the
   About page's equivalent post-hero statement, which uses the full
   content column with no artificial measure cap. */
.grid-ee.variant-b { --text-b-heading-column: 1 / 12; }
.grid-ee.variant-b .text-item-heading { --layout-heading-measure: none; }
@media screen and (width<=1180px) {
  .grid-ee.variant-b .text-item { --text-b-heading-column: 1 / 12; }
}
@media screen and (width<=980px) {
  .grid-ee.variant-b .text-item { --text-b-heading-column: 1 / 11; }
}

/* Home: "A Diverse Portfolio of Infrastructure Projects" image captions -
   client asked for larger, lighter (higher-contrast) caption text
   specifically, beyond the sitewide step--4 bump above. */
.grid-k .wf-caption {
  font-size: var(--step--3);
  color: #ffffff;
}

/* Leadership bios: Outlyne's built-in scroll-fade-in only knows about the
   "blog/grid item" wrapper classes (.wf-item-text, .wf-img, etc.) - the
   Leadership page's bio sections use a different "article" layout whose
   text wrappers (.article-heading-group, .article-body-wrap) aren't in
   that list, so only the photo faded in on scroll and the text just
   appeared. Extend the same fade-up treatment to the text, matching the
   timing/easing already used everywhere else (.wf-scroll-animations was
   also missing from these sections' HTML - see leadership/index.html). */
@media screen and (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .wf-scroll-animations .article-heading-group,
    .wf-scroll-animations .article-body-wrap {
      animation-duration: var(--wf-anim-duration);
      animation-name: wf-fade-up;
      animation-fill-mode: both;
      animation-range: entry 0 entry var(--wf-anim-range-distance);
      animation-timeline: view();
      animation-timing-function: var(--wf-anim-ease);
    }
  }
  @supports not (animation-timeline: view()) {
    .wf-scroll-animations .article-heading-group,
    .wf-scroll-animations .article-body-wrap {
      animation-duration: var(--wf-anim-duration);
      animation-name: wf-fade-up;
      animation-fill-mode: both;
      animation-delay: .1s;
      animation-timing-function: var(--wf-anim-ease);
      opacity: 0;
      transform: translateY(4vh);
    }
  }
}

/* Stats counter ($50B / 35+ / 50,000 / 12): the JS in site.js swaps each
   value's text as it counts up. Reserve the final width so the surrounding
   layout doesn't jump while short intermediate numbers render. */
.js-counter { display: inline-block; font-variant-numeric: tabular-nums; }

/* Bottom-of-page CTA button ("About us" / "Learn More" etc., the pattern
   used on the last section before the footer on Home/About/Leadership/
   Projects): outlined instead of filled white, with extra letter-spacing.
   (Font is already Aptos - see the --ps-cta-weight fix in tokens.css for
   why it previously looked like a different typeface.) */
/* (Round 6: outlined-white replaced by solid Franklin Blue fill.) */
.grid-b.variant-e .call-to-action-actions .theme-btn--primary {
  --btn-clr-bg: var(--franklin-blue);
  --btn-clr-bg-hover: var(--franklin-blue-dark);
  --btn-clr-border: transparent;
  --btn-clr-text: #fff;
  --btn-border-width: 0px;
  --btn-tracking: 2px;
  --btn-font-weight: 500;
}

/* Contact page: match the same outlined-button treatment as the bottom-CTA
   pattern above, all-caps label per client request. Selector matches (and
   so must equal-or-beat the specificity of) theme.css's own
   ".grid-hh .form-wrap .form-row.form-actions-row .form-btn" rule. */
.grid-hh .form-wrap .form-row.form-actions-row .form-btn {
  --btn-clr-bg: var(--franklin-blue);
  --btn-clr-bg-hover: var(--franklin-blue-dark);
  --btn-clr-text: #fff;
  background-color: var(--btn-clr-bg);
  color: var(--btn-clr-text);
  border: none;
  text-transform: uppercase;
  letter-spacing: 2.5px;
}
.grid-hh .form-wrap .form-row.form-actions-row .form-btn:hover {
  background-color: var(--btn-clr-bg-hover);
}

/* About page: partner-logo grid ("300,000 BPD Refinery" etc. under the
   white boxes) - these read as image captions, so use the body font
   (Aptos) like every other caption on the site instead of Oswald. */
.grid-a.variant-b .products-item-name {
  font-family: var(--font-body);
}

/* About page Timeline: entry headers ("Franklin Park founded") should be
   Oswald, a bit larger than the year/date beside them (both were the same
   size, and the header was set in the body font). Years stay as they are
   (already Oswald, already white). */
.grid-x.variant-a .item-subheading {
  font-family: var(--font-heading);
  font-size: var(--step-0);
  line-height: var(--step-0-lh);
}

/* Projects page: the paragraph directly under the hero video (no heading
   present in this instance of the section) should read like Home/About's
   post-hero statement - large, Oswald, white, on a wide column - instead
   of small muted body copy. */
.grid-ee.variant-d .text-item:not(:has(.text-item-heading-group)) {
  --text-d-body-column: 1 / 12;
}
.grid-ee.variant-d .text-item:not(:has(.text-item-heading-group)) .text-item-body {
  --layout-body-measure: none; /* was capped at 72ch by the base rule */
}
.grid-ee.variant-d .text-item:not(:has(.text-item-heading-group)) .text-item-body p.wf {
  font-family: var(--font-heading);
  font-weight: 200;
  font-size: var(--step-0);
  line-height: var(--step-0-lh);
  color: #fff;
  max-width: none;
}

/* Sitewide standard for small paragraph body copy (2026-07-24): client
   picked the Home page's "All of our businesses address a vital need..."
   text (grid-eee .text-item-body, styled by theme.css's own defaults) as
   the reference - step--3 / --ps-body-text-color, Aptos - and asked to
   bring every other small paragraph in line with it, having accumulated
   several different sizes across earlier rounds of fixes. Round 2 had
   bumped the Projects and Leadership paragraphs below up to step--1 for
   legibility; this supersedes that with the client's chosen reference
   size instead. Weight updated 400->300 in round 9's type consolidation
   (see "TYPE CONSOLIDATION" below). */
.grid-k.variant-e .section-description p.wf,
.grid-ee.variant-d .text-item:has(.text-item-heading-group) .text-item-body p.wf,
.grid-cc.variant-c .article-body .ec-p-wf,
.grid-cc.variant-c .article-body p.wf,
.grid-cc.variant-c .article-body ul,
.grid-cc.variant-c .article-body ol,
.grid-hh.variant-a .section-description p.wf {
  font-size: var(--step--3);
  line-height: var(--step--3-lh);
  color: var(--ps-body-text-color);
  font-weight: 300;
}
.grid-k.variant-e .section-description p.wf {
  max-width: 60ch;
}

/* (The earlier round-3 "Areas of Expertise" stacking block that lived here
   was superseded on 2026-07-26 by the RESPONSIVE HARDENING rules near the
   top of this file: stack below 1100px as TWO equal columns, single column
   only below 640px, with a fixed 45px icon column inside each item.) */

/* About page: partner-logo grid captions ("300,000 BPD Refinery" etc.)
   sat a full 24px below their white boxes - noticeably further than
   Home's photo captions, which sit tucked right at the image edge.
   Tighten the gap to read the same way. (A literal overlay like Home's
   would obscure the logos on these solid white cards, so this matches
   the visual closeness rather than the exact overlay technique.) */
.grid-a.variant-b .products-item-card {
  gap: 8px;
}

/* About page Timeline: entry headers must stay in the same light Oswald
   weight used for every large headline sitewide - never bold. (The
   article-body prose rules elsewhere on the site default headings to a
   bold weight; without this override that bold default would apply here
   too since .item-subheading is rendered as an h4.) */
.grid-x.variant-a .item-subheading {
  font-weight: 200;
}

/* About page: the category list at the top ("Renewable and conventional
   power generation" etc.) and the Timeline entry headers should match the
   size of the "Areas of Expertise" heading lower on the page. */
.grid-ii.variant-a .item-heading {
  font-size: var(--step-0);
  line-height: var(--step-0-lh);
}
.grid-x.variant-a .item-subheading {
  font-size: var(--step-0);
  line-height: var(--step-0-lh);
}

/* ============================================================
   ROUND 5 (2026-07-26, client feedback on staging)
   ============================================================ */

/* About "Areas of Expertise", stacked mode: the intro paragraph kept its
   61ch reading measure after the columns dropped below it, so it looked
   narrower than the grid underneath. Match it to the full column width. */
@media screen and (width<=1100px) {
  .grid-jj.variant-a .section-description,
  .grid-jj.variant-a .section-description p.wf {
    max-width: none;
    max-inline-size: none;
  }
}

/* Nav (desktop + mobile menu): styled like the ATVOS "LEARN MORE" text
   button - Aptos semibold, uppercase, 1.2px tracking - at 14px. The
   current page renders bold (700) at full opacity; both nav partials
   emit .current-link + aria-current via the page.url comparison. The
   mobile menu shares the exact same treatment, just stacked. */
.top-nav nav.main-nav a.primary-link,
.mobile-nav-popover a.ham-link {
  font-size: 14px;
  line-height: 1.4;
  font-weight: 500;
  letter-spacing: 2px;
  text-transform: uppercase;
}
/* Specificity bumped past theme.css's token-driven
   ".article-wrap .nav-wrap nav.main-nav .header-meta-link" rule. */
.top-nav .nav-wrap nav.main-nav a.primary-link.current-link,
.mobile-nav-popover .hamburger-nav a.ham-link.current-link {
  font-weight: 700;
  letter-spacing: 2px;
  opacity: 1;
}

/* Home stat counters: sized to match the "Engineering Mindset" column
   heads (step-1; the round-5 step-2 read too large on review). */
.grid-ii.variant-c .item-heading {
  font-size: var(--step-1);
  line-height: var(--step-1-lh);
}
.grid-ii.variant-c .wf-item {
  padding: 24px 28px;
}

/* Stat bars slide in from the left (staggered), then the counters run
   (sequenced in site.js via the .bar-in class). Hidden state is scoped
   to html.js so the bars are simply visible if JS doesn't run; reduced
   motion skips the slide entirely. */
@media screen and (prefers-reduced-motion: no-preference) {
  html.js .grid-ii.variant-c .wf-item {
    opacity: 0;
    transform: translateX(-64px);
    transition:
      opacity 0.45s ease-out,
      transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
    transition-delay: calc(var(--wf-item-index, 0) * 0.12s);
  }
  html.js .grid-ii.variant-c .wf-item.bar-in {
    opacity: 1;
    transform: none;
  }
}

/* Home "Our Business Approach": column text flush left / rag right
   (headline + subhead above stay centered), and close up the oversized
   gap (~175px) between the headline block and the four column heads. */
.grid-mm.variant-a .wf-item {
  align-items: flex-start;
}
.grid-mm.variant-a .wf-item-text,
.grid-mm.variant-a .motion-subtitle,
.grid-mm.variant-a .ghost-item-title-wrap {
  align-items: flex-start;
}
.grid-mm.variant-a .item-heading,
.grid-mm.variant-a .item-subheading {
  text-align: left;
}
.grid-mm.variant-a .wf-section-title-container {
  margin-block: 2.5em 0;
}
.grid-mm.variant-a .wf-layout-container {
  margin-block: 0.75em 2.5em;
}

/* ============================================================
   ROUND 6 (2026-07-26, second client pass)
   ============================================================ */

/* Alternating section backgrounds ("Franklin" system): sections step
   dark-gray / black instead of using divider lines. The bg-black /
   bg-gray classes are assigned in each page template BOTTOM-UP - the
   last content section above the footer is always black, alternating
   upward. NOTE: this system did NOT survive the Outlyne export (every
   section shipped as one flat #0a0a0a; see project notes) - rebuilt
   here from the client's description. Colors are tokens in tokens.css. */
.article-wrap.bg-black > .layout-section {
  background: var(--section-black);
}
.article-wrap.bg-gray > .layout-section {
  background: var(--section-gray);
}

/* Post-hero statements on Home/About/Projects: one shared size (step-0,
   the middle of the three sizes that had accumulated). Home also had
   75px of padding above and below - halved. About's was step-1 (39px). */
.grid-ii.variant-a .wf-section-title-container .section-title {
  font-size: var(--step-0);
  line-height: var(--step-0-lh);
}
.grid-ee.variant-b .root-element {
  padding-block: 40px;
}
/* Projects: theme re-indents the no-heading intro to grid column 5 below
   1180px by setting the column var on .text-item-content (closer to the
   consumer than our earlier .text-item-level override, so it won at
   those widths - that was the mystery indent). Pin it at the content
   level so the text stays flush left at every width. */
.grid-ee.variant-d .text-item:not(:has(.text-item-heading-group)) .text-item-content {
  --text-d-body-column: 1 / 12;
}

/* "Our Business Approach" column titles: down to step-0, matching the
   "ATVOS: Featured Example" section title below. */
.grid-mm.variant-a .item-heading {
  font-size: var(--step-0);
  line-height: var(--step-0-lh);
}

/* Leadership job titles ("Chief Executive Officer" etc.): Oswald at the
   same size as Home's "Discovery" column heads (step-05), keeping the
   existing gray. */
.grid-cc.variant-c .article-deck {
  font-family: var(--font-heading);
  font-weight: 200;
  font-size: var(--step-05);
  line-height: var(--step-05-lh);
}

/* About capability list ("Renewable and conventional power generation",
   ...): descenders were clipped by a leftover editor safeguard
   (-webkit-line-clamp:1 + overflow:hidden truncated every heading to
   one line, cutting g/p/y tails). Remove the clamp, then rule the list
   with thin Franklin Blue lines: above the first item, between items,
   and below the last. The 75px grid row-gap collapses so the rules and
   items read as one list; items carry the spacing instead. */
.grid-ii.variant-a .item-heading {
  display: block;
  overflow: visible;
  -webkit-line-clamp: unset;
  -webkit-box-orient: unset;
}
.grid-ii.variant-a .root-element-inner {
  row-gap: 0;
}
.grid-ii.variant-a .wf-section-title-container {
  margin-bottom: 44px;
}
.grid-ii.variant-a .wf-item {
  border-top: 1px solid var(--franklin-blue);
  padding-block: 20px;
  margin-block: 0;
}
.grid-ii.variant-a .wf-item:last-child {
  border-bottom: 1px solid var(--franklin-blue);
}

/* Timeline: separators solid gray instead of dashed; the heavy bar
   under "Franklin Park Timeline" becomes Franklin Blue. */
.grid-x.variant-a .wf-item {
  border-bottom-style: solid;
}
.grid-x.variant-a .wf-section-title-container {
  border-bottom-color: var(--franklin-blue);
}

/* Hamburger: three equal bars that animate into an X when the menu opens.
   The theme shipped an uneven two-bar mark (34px + 23px, third bar hidden)
   whose "X" was built from bars a+b. Here all three bars are the same
   width and evenly spaced; opening the menu rotates a and c into the X
   and fades b out. The theme's own open-state hook
   (body:has(.mobile-nav-popover.popover-open)) drives both the header
   button and the panel's close button, and .hamburger-btn .line already
   carries transition:all .25s - so the morph animates in both directions.
   Open-state selectors below intentionally mirror the theme's selector
   exactly, so they win on source order rather than a specificity war. */
.hamburger-btn .line.a,
.hamburger-btn .line.b,
.hamburger-btn .line.c {
  width: 28px;
  height: 2px;
}
.hamburger-btn .line.a { top: 17px; }
.hamburger-btn .line.b { top: 24px; }
.hamburger-btn .line.c { display: block; top: 31px; }

:is(body:has(.mobile-nav-popover.popover-open) header.article-wrap,
    body:has(.mobile-nav-popover.popover-open) .mobile-nav-popover)
  .hamburger-btn .line.a {
  top: 24px;
  transform: rotate(45deg);
}
:is(body:has(.mobile-nav-popover.popover-open) header.article-wrap,
    body:has(.mobile-nav-popover.popover-open) .mobile-nav-popover)
  .hamburger-btn .line.b {
  top: 24px;
  opacity: 0;
  transform: scaleX(0.4);
}
:is(body:has(.mobile-nav-popover.popover-open) header.article-wrap,
    body:has(.mobile-nav-popover.popover-open) .mobile-nav-popover)
  .hamburger-btn .line.c {
  top: 24px;
  opacity: 1;
  scale: 1;
  transform: rotate(-45deg);
}

/* Mobile menu panel: slides in from the right instead of the theme's
   default grow-from-the-bottom (translateY(100%)+scale). Selectors match
   theme.css's own exactly (attribute+class, same specificity) so this
   wins on source order alone - matches the pattern already used above
   for the hamburger-to-X morph. */
[popover].mobile-nav-popover {
  transform: translateX(100%);
}
[popover].mobile-nav-popover:popover-open {
  transform: translateX(0%);
}
@starting-style {
  .mobile-nav-popover[popover]:popover-open {
    transform: translateX(100%);
  }
}

/* ============================================================
   ROUND 7 (2026-07-27, client feedback)
   ============================================================ */

/* Home: "Franklin Park provides and improves..." bumped to the next
   Oswald size up from step-0 (used elsewhere, e.g. "Engineering Mindset"). */
.grid-ee.variant-b .text-item-heading {
  font-size: var(--step-1);
  line-height: var(--step-1-lh);
}

/* About "Areas of Expertise": item text (beside the globe icons) switched
   from Oswald to the sitewide Aptos body style. */
.grid-jj.variant-a .item-heading {
  font-family: var(--font-body);
  font-weight: 300;
}

/* About capability list ("Renewable and conventional power generation"
   etc.): the theme's default scroll animation moves the text vertically
   (translateY) while the blue rule lines (a static border on the row) stay
   put, so at some scroll positions the text sits visibly off the line it
   belongs to. Slide the text in horizontally instead - same scroll-linked
   mechanism and timing as the rest of the site, just translateX instead
   of translateY, so it never crosses its own line vertically.
   Targets .motion-span, NOT .wf-item-text: on this grid variant
   .wf-item-text (and every wrapper down to .motion-span) is
   `display:contents` - a box-less pass-through element - and
   animation-timeline:view() needs an actual box to measure scroll
   progress against. .motion-span is the first real (flex) box in the
   chain, matching how the theme's own animated-elements list treats it
   as a distinct target from .wf-item-text elsewhere on the site. */
.grid-ii.variant-a .motion-span {
  animation-name: none !important;
  opacity: 1;
  transform: none;
}
@media screen and (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .grid-ii.variant-a .motion-span {
      animation-name: wf-slide-in-left !important;
      animation-duration: var(--wf-anim-duration);
      animation-fill-mode: both;
      animation-range: entry 0 entry var(--wf-anim-range-distance);
      animation-timeline: view();
      animation-timing-function: var(--wf-anim-ease);
      opacity: 0;
    }
  }
  @supports not (animation-timeline: view()) {
    .grid-ii.variant-a .motion-span {
      animation-name: wf-slide-in-left !important;
      animation-duration: var(--wf-anim-duration);
      animation-fill-mode: both;
      animation-timing-function: var(--wf-anim-ease);
      opacity: 0;
      transform: translateX(-40px);
    }
  }
}
@keyframes wf-slide-in-left {
  0% { opacity: 0; transform: translateX(-40px); }
  to { opacity: 1; transform: translateX(0); }
}

/* Projects: "Current Projects" headline matched to the size of Home's
   "A Diverse Portfolio of Infrastructure Projects" (step-1). Checked at
   every width down to the existing 820px stack breakpoint - the heading
   column has room beside the paragraph the whole way; no reflow needed. */
.grid-ee.variant-d .text-item:has(.text-item-heading-group) .text-item-heading {
  font-size: var(--step-1);
  line-height: var(--step-1-lh);
}

/* Leadership: photo spacing tightened. Space below the photo before the
   name (was a flat 34px) down to ~35%; space above the photo (the
   section's own top padding, which scales at narrower widths - reduced
   proportionally via calc() rather than a fixed px) down to ~60%. */
.grid-cc.variant-c .article-media-wrap {
  margin-block-end: 12px; /* 35% of 34px */
}
.grid-cc.variant-c .root-element {
  padding-block-start: calc(var(--root-block-padding-b) * 0.6);
}

/* ============================================================
   ROUND 8 (2026-07-27, Business Approach redesign)
   ============================================================ */

/* Home "Our Business Approach": title block flush left (was centered),
   subtitle switched from small gray Aptos to Oswald at the "Discovery"
   heading size, still gray. */
.grid-mm.variant-a .ghost-section-title-wrap,
.grid-mm.variant-a .wf-section-title-container,
.grid-mm.variant-a .motion-subtitle {
  align-items: flex-start;
}
.grid-mm.variant-a .section-title {
  text-align: left;
}
.grid-mm.variant-a h3.section-subheading {
  text-align: left;
  font-family: var(--font-heading);
  font-weight: 200;
  font-size: var(--step-05);
  line-height: var(--step-05-lh);
}

/* Column body copy: was a small, semi-bold "item-subheading" default
   (13px/600) - bring it in line with the sitewide paragraph standard. */
.grid-mm.variant-a .item-subheading {
  font-size: var(--step--3);
  line-height: var(--step--3-lh);
  font-weight: 300;
  color: var(--ps-body-text-color);
}

/* Column headings ("Economic Common Sense" etc.): theme capped these at
   14ch, which forced awkward wraps (e.g. "Sense" stranded alone on its
   own line) independent of the card's actual available width. The card
   itself now bounds the width, so the cap is unnecessary. */
.grid-mm.variant-a .item-heading {
  max-width: none;
}

/* Each column becomes a card: a bold Franklin Blue top bar - 3x the
   original weight, per review (5px -> 15px) - and a thin gray line on
   the other three sides (matching the Timeline's own item separators).
   Row items stretch to equal height (theme defaulted to content-height,
   which would make cards with longer copy taller than their neighbors)
   so all 4 cards in a row line up evenly regardless of copy length. */
.grid-mm.variant-a .ghost-grid {
  align-items: stretch;
}
.grid-mm.variant-a .wf-item {
  box-sizing: border-box;
  border-top: 15px solid var(--franklin-blue);
  border-right: 1px solid var(--layout-muted-border-color);
  border-bottom: 1px solid var(--layout-muted-border-color);
  border-left: 1px solid var(--layout-muted-border-color);
  padding: 28px 28px 32px;
}

/* Double the gap between "How We Stay On Target" and the card row
   (was 0.75em). */
.grid-mm.variant-a .wf-layout-container {
  margin-block-start: 1.5em;
}

/* Cards fade in and up ONE UNIT AT A TIME, left to right - not the
   heading/body text animating independently inside a static card
   border. The theme's default targets three separate elements inside
   the card (.wf-item-text, .motion-span, .motion-subtitle), each
   sliding up on its own timing while the card's own border never
   moves - the same "text falling out of its box" effect the client
   flagged on the About capability list, just with a translateY origin
   this time instead of the list's translateX one. Neutralize all three
   inner animations and animate .wf-item (the bordered card itself, a
   real box - display:flex, confirmed) as a single unit instead. */
.grid-mm.variant-a .wf-item-text,
.grid-mm.variant-a .motion-span,
.grid-mm.variant-a .motion-subtitle {
  animation-name: none !important;
  opacity: 1;
  transform: none;
}
/* .wf-item itself was never one of the theme's animated targets, so it
   never had --wf-stagger-index computed on it - without this, all 4
   cards would share the same animation-range and fade in together
   instead of in left-to-right sequence. */
.grid-mm.variant-a .wf-item {
  --wf-stagger-index: calc(mod(var(--wf-item-index), var(--wf-stagger-index-modulo)) + var(--wf-motion-index, 0));
}
@media screen and (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .grid-mm.variant-a .wf-item {
      animation-name: wf-fade-up !important;
      animation-duration: var(--wf-anim-duration);
      animation-fill-mode: both;
      animation-range: entry calc(var(--wf-stagger-index) * var(--wf-stagger-range-step)) entry calc(var(--wf-anim-range-distance) + var(--wf-stagger-index) * var(--wf-stagger-range-step));
      animation-timeline: view();
      animation-timing-function: var(--wf-anim-ease);
      opacity: 0;
    }
  }
  @supports not (animation-timeline: view()) {
    .grid-mm.variant-a .wf-item {
      animation-name: wf-fade-up !important;
      animation-duration: var(--wf-anim-duration);
      animation-fill-mode: both;
      animation-delay: calc(var(--wf-stagger-index) * var(--wf-stagger-delay));
      animation-timing-function: var(--wf-anim-ease);
      opacity: 0;
      transform: translateY(4vh);
    }
  }
}

/* Cards fade in and up in sequence, left to right. The theme's built-in
   scroll stagger keys off --wf-item-index modulo --wf-stagger-index-modulo
   (default 3) - with exactly 4 cards (index 0-3), a modulo of 3 makes
   card 4 collide with card 1's stagger slot instead of following card 3.
   Raising the modulo to 4 gives each of the 4 cards its own ascending
   offset, so they trigger in true left-to-right order as the row
   scrolls into view. */
.grid-mm.variant-a.wf-scroll-animations {
  --wf-stagger-index-modulo: 4;
}

/* ============================================================
   TYPE CONSOLIDATION (2026-07-27): every text role at 16px or
   below drops from weight 400 to 300; the couple of small-text
   roles that had been inheriting the Oswald-heading weight (200)
   also move to 300. --ps-body-weight (tokens.css) was flipped
   400->300 as the sitewide default; the selectors below carry
   their own LITERAL font-weight (in theme.css or earlier rounds
   of this file) that bypasses the token, so each needs its own
   override to actually pick up the change.
   ============================================================ */

/* Home "A Diverse Portfolio" image captions - theme's own .wf-caption
   base rule hardcodes weight:400 (global, every caption sitewide). */
.wf-caption {
  font-weight: 300;
}

/* Home "Discovery / Development / Financing / Operation" column body
   copy - theme hardcodes weight:400 on this specific selector. */
.grid-gg.variant-d p.wf,
.grid-gg.variant-d ul,
.grid-gg.variant-d ol {
  font-weight: 300;
}

/* About partner-logo captions ("300,000 BPD Refinery" etc.) - these
   render via .item-heading, which carries the Oswald-heading weight
   token (200) regardless of the Aptos font-family swap in round 2. */
.grid-a.variant-b .products-item-name {
  font-weight: 300;
}

/* About "Areas of Expertise" item years ("2001" etc.) - theme.css sets
   this via a 3-class compound selector on the element itself
   (.features-item-subheading.item-subheading.layout-subheading, plus the
   .grid-jj.variant-a ancestor) at font-weight:500 - specificity (0,5,0)
   beats a plain ".grid-jj.variant-a .item-subheading" override (0,3,0)
   outright regardless of source order, so the compound selector has to
   be matched exactly. */
/* Round 12: item year now matches About's Timeline year spec exactly
   (Oswald, step-1/21px, weight 200, normal tracking) - color stays the
   muted #999da3 it already inherits from --ps-item-subheading-text-color,
   unlike Timeline year which is white. */
.grid-jj.variant-a .features-item-subheading.item-subheading.layout-subheading {
  font-family: var(--font-heading);
  font-size: var(--step--1);
  line-height: var(--step--1-lh);
  font-weight: 200;
  letter-spacing: normal;
}

/* Home "ATVOS: Featured Example" body copy - this is the paragraph the
   client picked as THE reference style back in round 4 ("use this text
   size/weight/color for all small paragraphs"). Turns out no rule in
   theme.css ever actually sets font-weight for .left-right-item-body -
   it was rendering at the browser's plain default (400/"normal") the
   whole time, not via any token, so flipping --ps-body-weight alone
   can't reach it; needs its own explicit rule like everything else. */
.grid-eee :is(.left-right-item-body, .left-right-section-body) :is(.ec-p-wf, p, p.wf, ol, ul, li) {
  font-weight: 300;
}

/* Cookie banner text (13px). */
.cookie-banner {
  font-weight: 300;
}

/* ============================================================
   ROUND 10 (2026-07-27, color consolidation + refinements)
   ============================================================ */

/* Leadership bios: lightened to pure white (the lightest color the site
   uses) instead of the shared body-copy gray - carve-out from the
   sitewide paragraph rule above, which every other page still uses. */
.grid-cc.variant-c .article-body .ec-p-wf,
.grid-cc.variant-c .article-body p.wf,
.grid-cc.variant-c .article-body ul,
.grid-cc.variant-c .article-body ol {
  color: #fff;
}

/* Weight-300 text needs a touch of tracking to keep it from reading too
   light/thin at small sizes - added throughout every selector round 9's
   type consolidation set to font-weight:300. */
.grid-k.variant-e .section-description p.wf,
.grid-ee.variant-d .text-item:has(.text-item-heading-group) .text-item-body p.wf,
.grid-cc.variant-c .article-body .ec-p-wf,
.grid-cc.variant-c .article-body p.wf,
.grid-cc.variant-c .article-body ul,
.grid-cc.variant-c .article-body ol,
.grid-hh.variant-a .section-description p.wf,
.grid-jj.variant-a .item-heading,
.grid-mm.variant-a .item-subheading,
.wf-caption,
.grid-gg.variant-d p.wf,
.grid-gg.variant-d ul,
.grid-gg.variant-d ol,
.grid-a.variant-b .products-item-name,
.cookie-banner,
.grid-eee :is(.left-right-item-body, .left-right-section-body) :is(.ec-p-wf, p, p.wf, ol, ul, li),
.grid-x.variant-a .item-description p.wf,
.grid-x.variant-a .item-description ul,
.grid-x.variant-a .item-description ol,
.grid-jj.variant-a .section-description p.wf,
.footer-text {
  letter-spacing: 0.2px;
}
/* .grid-jj.variant-a .item-heading carries theme.css's own
   letter-spacing:normal!important (left over from when this element was
   still Oswald condensed titling, pre round-7) - only another !important
   declaration can beat it. */
.grid-jj.variant-a .item-heading {
  letter-spacing: 0.2px !important;
}

/* ============================================================
   ROUND 12 (2026-07-27, contrast/weight/tracking + hero title fix)
   ============================================================ */

/* Home hero title ("ON TARGET"): fixed at 112px per client request,
   capped via min() so the existing fluid clamp still shrinks it on
   narrow viewports exactly as before - only Home's hero id is targeted
   because grid-hero-a variant-b is shared by Home/About/Projects/Contact,
   each with a different-length title that still needs its own auto-fit.
   ID beats theme.css's class-only selector on specificity, no !important
   needed. --title-min/--title-curve/--title-length-max are the same
   custom properties theme.css's own clamp() resolves on this element. */
#cc4756c5-7b78-43bc-9621-c16787d74af1 .section-title {
  font-size: min(112px, clamp(var(--title-min), calc(var(--title-curve) * 1cqw / sqrt(var(--heading-length, 20))), var(--title-length-max)));
}

/* Projects hero subheading ("Featured Project: Efficient Renewable Motor
   Fuel at Scale") - sized to match Leadership's job-title spec (Oswald,
   step-05/24px, weight 200) but kept pure white rather than job title's
   muted gray, per client request. Scoped to Projects hero's unique id
   since grid-hero-a variant-b is shared by Home/About/Projects/Contact. */
#b4a4454b-2263-48c8-af41-21db8ccd6db0 .section-subheading {
  font-family: var(--font-heading);
  font-weight: 200;
  font-size: var(--step-05);
  line-height: var(--step-05-lh);
  color: #fff;
}

/* Leadership: top nav background in our dark gray instead of the sitewide
   near-black --ps-header-background-color (#0a0a0a). Overriding the custom
   property itself (rather than .root-element-inner's background directly)
   keeps every other header-driven color (link text, hamburger lines, etc.)
   correctly derived - .leadership-page ancestor class (set in base.njk)
   scopes this to Leadership only, since the header partial is shared
   sitewide. */
.leadership-page .top-nav header.article-wrap .root-element {
  --header-background-color: var(--section-gray);
}

/* Home ATVOS-featured image: theme's default 16:9 crop is WIDER than the
   source photo's own ratio (2000x1255 = 400:251, ~8:5), so background-
   size:cover was cropping the top and bottom to fill that wider box -
   cutting the workers' feet off. Matching the aspect-ratio to the
   source photo's own ratio removes vertical cropping entirely (any crop
   now falls on the sides, per client request). Scoped to this specific
   image (grid-eee is only used once, on Home) rather than the shared
   --ps-media-aspect-ratio token, which other left-right sections could
   reasonably need at 16:9. */
.grid-eee.variant-b .left-right-item-image {
  aspect-ratio: 400 / 251;
}

/* About page Timeline: the Aptos body copy under each entry ("Franklin
   Park founded by Tom Tribone...") goes back to our light gray, carved
   out of the sitewide body-text-color token (white, since round 12) -
   the Oswald year/entry-header pair above it stays white, unchanged. */
.grid-x.variant-a .item-description p.wf {
  color: #999da3;
}
