/* ============================================================================
   Planning Engineered — motion system
   ----------------------------------------------------------------------------
   One stylesheet for every scroll-driven and hover-driven effect on the site.
   Paired with /pe-motion.js, which owns the single scroll loop.

   Colours here are the DESIGN.md master values. Brass #C2A15C is a DARK-ground
   colour (2.30:1 on cream); anything brass on a light ground uses the darkened
   weight #7F6530 (5.16:1). Never introduce a brand hex that is not in this
   block — it is mirrored from 00_Resources/DESIGN.md.

   Every effect degrades to a static, fully-visible page when JavaScript does
   not run or when the reader asks for reduced motion. Nothing here is load-
   bearing for content.
   ========================================================================== */

:root {
  --pe-brass:        #C2A15C;
  --pe-brass-light:  #7F6530;
  --pe-forest:       #2C3E2D;
  --pe-fern:         #3D5C3A;
  --pe-ink:          #1C2B1F;
  --pe-cream:        #FAF7F2;
  --pe-paper:        #F5F0E6;

  /* One easing family. Everything reads as the same hand. */
  --pe-ease:     cubic-bezier(.22, .61, .36, 1);
  --pe-ease-out: cubic-bezier(.16, 1, .30, 1);
  --pe-nav-h:    69px;
}

/* ==========================================================================
   1. Scroll progress
   A 2px brass hairline. Reads as a drafting rule filling in, not a loader.
   ========================================================================== */
.pe-progress {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  height: 2px; pointer-events: none;
}
.pe-progress__bar {
  position: absolute; inset: 0;
  transform-origin: 0 50%; transform: scaleX(0);
  background: linear-gradient(90deg, rgba(194,161,92,.55), var(--pe-brass) 55%, #E0AE3C);
  will-change: transform;
}
.pe-progress__head {
  position: absolute; top: -2px; left: -3px;
  width: 6px; height: 6px; border-radius: 50%;
  background: #E0AE3C; box-shadow: 0 0 9px 2px rgba(194,161,92,.8);
  opacity: 0; transition: opacity .35s var(--pe-ease); will-change: transform;
}
.pe-progress.is-on .pe-progress__head { opacity: 1; }

/* ==========================================================================
   2. Reveal on enter
   The base slide-up. Variants exist so a two-column section can converge
   toward its own centre rather than every element marching the same way.
   ========================================================================== */
.pe-reveal {
  opacity: 0;
  transform: translate3d(0, 26px, 0);
  transition: opacity .95s var(--pe-ease-out), transform .95s var(--pe-ease-out);
}
.pe-reveal.is-in { opacity: 1; transform: none; }

.pe-reveal--left   { transform: translate3d(-34px, 0, 0); }
.pe-reveal--right  { transform: translate3d(34px, 0, 0); }
.pe-reveal--rise   { transform: translate3d(0, 44px, 0); }
.pe-reveal--scale  { transform: translate3d(0, 26px, 0) scale(.965); }
.pe-reveal--blur   { filter: blur(6px); transition: opacity .95s var(--pe-ease-out),
                                                    transform .95s var(--pe-ease-out),
                                                    filter .95s var(--pe-ease-out); }
.pe-reveal--blur.is-in { filter: none; }

/* ⛔ The horizontal reveal variants BLEED PAST THE VIEWPORT until they fire.
   translate3d(±34px) on an element already near the edge adds real scrollWidth,
   so the page scrolls sideways for the second before the reveal lands — and a
   phone shows that as a page that wobbles horizontally on load. Measured 2px of
   overflow at 768px. There is no room to spare below the desktop breakpoint, so
   narrow viewports converge vertically instead; the effect is unchanged where
   there is margin for it. */
@media (max-width: 1023px) {
  .pe-reveal--left, .pe-reveal--right { transform: translate3d(0, 26px, 0); }
}

/* Stagger without hand-writing a delay on every child. */
.pe-stagger > * { transition-delay: 0ms; }
.pe-stagger > *:nth-child(1) { transition-delay:  40ms; }
.pe-stagger > *:nth-child(2) { transition-delay: 130ms; }
.pe-stagger > *:nth-child(3) { transition-delay: 220ms; }
.pe-stagger > *:nth-child(4) { transition-delay: 310ms; }
.pe-stagger > *:nth-child(5) { transition-delay: 400ms; }
.pe-stagger > *:nth-child(6) { transition-delay: 490ms; }

/* ==========================================================================
   3. Line-mask headline
   Each line sits in its own overflow box and rides up from beneath it, so the
   headline sets itself the way type falls onto a page. The bottom padding is
   what keeps descenders and the ink swash from being clipped by the mask —
   the negative margin gives that space back to the layout.
   ========================================================================== */
.pe-lines .pe-line {
  display: block; overflow: hidden;
  padding-bottom: .40em; margin-bottom: -.40em;
}
.pe-lines .pe-line > span {
  display: block;
  transform: translate3d(0, 115%, 0);
  transition: transform 1.05s var(--pe-ease-out);
}
.pe-lines.is-in .pe-line > span { transform: none; }
.pe-lines.is-in .pe-line:nth-child(2) > span { transition-delay: .10s; }
.pe-lines.is-in .pe-line:nth-child(3) > span { transition-delay: .20s; }

/* ==========================================================================
   4. Hero parallax
   Five planes, each at its own rate, written by pe-motion.js. A bigger rate
   means the plane lags further behind the scroll, which reads as further away.
   The floor gradient is what makes the depth work: the layers drift past the
   section edge, and the floor dissolves that clip line into the next section's
   ground so the hero has no visible bottom.
   ========================================================================== */
/* `clip` is the right value (it does not create a scroll container, so a
   sticky descendant elsewhere on the page is unaffected). `hidden` first as
   the fallback for Safari 15, which ignores `clip` and would let the planes
   spill past the section edge. */
.pe-hero { position: relative; overflow: hidden; overflow: clip; }
.pe-layer {
  position: absolute; left: 0; right: 0;
  pointer-events: none; will-change: transform;
}

.pe-layer--grid {
  top: -12%; bottom: -22%;
  background-image:
    linear-gradient(rgba(250,247,242,.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(250,247,242,.055) 1px, transparent 1px);
  background-size: 48px 48px;
  z-index: 0;
}
/* the drafted bridge elevation — the brand's own wallpaper */
.pe-layer--contour {
  top: 40%; bottom: -22%;
  display: flex; align-items: center; justify-content: center;
  opacity: .85; z-index: 1;
}
.pe-layer--contour svg { width: min(1500px, 128%); height: auto; }

.pe-layer--glow {
  top: -20%; bottom: -20%;
  background:
    radial-gradient(ellipse 62% 72% at 78% 46%, rgba(194,161,92,.20) 0%, transparent 64%),
    radial-gradient(ellipse 55% 60% at 12% 88%, rgba(61,92,58,.55) 0%, transparent 70%);
  z-index: 2;
}
/* Far ridge. Sits behind the elevation, ahead of the grid. Low contrast on
   purpose — a distant plane that competes for attention stops reading as
   distant. */
.pe-layer--ridge {
  left: -4%; right: -4%; bottom: 118px; top: auto; height: 260px;
  opacity: .5; z-index: 1;
}
.pe-layer--ridge svg { width: 100%; height: 100%; display: block; }

/* NEAR BRIDGE DECK — the occluding foreground.
   Two tones and a brass hairline: deck slab over girder web. That is enough to
   read as structure at a glance, and being pure gradient it is identical at
   390px and 1440px. The element extends far BELOW the section edge so that
   climbing never uncovers the floor — `height` minus `bottom` is what is
   actually visible at rest, and that is the number to tune. */
.pe-layer--girder {
  left: 0; right: 0; bottom: -300px; top: auto; height: 386px;   /* 86px visible at rest */
  z-index: 8;
  /* Top to bottom: brass hairline, the deck slab, a flange lip, then the web
     with vertical stiffeners every 120px. The ribs are the difference between
     "a dark band" and "a girder" — measured before they were added, the web
     was ~110px of uniform #131C0E on desktop and read as dead space. They are
     4.5% cream, so they register as texture at reading distance and vanish
     at a glance; the stats card floats over the middle of them. */
  background:
    repeating-linear-gradient(90deg,
      rgba(250,247,242,.045) 0, rgba(250,247,242,.045) 1px,
      transparent 1px, transparent 120px) 60px 31px / 100% 100% no-repeat,
    linear-gradient(180deg,
      rgba(194,161,92,.85) 0, rgba(194,161,92,.85) 2px,
      #1B2718 2px, #1B2718 30px,
      rgba(194,161,92,.22) 30px, rgba(194,161,92,.22) 31px,
      #131C0E 31px, #131C0E 100%);
  box-shadow: 0 -18px 30px -10px rgba(0,0,0,.45);
}

/* The stats card sits over the girder's lower edge — the deck carrying the
   load, literally. `z-index` must beat the girder's 8; the section is not a
   stacking context so both resolve against the root. */
/* ⚠️ Margins and padding live HERE, not in utility classes: the compiled
   tailwind.min.css is purged to what the pages used at build time, so a new
   `-mt-12` in the markup is silently no class at all. Measured on the first
   pass — the card sat flush under the hero with no overlap and no error. */
.pe-stats-wrap { margin-top: -40px; }
.pe-stats {
  position: relative; z-index: 10;
  padding: 32px 24px;
  box-shadow: 0 34px 60px -34px rgba(28,43,31,.55), 0 2px 0 rgba(255,255,255,.6) inset;
}
@media (min-width: 640px) { .pe-stats { padding: 32px 40px; } }
@media (min-width: 768px) { .pe-stats-wrap { margin-top: -48px; } }

/* Narrower viewports: the planner page fills most of the hero, so the same
   86px band would sit on the copy rather than under it. A hint of depth, not
   a slab. */
@media (max-width: 767px) {
  .pe-layer--girder { bottom: -300px; height: 372px; }   /* 72px visible */
  .pe-layer--ridge  { bottom: 74px; height: 190px; opacity: .38; }
}

/* dissolves the hero into whatever ground follows it */
.pe-layer--floor {
  left: 0; right: 0; bottom: -1px; top: auto; height: 210px;
  background: linear-gradient(180deg,
    rgba(20,30,18,0) 0%, rgba(20,30,18,.34) 55%, rgba(20,30,18,.72) 100%);
  z-index: 6;
}
.pe-hero__content { position: relative; z-index: 4; will-change: transform; }

/* ---- Generated heroes (apply_motion.py) ----------------------------------
   Every other hero page gets grid + ridge + glow, and a floor on a dark
   ground, written by the generator. No girder and no bridge elevation: those
   are the homepage's own set piece. `--plain` puts the ridge on the section
   edge (there is no girder for it to sit above) and drops the floor UNDER the
   copy — a short hero ends in its CTA, and the homepage floor at z 6 would
   dim it. */
.pe-hero--plain .pe-layer--ridge { bottom: -4px; }
.pe-hero--plain .pe-layer--floor { height: 140px; z-index: 3; }
@media (max-width: 767px) {
  .pe-hero--plain .pe-layer--ridge { bottom: -4px; height: 150px; opacity: .38; }
}
/* The same planes on paper. Ink grid, brass glow, the ridge hazed further
   back — a forest ridge at .5 on paper reads as a printed band, not distance. */
.pe-hero--light .pe-layer--grid {
  background-image:
    linear-gradient(rgba(28,43,31,.06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(28,43,31,.06) 1px, transparent 1px);
}
.pe-hero--light .pe-layer--glow {
  background:
    radial-gradient(ellipse 62% 72% at 78% 46%, rgba(194,161,92,.22) 0%, transparent 64%),
    radial-gradient(ellipse 55% 60% at 12% 88%, rgba(61,92,58,.14) 0%, transparent 70%);
}
.pe-hero--light .pe-layer--ridge { opacity: .16; }
@media (max-width: 767px) { .pe-hero--light .pe-layer--ridge { opacity: .12; } }

/* The scroll cue. Unused on the homepage — see the note in the commit. Kept
   for a page with solid ground under it; on a gradient there is no colour
   that reads across the whole band. Stops the moment the reader scrolls. */
.pe-cue {
  position: absolute; z-index: 7; left: 50%; bottom: 14px;
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  font-size: 10px; letter-spacing: .22em; text-transform: uppercase;
  color: rgba(44,62,45,.55); pointer-events: none;
  transition: opacity .5s var(--pe-ease);
}
.pe-cue__rail {
  width: 1px; height: 42px; overflow: hidden;
  background: rgba(44,62,45,.16);
}
.pe-cue__rail::after {
  content: ''; display: block; width: 1px; height: 16px;
  background: var(--pe-brass);
  animation: pe-cue-run 2.1s var(--pe-ease) infinite;
}
@keyframes pe-cue-run {
  0%   { transform: translateY(-16px); opacity: 0; }
  35%  { opacity: 1; }
  100% { transform: translateY(42px); opacity: 0; }
}
.pe-cue.is-gone { opacity: 0; }

/* ==========================================================================
   5. Cards
   Supersedes .card-lift. Three things move together on hover and that is what
   reads as "considered" rather than "animated": the card rises, a brass
   hairline resolves out of the aged border, and a low sheen crosses the face.
   ========================================================================== */
.pe-card {
  position: relative; isolation: isolate;
  transition: transform .55s var(--pe-ease-out),
              box-shadow .55s var(--pe-ease-out),
              border-color .4s var(--pe-ease);
}
.pe-card::after {
  content: ''; position: absolute; inset: 0; z-index: 3;
  border-radius: inherit; pointer-events: none;
  background: linear-gradient(115deg, transparent 32%, rgba(250,247,242,.16) 48%, transparent 62%);
  opacity: 0; transform: translateX(-24%);
  transition: opacity .5s var(--pe-ease), transform .85s var(--pe-ease-out);
}
.pe-card:hover,
.pe-card:focus-visible {
  transform: translateY(-7px);
  box-shadow: 0 26px 48px -22px rgba(28,43,31,.32), 0 2px 6px -2px rgba(28,43,31,.10);
}
.pe-card:hover::after,
.pe-card:focus-visible::after { opacity: 1; transform: translateX(24%); }
.pe-card:hover  { border-color: rgba(194,161,92,.55); }

/* on forest cards the sheen has to be lighter or it reads as a smear */
.pe-card--dark::after {
  background: linear-gradient(115deg, transparent 32%, rgba(194,161,92,.14) 48%, transparent 62%);
}
.pe-card--dark:hover {
  box-shadow: 0 30px 56px -24px rgba(28,43,31,.55);
  border-color: rgba(194,161,92,.45);
}

/* the arrow that follows the eye out of the card */
.pe-card .pe-go { transition: gap .35s var(--pe-ease); }
.pe-card:hover .pe-go { gap: .75rem; }

/* media inside a card breathes in slightly rather than zooming */
.pe-card__media { overflow: hidden; }
.pe-card__media img {
  transition: transform 1.1s var(--pe-ease-out), filter .6s var(--pe-ease);
  will-change: transform;
}
.pe-card:hover .pe-card__media img { transform: scale(1.045); }

/* An element that is BOTH a reveal and a card has to declare both transitions
   in one rule. `transition` is not additive: the later of .pe-reveal and
   .pe-card wins outright, and since .pe-card is declared second its list has
   no `opacity` — so a revealing card would slide in with its opacity snapping
   from 0 to 1 in a single frame. Slow while it arrives, snappy once it is in. */
.pe-reveal.pe-card {
  transition: opacity .95s var(--pe-ease-out),
              transform .95s var(--pe-ease-out),
              box-shadow .55s var(--pe-ease-out),
              border-color .4s var(--pe-ease);
}
.pe-reveal.pe-card.is-in {
  transition: opacity .45s var(--pe-ease-out),
              transform .55s var(--pe-ease-out),
              box-shadow .55s var(--pe-ease-out),
              border-color .4s var(--pe-ease);
}

/* Keep the old hook working — several pages still ship .card-lift. */
.card-lift { transition: transform .45s var(--pe-ease-out), box-shadow .45s var(--pe-ease-out); }
.card-lift:hover { transform: translateY(-5px); box-shadow: 0 20px 40px -20px rgba(44,62,45,.28); }

/* ==========================================================================
   6. Sticky deck stack
   Desktop only. Each card sticks under the nav; as the next one climbs over
   it, the one beneath scales down and dims, so the stack reads as depth
   rather than as a list scrolling by. Every width stacks; a phone gets a
   shorter card, not a different behaviour.
   ========================================================================== */
.pe-decks { position: relative; }
/* ⚠️ THE STACK IS EVERY WIDTH NOW, AND THE PIN OFFSET LIVES IN TWO FILES.
   `top` below is the sticky offset; pe-motion.js reads the same rects to
   write scale/recede. Until 2026-09-02 the stack was gated at 768px and a
   phone got a plain list — "make sure mobile is also getting the same
   treatment" (Luis) is why it is not any more. What changed for a phone is
   the CARD, not the stack: media on top at a fixed 176px, tighter copy, and a
   pin at 88px (the 77px nav plus a lip) so a whole card (media + copy + CTA) fits under the nav on an
   844px screen with ~40px to spare. Measured card heights are in the commit.
   ⛔ A pinned card taller than the viewport hides its own CTA for ever — the
   next card climbs over before the bottom is ever on screen. Keep <=740px. */
/* No `perspective` on the stack. It was here to foreshorten the receding card,
   and it did — along with degrading the type on every card it applied to. A 2D
   scale reads as the same recession and keeps the text crisp. */
.pe-deck {
  /* ⛔ display:block/flex is LOAD-BEARING and it is not cosmetic.
     A deck is an <a>. Left to `display: inline`, its background painted
     around thin inline fragments instead of behind its block children — the
     section ground showed straight through the card. On the forest deck that
     put cream text on #F5F0E6 paper and made it genuinely unreadable on every
     phone. Measured: the card interior sampled #F5F0E6, byte-identical to the
     page background beside it. Computed style said `background:
     rgb(44,62,45)` the whole time, which is why probing the cascade missed it
     — the rule applied, the paint did not. */
  display: flex; flex-direction: column;
  position: sticky; top: 88px;
  border-radius: 24px; overflow: hidden;
  background: var(--pe-cream);
  border: 1px solid rgba(44,62,45,.12);
  transform-origin: 50% 0;
  margin-top: 0 !important;
  margin-bottom: 28px;
  box-shadow: 0 40px 80px -46px rgba(28,43,31,.55);
  will-change: transform, filter;
}
.pe-deck--dark { background: var(--pe-forest); border-color: rgba(194,161,92,.22); }
/* the last deck must not be dimmed by a card that does not exist */
.pe-deck:last-child { margin-bottom: 0; }
/* No preserve-3d / backface-visibility here: both force layer promotion,
   which costs subpixel antialiasing on every deck permanently. */

/* A pinned deck must not also lift on hover. The engine writes an inline
   scale() on every deck but the last, so a stylesheet lift would be ignored
   on those and honoured on the last one — the same three cards behaving two
   different ways. The sheen, the media breath and the arrow still move. */
.pe-deck.pe-card:hover,
.pe-deck.pe-card:focus-visible {
  transform: none;
  box-shadow: 0 40px 80px -46px rgba(28,43,31,.55);
}

.pe-deck__media { position: relative; overflow: hidden; }
.pe-deck__media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.pe-deck__media::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(28,43,31,.06), rgba(28,43,31,.34));
}

/* Phone: a cover image, then the copy. Fixed media height because the card's
   total height is the budget (see the note above), and the copy's own
   padding is set in the markup (p-6 sm:p-8). */
@media (max-width: 767px) {
  .pe-deck__media { order: -1; flex: none; height: 176px; min-height: 0; }
  .pe-deck__copy  { flex: 1 1 auto; }
}

@media (min-width: 768px) {
  .pe-deck {
    top: 104px;
    display: grid; grid-template-columns: 1fr 1fr;
    min-height: 460px;
    margin-bottom: 34px;
  }
  .pe-deck--flip .pe-deck__copy { order: 2; }
  .pe-deck:last-child { margin-bottom: 0; }
}

/* ==========================================================================
   6b. Scroll-linked media
   An image with [data-parallax] drifts inside its clipping box as the box
   crosses the screen. The engine writes only `--py`; the scale that gives it
   room to move lives here, and the hover breath is a second custom property so
   the two never fight over `transform`. ⚠️ 1.14 is read by pe-motion.js as
   well (it caps the drift at the slack this scale creates) — change both.
   ========================================================================== */
@property --ps { syntax: '<number>'; inherits: false; initial-value: 1.14; }
img[data-parallax] {
  --py: 0px; --ps: 1.14;
  transform: translate3d(0, var(--py), 0) scale(var(--ps));
  transition: --ps 1.1s var(--pe-ease-out), filter .6s var(--pe-ease);
  will-change: transform;
}
.pe-card:hover img[data-parallax] { --ps: 1.19; transform: translate3d(0, var(--py), 0) scale(var(--ps)); }
/* A DESIGNED image — a thumbnail with a corner wordmark, a product cover with
   a page layout — cannot give up 7% of each edge: the crop eats the very
   thing that was composed. `data-parallax-fit="tight"` keeps the drift and
   shrinks the slack to 2.5% (1.05), with a smaller breath on hover. ⚠️ Read
   by pe-motion.js as well — change both. */
img[data-parallax][data-parallax-fit="tight"] { --ps: 1.05; }
.pe-card:hover img[data-parallax][data-parallax-fit="tight"] { --ps: 1.08; }

/* Primary and secondary buttons lift a touch instead of just fading — the
   fade alone read as "disabled" on the brass. Specificity beats Tailwind's
   `hover:opacity-90` so opacity stays put. */
main a.bg-brass, main a.bg-forest, main button.bg-brass,
main a.border.border-forest, main a.border.border-cream\/15 {
  transition: transform .35s var(--pe-ease), box-shadow .35s var(--pe-ease),
              background-color .25s, border-color .25s, color .25s;
}
main a.bg-brass:hover, main a.bg-forest:hover, main button.bg-brass:hover,
main a.border.border-forest:hover, main a.border.border-cream\/15:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 24px -12px rgba(28,43,31,.55);
  opacity: 1;
}
main a.bg-brass:active, main a.bg-forest:active, main button.bg-brass:active { transform: translateY(0); }

/* ==========================================================================
   7. Scroll-linked prose focus
   The paragraph nearest the middle of the screen holds full ink; the others
   sit back. It is the reading equivalent of a shallow depth of field, and it
   is why the story section stops feeling like a wall.
   ========================================================================== */
.pe-focus { transition: opacity .45s var(--pe-ease); }

/* ==========================================================================
   8. Nav over a dark hero
   Only active on a page that opts in with data-hero-nav. At the top the bar
   is glass over the forest hero and its links invert to cream; past 40px it
   settles into the standard cream bar. Without the opt-in nothing changes,
   which is what keeps this safe on the other 130-odd pages.
   ========================================================================== */
body[data-hero-nav] nav[aria-label="Main navigation"] {
  transition: background-color .45s var(--pe-ease), border-color .45s var(--pe-ease),
              backdrop-filter .45s var(--pe-ease);
}
body[data-hero-nav] nav[aria-label="Main navigation"].pe-nav-top {
  background-color: rgba(44,62,45,.30) !important;
  border-bottom-color: rgba(250,247,242,.10) !important;
  backdrop-filter: blur(10px) saturate(1.1);
  -webkit-backdrop-filter: blur(10px) saturate(1.1);
}
body[data-hero-nav] nav.pe-nav-top #desktop-nav a,
body[data-hero-nav] nav.pe-nav-top #desktop-nav button { color: rgba(250,247,242,.82); }
body[data-hero-nav] nav.pe-nav-top #desktop-nav a:hover,
body[data-hero-nav] nav.pe-nav-top #desktop-nav button:hover { color: var(--pe-brass); }
body[data-hero-nav] nav.pe-nav-top .font-playfair { color: var(--pe-cream); }
body[data-hero-nav] nav.pe-nav-top .font-playfair .text-forest { color: var(--pe-brass); }
body[data-hero-nav] nav.pe-nav-top a[data-path="/"] img {
  filter: brightness(0) invert(1);
}
body[data-hero-nav] nav.pe-nav-top a[data-path="/contact"] {
  color: var(--pe-cream); border-color: rgba(250,247,242,.28);
}
body[data-hero-nav] nav.pe-nav-top a[data-path="/contact"]:hover {
  color: var(--pe-brass); border-color: rgba(194,161,92,.6);
}
body[data-hero-nav] nav.pe-nav-top #nav-toggle {
  color: var(--pe-cream); border-color: rgba(250,247,242,.24);
}
/* the mobile sheet drops over the hero, so it needs its own solid ground */
body[data-hero-nav] nav.pe-nav-top #mobile-menu {
  background-color: var(--pe-cream);
  border-top-color: rgba(44,62,45,.12);
}

/* ==========================================================================
   9. Count-up figures
   Tabular numerals stop the stat bar from shivering as the digits climb.
   ========================================================================== */
.pe-count { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

/* ==========================================================================
   10. Section rule
   Replaces the flat gradient bar with one that draws itself in on enter.
   ========================================================================== */
.pe-rule {
  height: 2px; transform: scaleX(0); transform-origin: 50% 50%;
  background: linear-gradient(to right, rgba(44,62,45,0), var(--pe-brass), rgba(44,62,45,0));
  transition: transform 1.3s var(--pe-ease-out);
}
.pe-rule.is-in { transform: scaleX(1); }

/* ==========================================================================
   12. Tap targets
   WCAG 2.5.8 sets 24x24 as the floor; 44x44 is the size a thumb actually hits.
   Measured by check_responsive.py on every page: the footer social icons come
   out 18x28 and 23x28, the mobile nav toggle 40x40, the nav wordmark 36x36.
   The icons are padded rather than enlarged, so the row looks identical and
   only the hit area grows.
   ⚠️ This reaches the pages that link pe-motion.css. The same footer markup is
   pasted into ~153 pages; the rest still carry the small targets, and fixing
   those means a sitewide rewrite through sync_site_footer_social.py.
   ========================================================================== */
footer [aria-label="Social media links"] a {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
  margin: -8px;                 /* keep the visual row spacing unchanged */
}
nav#nav-toggle, nav button#nav-toggle, #nav-toggle {
  min-width: 44px; min-height: 44px;
}
nav a[aria-label="Planning Engineered, home"],
nav a[aria-label="Planning Engineered home"] { min-height: 44px; }
/* mobile menu rows were 36px tall — the most-tapped list on the site */
#mobile-menu a, #mobile-menu button,
#mobile-content-menu a { min-height: 44px; display: flex; align-items: center; }
/* footer CTA buttons */
footer a[href="/newsletter"] { min-height: 44px; }

/* ==========================================================================
   11. Reduced motion
   Everything above resolves to its finished state. No transitions, no
   parallax writes (pe-motion.js checks the same query and returns early),
   no running cue.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .pe-reveal,
  .pe-reveal--left, .pe-reveal--right, .pe-reveal--rise,
  .pe-reveal--scale, .pe-reveal--blur {
    opacity: 1 !important; transform: none !important; filter: none !important;
    transition: none !important;
  }
  .pe-lines .pe-line > span { transform: none !important; transition: none !important; }
  .pe-rule { transform: scaleX(1) !important; transition: none !important; }
  .pe-card, .pe-card::after, .pe-card__media img, .card-lift { transition: none !important; }
  .pe-card:hover, .card-lift:hover { transform: none; }
  .pe-cue__rail::after { animation: none; }
  .pe-deck { position: static !important; transform: none !important; filter: none !important; }
  img[data-parallax] { transform: none !important; transition: none !important; }
  main a.bg-brass, main a.bg-forest, main button.bg-brass { transition: none !important; transform: none !important; }
}
