@font-face{font-family:'HNP';src:url('../fonts/HelveticaNeueLTProRoman.otf') format('opentype');font-weight:400;font-style:normal;font-display:swap;}
@font-face{font-family:'HNP';src:url('../fonts/HelveticaNeueLTProMd.otf') format('opentype');font-weight:500;font-style:normal;font-display:swap;}
@font-face{font-family:'HNP';src:url('../fonts/HelveticaNeueLTProBd.otf') format('opentype');font-weight:700;font-style:normal;font-display:swap;}
@font-face{font-family:'HNP';src:url('../fonts/HelveticaNeueLTProIt.otf') format('opentype');font-weight:400;font-style:italic;font-display:swap;}
@font-face{font-family:'HNP';src:url('../fonts/HelveticaNeueLTProMdIt.otf') format('opentype');font-weight:500;font-style:italic;font-display:swap;}
@font-face{font-family:'HNP';src:url('../fonts/HelveticaNeueLTProBdIt.otf') format('opentype');font-weight:700;font-style:italic;font-display:swap;}

/* 'HNP-UI' — HNP with balanced vertical metrics, for pill/button labels.
   HNP's own metrics are lopsided: the hhea ascender (714) sits exactly on the
   cap height (714), with a 286 descender. So a line box has zero slack above
   the caps and .286em below it, and flex-centring a label centres that lopsided
   box rather than the letters: the text lands .143em (2px at text-sm) above the
   true centre of its pill. These are the same six faces with the slack split
   evenly, ascent .857 / descent .143 — sum unchanged at 1em, so line boxes keep
   their height and only the baseline within them moves. Scoped to pills below,
   which leaves body-copy metrics (and the vertical rhythm tuned around them)
   untouched.

   The balance is baked into the font files themselves (the -UI.otf copies carry
   hhea, OS/2 typo and win metrics of 857/143, built with fontTools), not only
   into the ascent-/descent-override descriptors: Safari, desktop and iOS,
   ignores those descriptors (they ship only in Technology Preview), so with the
   descriptors alone every pill label on an iPhone rode 2px high. The
   descriptors stay as belt and braces for Chrome and Firefox. */
@font-face{font-family:'HNP-UI';src:url('../fonts/HelveticaNeueLTProRoman-UI.otf') format('opentype');font-weight:400;font-style:normal;font-display:swap;ascent-override:85.7%;descent-override:14.3%;}
@font-face{font-family:'HNP-UI';src:url('../fonts/HelveticaNeueLTProMd-UI.otf') format('opentype');font-weight:500;font-style:normal;font-display:swap;ascent-override:85.7%;descent-override:14.3%;}
@font-face{font-family:'HNP-UI';src:url('../fonts/HelveticaNeueLTProBd-UI.otf') format('opentype');font-weight:700;font-style:normal;font-display:swap;ascent-override:85.7%;descent-override:14.3%;}
@font-face{font-family:'HNP-UI';src:url('../fonts/HelveticaNeueLTProIt-UI.otf') format('opentype');font-weight:400;font-style:italic;font-display:swap;ascent-override:85.7%;descent-override:14.3%;}
@font-face{font-family:'HNP-UI';src:url('../fonts/HelveticaNeueLTProMdIt-UI.otf') format('opentype');font-weight:500;font-style:italic;font-display:swap;ascent-override:85.7%;descent-override:14.3%;}
@font-face{font-family:'HNP-UI';src:url('../fonts/HelveticaNeueLTProBdIt-UI.otf') format('opentype');font-weight:700;font-style:italic;font-display:swap;ascent-override:85.7%;descent-override:14.3%;}

/* Every pill on the site: buttons, nav links, badges, chips, category filters.
   A second download of whichever weights pills use (bold, and roman for the
   form fields) — about 30KB each, the price of metrics Safari will honour.
   `rounded-full` also lands on avatars and icon circles, where re-centring text
   there is either invisible or the same improvement. The contact form fields
   (.f-input, .f-select) are here for the same reason: a single-line field is a
   pill with corners, and with plain HNP its value and placeholder sat ~2–3px
   above the middle of the box. */
.btn-hard,.hero-btn,.nav-link,.cs-beat,.cs-chip,.cs-tag-service,.svc-chip,
.f-input,.f-select,
a[class*="rounded-full"],button[class*="rounded-full"],
div[class*="rounded-full"],span[class*="rounded-full"],li[class*="rounded-full"]{
  font-family:'HNP-UI','HNP','Helvetica Neue',Helvetica,Arial,sans-serif;
}

/* A tracked label carries its letter-spacing after its last letter too, so the
   ink of the label (or of a dot-plus-label group) sits half a tracking unit
   left of the pill's centre: .77px for .14em at 11px. The end padding of each
   tracked pill is shortened by exactly that trailing space, which puts the ink
   back in the middle without touching the start padding. The site's own chips
   do this in their own rules below; the Tailwind eyebrow pills (px-4,
   tracking-[0.14em]) get it here. Inline chips (.cs-tag-service) are .3px out
   at most and are left alone.

   Chrome also rounds a font's ascent to whole pixels and floors the
   half-leading, so an 11px label on the body's 1.5 leading (a 16.5px line)
   paints a pixel or more above centre whatever the metrics say. A 17px line
   keeps the leading whole (3px a side) and the error drops under half a pixel;
   Safari centres either way. The pill grows half a pixel. .cs-beat and
   .gl-step set the same 17px line in their own rules. Below sm the eyebrow
   pills are text-[11px]; from sm they are text-xs, whose 16px line is whole. */
[class*="rounded-full"][class*="tracking-[0.14em]"].px-4{padding-right:calc(1rem - .14em);}
@media (max-width:639px){
  [class*="rounded-full"].text-\[11px\]{line-height:17px;}
}
}

html{scroll-behavior:smooth;}
::selection{background:#FF3B00;color:#FFFBE7;}

/* headlines on phones: one size per tier (2.1rem page heroes, 1.6rem section
   headlines, set in the markup) and balanced wraps, so a long line never
   leaves one word stranded on a line of its own. */
@media (max-width:639px){
  main h1,main h2{text-wrap:balance;}
}
/* headlines up to the phone-nav breakpoint always wrap inside their column:
   the sm:whitespace-nowrap spans that hold a line together on desktop let go,
   and a word too long for the line breaks rather than running past the
   gutter, so every headline keeps the section's even left and right padding.
   The homepage hero (the one holding #heroWord) keeps its own wrapping. */
@media (max-width:767.98px){
  main :is(h1,h2,h3):not(:has(#heroWord)){max-width:100%;overflow-wrap:break-word;}
  main :is(h1,h2,h3):not(:has(#heroWord)),
  main :is(h1,h2,h3):not(:has(#heroWord)) *{white-space:normal !important;}
}

/* no scroll-triggered entrances anywhere: content renders in place. The
   .reveal / .reveal-stagger classes still sit in the markup as inert hooks. */

/* marquee */
.marquee{overflow:hidden;-webkit-mask-image:linear-gradient(90deg,transparent,black 8%,black 92%,transparent);mask-image:linear-gradient(90deg,transparent,black 8%,black 92%,transparent);}
.marquee-track{display:flex;align-items:center;gap:72px;width:max-content;animation:marquee 60s linear infinite;}
.marquee-track-reverse{animation-direction:reverse;}
/* marquees never pause: no hover state, the logo bands scroll continuously. */
@keyframes marquee{from{transform:translateX(0);}to{transform:translateX(-50%);}}

/* ── brand ticker ──
   The marquee mechanics reused for a word band: same -50% translate, same edge
   mask, same reduced-motion stop. Only the spacing changes — a logo marquee
   sets its items 72px apart, a ticker reads as one continuous line. The band
   carries the cream horizontal lockup rather than set type, so there is no
   text here to hold against a contrast floor. */
.ticker-track{gap:56px;}
/* the lockup is cropped to its own ink, so this height is the cap height. */
.ticker-logo{height:18px;width:auto;flex-shrink:0;display:block;}

/* ── creative wall (/ad-creatives) ──
   The marquee mechanics a third time, now carrying 4:5 ad statics instead of
   logos. Only two things change. The gap drops to the 24px card step: logos sit
   72px apart so each reads on its own, a wall of creative has to read as a wall.
   Tile widths are set here rather than in utilities so both rows stay in step. */
.wall-track{gap:24px;animation-duration:calc(60s * var(--wall-tiles, 11) / 11);} /* holds the shared marquee's 60s-per-11-tiles pace at any count; each track sets --wall-tiles — see the CREATIVE WALL comment in ad-creatives/content.html */
.wall-tile{flex:0 0 auto;width:190px;}
.wall-tile img,.wall-tile video{display:block;width:100%;aspect-ratio:4/5;object-fit:cover;border:2px solid #17140F;border-radius:22px;}
@media (min-width:640px){.wall-track{gap:28px;}.wall-tile{width:248px;}}
@media (min-width:1024px){.wall-tile{width:286px;}}

/* google reviews: a static grid — cards render in place, no entrance.
   Read more / read less slides the quote open and shut. The clamp itself
   can't animate, so the script measures the clamped and open heights and
   transitions between them under .review-text-animating, then hands the
   settled state back to the plain classes below. */
.review-card{transition:border-color .2s ease, box-shadow .2s ease;}
.review-card.review-expanded{border-color:#FF3B00;box-shadow:0 10px 24px rgba(23,20,15,0.08);}
.review-text.review-text-animating{display:block;overflow:hidden;transition:height .4s cubic-bezier(.22,.7,.2,1);}
.review-text.review-text-expanded{display:block;max-height:13rem;overflow-y:auto;}

@keyframes slowspin{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}
@keyframes floaty{0%,100%{transform:translateY(0) rotate(-4deg);}50%{transform:translateY(-10px) rotate(2deg);}}
@keyframes spinslow{from{transform:rotate(0);}to{transform:rotate(360deg);}}
@keyframes bob{0%,100%{transform:translateY(0);}50%{transform:translateY(-8px);}}
.anim-slowspin{animation:slowspin 17.33s linear infinite;}
.anim-floaty{animation:floaty 5s ease-in-out infinite;}
.anim-spinslow{animation:spinslow 6s linear infinite;}
.anim-bob{animation:bob 4s ease-in-out infinite;}

@media (prefers-reduced-motion: reduce){
  .marquee-track,.anim-slowspin,.anim-floaty,.anim-spinslow,.anim-bob{animation:none !important;}
  html{scroll-behavior:auto;}
}

/* nav island (floating pill once the page is scrolled) */
#siteNav{background:transparent;transition:padding .4s cubic-bezier(.65,0,.35,1);}
#navInner{transition:max-width .4s cubic-bezier(.65,0,.35,1), border-radius .4s cubic-bezier(.65,0,.35,1), border-color .4s ease, box-shadow .4s ease, background-color .4s ease, height .4s cubic-bezier(.65,0,.35,1);}
#siteNav.nav-scrolled{padding-top:14px;}
#siteNav.nav-scrolled #navInner{max-width:1160px;height:68px;border-radius:22px;border:2px solid #17140F;background-color:#FFFBE7;box-shadow:0 10px 30px -8px rgba(23,20,15,.28);}
@media (max-width:900px){
  #siteNav.nav-scrolled{padding-top:10px;}
  #siteNav.nav-scrolled #navInner{border-radius:22px;height:64px;}
}

/* nav link hover: flat yellow fill with a bold ink border, no slide/wipe animation */
.nav-link{position:relative;display:inline-flex;align-items:center;padding:8px 16px;border-radius:9999px;background-color:transparent;border:2px solid transparent;transition:background-color .2s ease, border-color .2s ease;cursor:pointer;}
.nav-link:hover,.nav-item.mega-open .nav-link{background-color:#FDDB0A;border-color:#17140F;}
.nav-link .nav-link-label{position:relative;display:inline-flex;align-items:center;gap:6px;}

/* Services dropdown: the phone menu's card under the Services pill.
   Cream, 2px ink border, 22px radius and the island's tinted shadow, 12px
   padding, holding three white rows with a 2px ink border, 12px apart, as
   the homepage service cards sit on their panel. Each row: a 48px sticker,
   the name at the phone menu's 20px uppercase over a 13px description, and
   the ledger's 28px yellow arrow disc at the end. Rows have a 14px radius;
   a row fills flat yellow on hover and its disc goes orange, as the
   phone discs do on press. Rows rise in 35ms apart behind the card's spring
   and fade out together on close. */
.mega-menu{position:absolute;top:calc(100% + 6px);left:50%;transform-origin:top center;transform:translateX(-50%) translateY(-8px) scale(0.96);opacity:0;visibility:hidden;pointer-events:none;box-shadow:0 10px 30px -8px rgba(23,20,15,.28);transition:opacity .18s ease,transform .38s cubic-bezier(.34,1.56,.64,1),visibility .38s ease;z-index:60;display:flex;flex-direction:column;gap:12px;}
.nav-item.mega-open .mega-menu{opacity:1;visibility:visible;transform:translateX(-50%) translateY(0) scale(1);pointer-events:auto;}
.nav-item.mega-open .mega-chevron{transform:rotate(180deg);}
/* once the header is the bordered island, the pill sits 14px above the
   island's 2px bottom border; the card's top border lands exactly on it,
   so the card hangs off the island on one shared line */
#siteNav.nav-scrolled .mega-menu{top:calc(100% + 14px);}
.mega-row{display:grid;grid-template-columns:48px minmax(0,1fr) 28px;align-items:center;column-gap:16px;padding:20px 20px 20px 18px;border-radius:14px;background-color:#fff;border:2px solid #17140F;color:#17140F;text-decoration:none;opacity:0;transform:translateY(6px);transition:background-color .2s ease,border-color .2s ease,opacity .18s ease,transform .18s ease;}
.mega-row:hover,.mega-row:focus-visible{background-color:#FDDB0A;outline:none;}
.mega-row-text{display:flex;flex-direction:column;gap:6px;min-width:0;}
.mega-row-name{font-weight:700;text-transform:uppercase;font-size:1.25rem;line-height:1;}
.mega-row-sub{font-size:.8125rem;font-weight:400;line-height:1.4;color:rgba(23,20,15,.65);}
.mega-row-icon{display:inline-flex;align-items:center;justify-content:center;width:48px;height:48px;}
.mega-row-icon img{width:48px;height:48px;object-fit:contain;transition:transform .3s cubic-bezier(.34,1.56,.64,1);}
/* the sticker perks up on hover: scale rides on Tailwind's transform vars so
   the sticker keeps its tilt */
.mega-row:hover .mega-row-icon img,.mega-row:focus-visible .mega-row-icon img{--tw-scale-x:1.12;--tw-scale-y:1.12;}
.mega-row-disc{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:2px solid #17140F;border-radius:9999px;background-color:#FDDB0A;color:#17140F;transition:background-color .2s ease,transform .3s cubic-bezier(.34,1.56,.64,1);}
.mega-row-disc svg{width:14px;height:14px;}
.mega-row:hover .mega-row-disc,.mega-row:focus-visible .mega-row-disc{background-color:#FF3B00;transform:translate(2px,-2px);}
.nav-item.mega-open .mega-row{opacity:1;transform:translateY(0);transition:background-color .2s ease,border-color .2s ease,opacity .4s cubic-bezier(.16,1,.3,1),transform .4s cubic-bezier(.16,1,.3,1);transition-delay:0s,0s,calc(60ms + var(--i,0) * 35ms),calc(60ms + var(--i,0) * 35ms);}
@media (prefers-reduced-motion:reduce){
  .mega-menu,.nav-item.mega-open .mega-menu{transition:opacity .15s ease,visibility .15s ease;transform:translateX(-50%);}
  .mega-row,.nav-item.mega-open .mega-row{transition:background-color .2s ease,border-color .2s ease;transition-delay:0s;transform:none;}
  .mega-row-icon img,.mega-row-disc{transition:none;}
  .mega-row:hover .mega-row-disc{transform:none;}
}
@media (max-width:820px){
  .mega-menu{position:static;transform:none !important;width:auto;box-shadow:none;border:none;padding:0;margin-top:14px;display:none;}
  .nav-item.mega-open .mega-menu{display:block;opacity:1;visibility:visible;}
}

/* ── Phone menu (below md, where the hamburger shows) ──
   The island behaves exactly as it does on desktop: transparent at rest,
   a bordered cream pill once scrolled. Opening the menu stretches that
   pill: the panel is the island's second flex line, and as it unfolds on
   the FAQ's grid-rows trick (0fr -> 1fr) the island's height follows, so
   its one 2px outline and its corners simply travel down and back up.
   The island's corners are the site's 22px card radius, open or closed,
   so they never visibly change. The header takes the island
   treatment even at the top of the page (`.nav-menu`, set by script.js on
   open and held until the fold has finished, so the pill never drops its
   border mid-close). The page behind dims on a fixed scrim
   (header::before) and is locked (html.mnav-open); tapping the scrim or
   pressing Escape closes the menu. */
#mnav-toggle{display:none;}
.mobile-panel{display:none;}
@media (max-width:767.98px){
  html.mnav-open{overflow:hidden;overscroll-behavior:none;} /* html only: locking body too makes body the scroll container and unsticks the bar */
  /* the island, inset from the screen edge so the open menu has air around it */
  #siteNav.nav-scrolled,#siteNav.nav-menu{padding:10px 12px 0;}
  /* no border-radius in this transition list: from the flat bar at the top
     of the page the corners switch to 22px the instant the menu opens,
     while the bar still has no visible edge, rather than rounding over
     .4s on a box that is already growing. The scrolled island is already
     22px, so opening from it changes no radius at all. */
  #siteNav.nav-menu #navInner{height:auto;min-height:64px;flex-wrap:wrap;align-content:flex-start;border:2px solid #17140F;border-radius:22px;background-color:#FFFBE7;box-shadow:0 10px 30px -8px rgba(23,20,15,.28);transition:border-color .3s ease,box-shadow .4s ease,background-color .3s ease,height .4s cubic-bezier(.65,0,.35,1);}
  /* The bar's two blocks (the wordmark link and the hamburger group) carry
     the bar line's height explicitly so it can animate: 80px inside the
     84px resting bar, 60px inside the 64px island, on the island's own
     curve. With the island at height:auto this is what keeps the wordmark
     still when the menu opens from the flat bar (the padding grows 10px as
     the line shrinks 20px) and when the bar is handed back on close.
     row-gap:0 because Tailwind's gap-6 would otherwise add 24px between
     the bar line and the menu line. */
  #navInner{row-gap:0;}
  #navInner > a,#navInner > div:not(.mobile-panel){display:flex;align-items:center;min-height:80px;transition:min-height .4s cubic-bezier(.65,0,.35,1);}
  #siteNav.nav-scrolled #navInner > a,#siteNav.nav-scrolled #navInner > div:not(.mobile-panel),
  #siteNav.nav-menu #navInner > a,#siteNav.nav-menu #navInner > div:not(.mobile-panel){min-height:60px;}
  /* on the narrowest phones the inset pill is too tight for the wordmark
     plus the icon, and the icon would wrap under the logo as a second
     line; a 20px wordmark keeps the bar on one line down to 320px */
  @media (max-width:359px){ #navInner > a > img{height:20px;} }
  /* the scrim: fixed, behind the island, in front of the page */
  #siteNav::before{content:"";position:fixed;inset:0;z-index:-1;background:rgba(23,20,15,.42);opacity:0;visibility:hidden;pointer-events:none;transition:opacity .45s ease,visibility 0s linear .45s;}
  header:has(#mnav-toggle:checked)::before{opacity:1;visibility:visible;pointer-events:auto;transition:opacity .45s ease,visibility 0s;}
  /* the panel: the island's second line, full width, growing top to bottom.
     It is only IN the flex flow while the island is open or folding
     (.nav-menu); otherwise it is absolutely positioned out of the row, or
     its full-width basis would sit on the bar's one line and push the
     hamburger into the middle of the closed bar. */
  .mobile-panel{display:grid;grid-template-rows:0fr;position:absolute;flex:0 0 100%;min-width:0;min-height:0;visibility:hidden;transition:grid-template-rows .5s cubic-bezier(.65,0,.35,1),visibility 0s linear .5s;}
  #siteNav.nav-menu .mobile-panel{position:static;}
  header:has(#mnav-toggle:checked) .mobile-panel{grid-template-rows:1fr;visibility:visible;transition:grid-template-rows .55s cubic-bezier(.65,0,.35,1),visibility 0s;}
  .mnav-card{min-height:0;overflow:hidden;}
  .mnav-card-inner{padding:6px 0 20px;max-height:calc(100dvh - 120px);overflow-y:auto;}
  /* rows: one 36px icon slot for every row (sticker for a service, the
     ledger's yellow arrow disc for a page) so every name starts on the same
     line; a 2px rule only where the services end and the pages begin */
  .mnav-list{display:flex;flex-direction:column;}
  .mnav-link{display:flex;align-items:center;gap:14px;min-height:56px;padding:6px 0;font-weight:700;text-transform:uppercase;font-size:1.25rem;line-height:1;color:#17140F;text-decoration:none;}
  .mnav-link-page{margin-top:10px;padding-top:16px;border-top:2px solid rgba(23,20,15,.15);}
  .mnav-icon{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;flex:none;}
  .mnav-icon img{width:36px;height:36px;object-fit:contain;}
  .mnav-disc{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:2px solid #17140F;border-radius:9999px;background-color:#FDDB0A;color:#17140F;transition:background-color .2s ease;}
  .mnav-disc svg{width:14px;height:14px;}
  .mnav-link:active .mnav-disc{background-color:#FF3B00;}
  .mnav-cta{margin-top:18px;min-height:56px;padding:14px 24px;}
  /* the rows settle in a beat behind the unfold and leave together */
  .mnav-item{opacity:0;transform:translateY(6px);transition:opacity .18s ease,transform .18s ease;}
  header:has(#mnav-toggle:checked) .mnav-item{opacity:1;transform:translateY(0);transition:opacity .4s cubic-bezier(.16,1,.3,1),transform .4s cubic-bezier(.16,1,.3,1);transition-delay:calc(140ms + var(--i,0) * 35ms);}
}
@media (prefers-reduced-motion: reduce){
  .mobile-panel,header:has(#mnav-toggle:checked) .mobile-panel{transition:visibility 0s;}
  #siteNav::before,header:has(#mnav-toggle:checked)::before{transition:visibility 0s;}
  .mnav-item,header:has(#mnav-toggle:checked) .mnav-item{transition:none;transition-delay:0s;}
  .mnav-disc{transition:none;}
}

/* hamburger -> X: two bars, two moves in sequence. Opening, the bars slide
   together to the centre line and then twist into the cross. Closing runs it
   backwards: untwist first, then part. The closed-state rules carry the
   closing delays and the open-state rules the opening ones, so each
   direction sequences itself. The twist pivots on the icon centre (12,12 in
   the viewBox) and lands on an exponential ease-out, no overshoot. */
.hb-move{transition:transform .22s cubic-bezier(.65,0,.35,1) .2s;}
.hb-move-a{transform:translateY(-4px);}
.hb-move-b{transform:translateY(4px);}
.hb-turn{transform-origin:12px 12px;transition:transform .3s cubic-bezier(.22,1,.36,1) 0s;}
header:has(#mnav-toggle:checked) .hb-move{transform:translateY(0);transition-delay:0s;}
header:has(#mnav-toggle:checked) .hb-turn{transition-delay:.16s;}
header:has(#mnav-toggle:checked) .hb-turn-a{transform:rotate(45deg);}
header:has(#mnav-toggle:checked) .hb-turn-b{transform:rotate(-45deg);}
@media (prefers-reduced-motion: reduce){
  .hb-move,.hb-turn{transition:none;}
}

/* FAQ smooth accordion (native details, CSS-grid trick) */
details.faq-item > .faq-a{display:grid !important;grid-template-rows:0fr;transition:grid-template-rows .3s ease;overflow:hidden;}
details.faq-item[open] > .faq-a{grid-template-rows:1fr;}
details.faq-item summary::-webkit-details-marker{display:none;}
details.faq-item .faq-plus{transition:background-color .3s ease, transform .4s cubic-bezier(.34,1.56,.64,1);}
details.faq-item summary:hover .faq-plus,
details.faq-item[open] .faq-plus{background-color:#FDDB0A;transform:rotate(90deg);}
/* Open state reads as a minus: the badge has rotated 90°, so the bar that
   started horizontal is now the upright one — fade it out and the other bar
   is left lying flat. Hover alone keeps the full plus. */
details.faq-item .faq-plus > span{transition:opacity .25s ease;}
details.faq-item[open] .faq-plus > span:first-child{opacity:0;}

/* card micro-interactions (transform/opacity only, no transition-all) */
.card-hard{transition:transform .2s ease;}
.card-hard:hover{transform:translate(-2px,-4px);}
.btn-hard{transition:background-color .18s ease, color .18s ease;}

/* hero buttons */
.hero-btn{transition:background-color .15s ease;}
.hero-btn-primary:hover{background-color:#FDDB0A;}
.hero-btn-secondary:hover{background-color:#FDDB0A;}

/* mobile team carousel: hide scrollbar, native momentum scroll */
.team-mobile-track{scrollbar-width:none;-webkit-overflow-scrolling:touch;}
.team-mobile-track::-webkit-scrollbar{display:none;}

/* desktop team carousel: same hidden scrollbar. overflow-y is pinned to hidden because
   overflow-x:auto would otherwise make it auto too, and a flip card mid-rotation bulges
   past its box under perspective, which would flash a vertical scrollbar on every hover.
   The track's py-4/-my-4 pair gives the bulge room so it isn't clipped either. */
.team-desktop-track{scrollbar-width:none;-webkit-overflow-scrolling:touch;overflow-y:hidden;}
.team-desktop-track::-webkit-scrollbar{display:none;}

/* case studies carousel: hide scrollbar, native momentum scroll */
.case-studies-track{scrollbar-width:none;-webkit-overflow-scrolling:touch;}
.case-studies-track::-webkit-scrollbar{display:none;}

/* angle framework rail (/ad-creatives) — same loop carousel as the case studies */
.angles-track{scrollbar-width:none;-webkit-overflow-scrolling:touch;}
.angles-track::-webkit-scrollbar{display:none;}

/* Case-study tiles — one card, four pages (home rail, Paid Advertising proof,
   Ad Creatives proof, case studies index). The whole card is the link, so
   hovering anywhere on it fills the CTA yellow. The card itself stays white.
   No lift, no fade — the highlight lands the moment the pointer arrives. */
a.case-study-card{transition:none;}
a.case-study-card:hover .cs-card-cta,
a.case-study-card:focus-visible .cs-card-cta{background-color:#FDDB0A;}

/* team cards: flip on hover to reveal role description */
.flip-card{perspective:1200px;}
.flip-card-inner{position:relative;transition:transform .6s cubic-bezier(.4,.2,.2,1);transform-style:preserve-3d;}
.flip-card:hover .flip-card-inner, .flip-card:focus-within .flip-card-inner{transform:rotateY(180deg);}
.flip-card-front, .flip-card-back{backface-visibility:hidden;-webkit-backface-visibility:hidden;}
.flip-card-back{position:absolute;inset:0;transform:rotateY(180deg);}
/* the card is a tabindex div, so the global a/button focus-visible rule above
   does not reach it — without this it falls back to the browser blue ring. */
.flip-card:focus-visible{outline:3px solid #FF3B00;outline-offset:3px;border-radius:22px;}

/* testimonials carousel: hide scrollbar, native momentum scroll */
.testimonials-track{scrollbar-width:none;-webkit-overflow-scrolling:touch;}
.testimonials-track::-webkit-scrollbar{display:none;}

/* Challenges rail (/paid-advertising): a three-up grid from 640px, a swipe rail
   below it. Cards snap to the left gutter at 82% of the rail so the next one
   peeks in, and the negative margin lets that peek run to the screen edge rather
   than stopping at the section gutter — so scroll-padding has to repeat the
   gutter, otherwise a snapped card tucks under it. Same hidden scrollbar and
   momentum scroll as the other rails; the dots underneath are built in script.js.
   Selected by id, not class: the Tailwind CDN injects its utilities after this
   stylesheet, so a plain class would lose the display/gap tie to grid and gap-8.
   /organic-worlds reuses the exact same rail for its own six challenge cards
   under its own id (#challengesRail) — same rule, listed alongside. */
@media (max-width: 639px){
  #problemsRail,#challengesRail{display:flex;gap:1.5rem;overflow-x:auto;scroll-snap-type:x mandatory;scroll-padding-left:1.25rem;scrollbar-width:none;-webkit-overflow-scrolling:touch;margin-left:-1.25rem;margin-right:-1.25rem;padding:0.5rem 1.25rem;}
  #problemsRail::-webkit-scrollbar,#challengesRail::-webkit-scrollbar{display:none;}
  /* grid on the tilt wrapper so its card stretches to the tallest in the rail,
     rather than leaning on a percentage height inside a flex item. */
  #problemsRail > *,#challengesRail > *{flex:0 0 82%;scroll-snap-align:start;display:grid;height:auto;}
}

/* carousel dot indicators (case studies / testimonials / team) */
.carousel-dot{width:8px;height:8px;border-radius:9999px;background-color:rgba(23,20,15,0.2);border:none;padding:0;cursor:pointer;transition:background-color .2s ease, width .2s ease;}
.carousel-dot:hover{background-color:rgba(23,20,15,0.4);}
.carousel-dot.is-active{width:22px;background-color:#FF3B00;}

/* focus-visible states for every interactive element */
a:focus-visible, button:focus-visible, summary:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible{
  outline:3px solid #FF3B00; outline-offset:3px; border-radius:4px;
}

/* The header and footer logo links are the one exception: an orange ring around
   an orange wordmark reads as part of the mark rather than as focus, so it stays
   off there. Every other control keeps its ring. */
a:focus-visible:has(> img[src$="pulp-logo.png"]){outline:none;}

/* ─── case studies index: sticky filter bar, chips, tiles, image skeletons ─── */

/* Sticks directly under the nav. The nav is 84px tall at rest and collapses to a
   68px pill + 14px of padding once scrolled, so 84px clears it in both states. */
#csFilterBar{position:sticky;top:84px;z-index:40;}
@media (max-width:900px){#csFilterBar{top:78px;}}

/* The service filters: four chips on one centred row at every width. They never
   scroll, because a filter the reader can't see is a filter they won't use, and
   they never wrap, because a 2x2 block read as two rows of tabs rather than one
   set of options. The negative margin/padding pair keeps the focus ring from
   being clipped. */
.cs-chip-row{display:flex;flex-wrap:nowrap;align-items:center;justify-content:center;gap:8px;min-width:0;max-width:100%;margin:-5px;padding:5px;}

.cs-chip{--chip-pad-y:7px;--chip-pad-x:15px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;padding:var(--chip-pad-y) calc(var(--chip-pad-x) - .02em) var(--chip-pad-y) var(--chip-pad-x);border-radius:9999px;border:2px solid #17140F;background-color:#fff;font-size:12px;line-height:1.5;font-weight:700;text-transform:uppercase;letter-spacing:0.02em;white-space:nowrap;cursor:pointer;transition:background-color .18s ease,border-color .18s ease,color .18s ease;}
.cs-chip:hover{background-color:#FDDB0A;border-color:#17140F;}
.cs-chip[aria-checked="true"]{background-color:#FF3B00;border-color:#17140F;color:#17140F;}
.cs-chip[aria-checked="true"]:hover{background-color:#FDDB0A;color:#17140F;}

/* Centring the capitals, not the line box. Helvetica Neue's hhea metrics put
   the baseline 71.4% down the em, so an all-caps label rides ~0.14em high in
   any box centred on its line box. HNP-UI's ascent-override (top of file)
   corrects that in Chrome and Firefox, but Safari ignores the descriptor, which
   is why the chips sat high on iPhones. text-box-trim crops the line box to the
   cap height itself, so wherever it is supported (Safari 18.2+, Chrome 133+)
   the centring stops depending on font metrics at all. The label is in its own
   block so the trim applies to a real line box rather than the button's
   internal flex layout. The padding grows by the half-leading the trim removed
   ((1.5em - 0.714em) / 2) so the pill keeps its height. */
/* Desktop: the whole chip 30% up on the base size (type, padding and gap). */
@media (min-width:1024px){
  .cs-chip-row{gap:10.4px;}
  .cs-chip{--chip-pad-y:9.1px;--chip-pad-x:19.5px;font-size:15.6px;}
}

.cs-chip > span{display:block;}
@supports (text-box-trim:trim-both){
  .cs-chip > span{text-box-trim:trim-both;text-box-edge:cap alphabetic;}
  .cs-chip{padding-top:calc(var(--chip-pad-y) + 0.393em);padding-bottom:calc(var(--chip-pad-y) + 0.393em);}
}

/* Below sm each chip shows its short label (ALL, PAID, ORGANIC, CREATIVES)
   instead of the full service name, and the row scales to the screen instead
   of wrapping. The short labels measure ~15em in Helvetica Neue Bold caps
   (including tracking); with 0.75em of side padding per chip, 0.3em gaps and
   2px borders the whole row is ~22em + 16px, inside the 100vw - 40px the page
   gutters leave. Divide one by the other and every phone gets the largest type
   that fits on one line, capped at 15px (a quarter up on the 12px the chips
   use from sm): 15px from ~385px up, ~14.5px at 375px, ~12px at 320px. Padding
   and gap are in em so the pills keep their proportions as they scale. */
.cs-chip-short{display:none;}
@media (max-width:639px){
  .cs-chip-full{display:none;}
  .cs-chip-short{display:inline;}
  .cs-chip-row{gap:0.3em;font-size:clamp(8px,calc((100vw - 56px) / 22),15px);}
  .cs-chip{--chip-pad-y:0.6em;--chip-pad-x:0.75em;font-size:1em;}
}


/* The script hides these with the `hidden` attribute; each carries a Tailwind
   display class that would otherwise win over the UA's [hidden] rule. */
.cs-card[hidden],#csLoadMoreWrap[hidden],#csEmpty[hidden]{display:none;}

/* Card head: client logo left, service right — the one thing the index tile adds
   to the shared card, so a filtered grid still says which service each result
   belongs to. The slot is a fixed 44px band, so the headline below starts on the
   same baseline on every card no matter whether the logo is a long horizontal
   wordmark or a tall stacked lockup. Per-logo heights are set inline in the
   markup and optically matched, not literal. */
.cs-head{display:flex;align-items:center;justify-content:space-between;gap:12px;}
.cs-logo-slot{display:flex;align-items:center;height:44px;min-width:0;}
.cs-logo{width:auto;max-width:100%;object-fit:contain;object-position:left center;}
.cs-wordmark{font-size:15px;font-weight:700;line-height:1.2;text-transform:uppercase;color:rgba(23,20,15,0.85);}

.cs-tag-service{flex-shrink:0;border:2px solid #17140F;border-radius:9999px;padding:3px 10px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.06em;color:#17140F;line-height:1.5;}

/* Reserved slot: a real client whose result and visual aren't published yet.
   No invented number, no link affordance, no hover lift — the card says plainly
   that it isn't live. To publish one: drop both --soon classes, replace the
   bracketed headline, wrap the inner <div> in the same <a> the live cards use,
   and swap the panel for a .cs-media block with the real image and pill. */
.cs-media.cs-media--soon{display:flex;align-items:center;justify-content:center;border-style:dashed;border-color:rgba(23,20,15,0.3);}
.cs-soon{padding:0 calc(18px - .12em) 0 18px;text-align:center;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.12em;line-height:1.5;color:rgba(23,20,15,0.62);}

/* Image skeleton: the media box already holds its own aspect ratio, so the
   shimmer is swapped for the image in place — nothing reflows when it lands.
   Scoped to .cs-skeletons, which the script adds to the grid on init, so with
   JS off the images just render normally instead of staying at opacity 0. */
.cs-media{background-color:#EFEBD8;}
.cs-skeletons .cs-media{background-image:linear-gradient(100deg,rgba(255,255,255,0) 20%,rgba(255,255,255,.65) 50%,rgba(255,255,255,0) 80%);background-size:220% 100%;background-repeat:no-repeat;animation:cs-shimmer 1.5s linear infinite;}
.cs-skeletons .cs-media img{opacity:0;transition:opacity .3s ease;}
.cs-skeletons .cs-media.is-loaded{background-image:none;animation:none;}
.cs-skeletons .cs-media.is-loaded img{opacity:1;}
/* a reserved slot has no image to wait on — it must never shimmer */
.cs-skeletons .cs-media--soon{background-image:none;animation:none;}
@keyframes cs-shimmer{from{background-position:-60% 0;}to{background-position:160% 0;}}

@media (prefers-reduced-motion: reduce){
  .cs-skeletons .cs-media{animation:none;background-image:none;}
  .cs-skeletons .cs-media img{transition:none;}
}

/* hero typewriter word */
@keyframes typeBlink{0%,45%{opacity:1;}50%,95%{opacity:0;}100%{opacity:1;}}
.type-cursor{display:inline-block;animation:typeBlink 1.1s ease-in-out infinite;}
@media (prefers-reduced-motion: reduce){
  .type-cursor{animation:none;}
}

/* ─────────────────────────────────────────────────────────────────────────────
   ABOUT US — the juice rail
   ─────────────────────────────────────────────────────────────────────────────
   Everything below is scoped to `main.ab` (about-us/content.html) so it no-ops on
   every other page. The page's one authored motion is the rail: a cream pipe
   with a 2px ink border running down the timeline, filling with orange as you
   read — the juice level rising through the story. Each chapter's bead and year
   flip from muted to orange the moment the fill passes them.

   DEFAULT STATE IS "FULLY POURED." The rail renders full, every bead orange.
   Only once the script attaches does it add `.tl-live` and take the rail back
   to its pre-scroll state. A failed or blocked script therefore shows the
   finished page, never an empty one — and tools/screenshot.mjs strips
   `.tl-live` to capture that same end state.
   ────────────────────────────────────────────────────────────────────────── */


/* ── timeline geometry ──
   --rail-x is the pipe's left edge. The pipe is 14px wide, so its centre sits at
   --rail-x + 7px and the 24px bead's left edge at --rail-x - 5px. Change --rail-x
   and the pipe, the beads and the content indent all move together. */
.tl{position:relative;--rail-x:12px;--bead-top:3px;}
@media (min-width:1024px){.tl{--rail-x:25px;}}

.tl-rail{position:absolute;left:var(--rail-x);top:calc(var(--bead-top) + 17px);bottom:0;width:14px;border:2px solid #17140F;border-radius:9999px;background:#FFFBE7;overflow:hidden;pointer-events:none;}
.tl-fill{position:absolute;left:0;right:0;top:0;height:100%;background:#FF3B00;transform-origin:top center;transform:scaleY(1);will-change:transform;}
.tl-live .tl-fill{transform:scaleY(var(--fill,0));}

.tl-item{position:relative;padding-left:56px;}
/* The rail div is the list's first child, so space-y-* also lands on the first
   item and collapses through the ol's own margin-top, doubling the gap above it. */
ol.tl > li.tl-item:first-of-type{margin-top:0;}
@media (min-width:1024px){.tl-item{padding-left:96px;}}

.tl-bead{position:absolute;left:calc(var(--rail-x) - 10px);top:var(--bead-top);width:34px;height:34px;border-radius:9999px;border:2px solid #17140F;background:#FF3B00;transition:background-color .3s ease,transform .35s cubic-bezier(.16,1,.3,1);}
.tl-live .tl-item:not(.is-reached) .tl-bead{background:#FFFBE7;transform:scale(.82);}

.tl-year{color:#FF3B00;transition:color .3s ease;}
.tl-live .tl-item:not(.is-reached) .tl-year{color:rgba(23,20,15,.35);}

/* ─── case study detail page ───────────────────────────────────────────────
   The detail page is read start-to-finish, not scanned, so it is built as a
   sequence of named story beats rather than a grid of tiles. Everything below
   is shared by every client's page via case-studies/_template.html. */

/* Each beat opens on a pill carrying its name, so the challenge -> solution ->
   result sequence is legible without the reader having to infer it from the
   headings. The alternating section bands are the chapter break; the pill is the
   documented 11px uppercase label role carrying the sequence.

   White fill rather than the yellow of the page-header badge: the hero already
   spends that badge on the service name, and a second yellow pill per section
   would dilute it. The orange dot is what ties the two together as one family.
   Label text stays ink — orange on cream is 3.4:1, which clears the large-text
   bar the stat figures sit at but not the 4.5:1 an 11px label needs. */
.cs-beat{display:inline-flex;align-items:center;gap:10px;border:2px solid #17140F;border-radius:9999px;background:#fff;padding:7px calc(16px - .12em) 7px 16px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;line-height:17px;color:#17140F;}
.cs-beat::before{content:"";flex:0 0 auto;width:8px;height:8px;border-radius:9999px;background:#FF3B00;border:1px solid #17140F;}

/* Both of the detail page's stat strips — the hero's and the result's — use the
   boxed, white-celled component already shipped on the homepage, About and Paid
   Advertising pages, built from Tailwind utilities in tools/build.py rather than
   from classes here. An earlier unboxed `.cs-proof` treatment was removed: two
   different stat components on one page made a figure in the hero read as a
   different kind of thing to a figure in the result. */

/* ── placeholder slots ──
   One consistent treatment everywhere content is still to be written: dashed
   ink rule, soft cream fill, and a labelled chip saying exactly what goes here.
   Deliberately NOT a link and carrying no invented number. */
.ph{border:2px dashed rgba(23,20,15,.32);background:#FFF7D6;border-radius:22px;}
.ph-chip{display:inline-flex;align-items:center;gap:6px;border:2px solid rgba(23,20,15,.28);border-radius:9999px;padding:3px calc(10px - .12em) 3px 10px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;color:rgba(23,20,15,.6);line-height:1.5;}
.ph-inline{font-weight:700;color:#FF3B00;border-bottom:2px dashed rgba(23,20,15,.4);}
.ph--dark{border-color:rgba(255,251,231,.28);background:rgba(255,251,231,.05);}
.ph--dark .ph-chip{border-color:rgba(255,251,231,.3);color:rgba(255,251,231,.65);}

/* ── PULP acronym switcher (.pw) ──
   Sits directly on the section's own cream band -- no inset panel. Four tiles
   stagger to an uneven skyline and overlap down into a floating white card,
   the one sanctioned "floating" shadow in the system, reused from the nav
   island rather than invented. All four are yellow at rest -- a four-stop
   ramp from full yellow down to yellow-tint, the two middle stops derived
   from those tokens via color-mix rather than new invented hexes -- with ink
   letters, matching every other yellow surface on the site. Hovering, or
   selecting, any tile flips it to solid brand orange with a cream letter: one
   consistent "this one" cue rather than baking selection into the resting
   palette. The default focus-visible ring (a 4px-radius outline from the
   sitewide button rule) reads as a mismatched halo against a 12px-radius
   tile, and the colour flip already marks focus clearly, so it's suppressed
   here specifically. All four panels share one grid cell so the card's
   height matches the tallest of them rather than jumping as copy length
   changes; align-items:center keeps a shorter panel's text centred in that
   shared height instead of pinned to the top with the slack left at the
   bottom. Autoplay lives in script.js, pausing on hover/focus like the
   marquees. Under prefers-reduced-motion the timer never starts, leaving
   tile/panel 0 already marked is-active in the markup -- correct with no JS
   at all. */
.pw-tiles{position:relative;z-index:2;display:grid;grid-template-columns:repeat(4,1fr);align-items:end;gap:10px;margin-bottom:-26px;}
.pw-tile{display:flex;align-items:flex-start;justify-content:center;padding:18px 6px;border:2px solid #17140F;color:#17140F;font-family:inherit;cursor:pointer;box-shadow:0 10px 30px -8px rgba(23,20,15,.28);transition:background-color .2s ease,color .2s ease,transform .2s ease;}
.pw-tile:nth-child(1){height:clamp(132px,16vw,190px);background-color:#FDDB0A;}
.pw-tile:nth-child(2){height:clamp(112px,14vw,166px);background-color:color-mix(in srgb,#FDDB0A 67%,#FDF1A7);}
.pw-tile:nth-child(3){height:clamp(100px,12.5vw,150px);background-color:color-mix(in srgb,#FDDB0A 33%,#FDF1A7);}
.pw-tile:nth-child(4){height:clamp(108px,13.5vw,160px);background-color:#FDF1A7;}
.pw-tile:hover,.pw-tile:focus-visible{background-color:#FF3B00;color:#FFFBE7;transform:translateY(-3px);}
.pw-tile:focus-visible{outline:none;}
.pw-tile.is-active,.pw-tile.is-active:hover{background-color:#FF3B00;color:#FFFBE7;transform:translateY(-6px);}
.pw-letter{font-weight:700;line-height:1;font-size:clamp(1.75rem,4vw,2.75rem);}
.pw-card{position:relative;z-index:1;border-radius:22px;border:2px solid #17140F;background:#fff;color:#17140F;padding-top:26px;overflow:hidden;}
.pw-panels{position:relative;display:grid;align-items:center;padding:28px 24px;}
.pw-panel{grid-area:1/1;opacity:0;transform:translateY(6px);transition:opacity .35s ease,transform .35s ease;pointer-events:none;}
.pw-panel.is-active{opacity:1;transform:translateY(0);pointer-events:auto;}
.pw-panel-copy{color:rgba(23,20,15,.7);line-height:1.7;margin-top:10px;max-width:60ch;}
@media (min-width:640px){.pw-card{padding-top:34px;}.pw-panels{padding:36px 32px;}}
@media (prefers-reduced-motion: reduce){.pw-panel{transition:opacity .2s ease;transform:none;}.pw-tile{transition:background-color .2s ease,color .2s ease;}.pw-tile.is-active{transform:none;}}



/* ── touch fallback for the team flip cards ──
   A hover flip is unreachable on touch, so on those devices the card stops
   flipping and the role description prints on the front instead. Scoped to the
   about page so the homepage's own team grid keeps its current behaviour. */
.team-touch-desc{display:none;}
@media (hover:none){
  .ab .flip-card{cursor:default;}
  .ab .flip-card-inner{transform:none !important;transition:none;}
  .ab .flip-card-back{display:none;}
  .ab .team-touch-desc{display:block;}
}

@media (prefers-reduced-motion: reduce){
  .tl-live .tl-fill{transform:scaleY(1);}
  .tl-bead,.tl-year{transition:none;}
  .tl-live .tl-item:not(.is-reached) .tl-bead{background:#FF3B00;transform:none;}
  .tl-live .tl-item:not(.is-reached) .tl-year{color:#FF3B00;}
}

/* ── contact form ──
   No forms exist elsewhere on the site, so these are the first field styles.
   Same family as every other surface: 2px ink border, 12px radius (the DESIGN.md
   "md" step — one below the 22px card radius, since a field sits inside a card
   rather than being one), flat fills. Focus opts out of the global orange ring:
   a ring offset outside an already-inked 2px border reads as a double outline,
   so the field's own border turns orange instead. */
.f-label{display:block;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;color:rgba(23,20,15,.7);margin-bottom:8px;}
.f-input{width:100%;border:2px solid #17140F;border-radius:12px;background-color:#fff;padding:12px 16px;font-size:1rem;line-height:1.5;color:#17140F;transition:background-color .15s ease,border-color .15s ease;}
.f-input::placeholder{color:rgba(23,20,15,.4);}
textarea.f-input{resize:vertical;min-height:120px;}

.f-select{appearance:none;-webkit-appearance:none;width:100%;border:2px solid #17140F;border-radius:12px;background-color:#fff;transition:border-color .15s ease;background-repeat:no-repeat;background-position:right 16px center;background-size:12px;padding:12px 40px 12px 16px;font-size:1rem;color:#17140F;cursor:pointer;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2317140F' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");}
.f-select:invalid{color:rgba(23,20,15,.45);}

/* Focused field: orange border, no ring. Both :focus and :focus-visible are
   listed so a click and a tab land on the same state, and the ring is cleared
   for either. Orange matches the .svc-chip focus ring below, so every control in
   the form signals focus with the same colour. The radius is restated because the global focus-visible rule also
   sets border-radius:4px to shape its ring — without this the field itself
   squares off the moment it takes focus. */
.f-input:focus, .f-input:focus-visible,
.f-select:focus, .f-select:focus-visible{outline:none;border-color:#FF3B00;border-radius:12px;}

/* "Services Required" — pill toggle chips rather than checkbox+label rows;
   same shape language as the case-studies filter chips (.cs-chip). Native
   checkbox for a11y/keyboard/form semantics, visually hidden and stretched
   over the pill so the whole chip is the hit target. */
.svc-chip{position:relative;display:inline-flex;align-items:center;border:2px solid #17140F;border-radius:9999px;background-color:#fff;padding:10px calc(18px - .02em) 10px 18px;font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:.02em;cursor:pointer;transition:background-color .15s ease,color .15s ease;}
.svc-chip:hover{background-color:#FDDB0A;}
.svc-chip:has(.svc-chip-input:checked){background-color:#FDDB0A;color:#17140F;}
.svc-chip:has(.svc-chip-input:checked):hover{background-color:#FDDB0A;}
.svc-chip-input{position:absolute;inset:0;opacity:0;margin:0;cursor:pointer;}
.svc-chip:has(.svc-chip-input:focus-visible){outline:3px solid #FF3B00;outline-offset:3px;}
/* From the form's two-column breakpoint up, the three chips share the row
   equally so the group runs the full width of the submit button below it. */
@media (min-width:640px){
  .svc-chip{flex:1 1 0;justify-content:center;text-align:center;}
}

/* Contact form states, driven by the submit handler in script.js.
   Error copy stays ink, not orange: orange on the yellow-tint card is ~3:1,
   too faint for body-size text. The orange lives in the chip borders and the
   dot, the same dot the hero pill uses, so a problem reads at a glance without
   costing legibility. */
.svc-chip-group-invalid .svc-chip{border-color:#FF3B00;}
.f-error,.f-status.is-error{display:flex;align-items:baseline;gap:8px;margin-top:8px;font-size:1rem;font-weight:700;line-height:1.4;color:#17140F;}
.f-error::before,.f-status.is-error::before{content:"";flex:none;width:8px;height:8px;border-radius:9999px;background:#FF3B00;border:1px solid #17140F;transform:translateY(-1px);}
.f-error[hidden]{display:none;}
.f-status{margin-top:-8px;font-size:1rem;line-height:1.5;color:rgba(23,20,15,.7);}
.f-status:empty{display:none;}
.f-status a{text-decoration:underline;text-underline-offset:3px;}
.f-status.is-error{margin-top:-8px;font-weight:400;}
.btn-hard[aria-busy="true"]{cursor:progress;background-color:#FDDB0A;}
/* Honeypot: out of sight and out of the accessibility tree, but still in the
   form so a bot posting every field fills it. Not display:none, which some
   bots read as "skip". */
.f-hp{position:absolute;left:-10000px;top:auto;width:1px;height:1px;overflow:hidden;}

/* Application received. The form card gives way to a wider two-part card:
   the team photo (the people who will read the application) on the left,
   the heading, three steps and a case-studies link on the right. On phones
   the photo sits on top. The slogan sticker straddles the divide between
   the two, the one piece of brand play on the panel. It enters once, from
   slightly below, on the exponential ease the rest of the site uses. */
.f-sent{display:grid;overflow:hidden;max-width:920px;margin:0 auto;background:#FDF1A7;border:2px solid #17140F;border-radius:22px;}
.f-sent:focus{outline:none;}
.f-sent[hidden]{display:none;}
.f-sent-photo{position:relative;border-bottom:2px solid #17140F;}
.f-sent-photo>img:first-child{display:block;width:100%;height:100%;aspect-ratio:16/10;object-fit:cover;object-position:50% 35%;}
.f-sent-sticker{position:absolute;z-index:1;width:96px;height:auto;right:20px;bottom:-34px;transform:rotate(-8deg);filter:drop-shadow(0 6px 10px rgba(23,20,15,.18));}
.f-sent-body{padding:48px 24px 32px;}
.f-sent-steps{display:grid;gap:16px;margin-top:24px;}
.f-sent-steps li{display:grid;grid-template-columns:32px 1fr;gap:14px;align-items:start;}
/* HNP-UI, as on .ce-num: the plain HNP file's metrics sit a digit low in Safari. */
.f-sent-steps li>span{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:9999px;border:2px solid #17140F;background:#FDDB0A;font-family:'HNP-UI','HNP','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:700;font-size:1rem;line-height:1;font-variant-numeric:tabular-nums;}
.f-sent-steps li>p{font-size:1rem;line-height:1.5;color:#17140F;padding-top:4px;}
@media (min-width:640px){
  .f-sent{grid-template-columns:5fr 7fr;}
  .f-sent-photo{border-bottom:0;border-right:2px solid #17140F;}
  .f-sent-photo>img:first-child{aspect-ratio:auto;min-height:100%;}
  .f-sent-sticker{width:112px;right:-56px;bottom:auto;top:50%;transform:translateY(-50%) rotate(-8deg);}
  .f-sent-body{padding:40px 40px 40px 72px;}
}
@media (min-width:1024px){
  .f-sent-body{padding:48px 48px 48px 80px;}
}
@media (prefers-reduced-motion:no-preference){
  .f-sent:not([hidden]){opacity:0;transform:translateY(16px);transition:opacity .6s cubic-bezier(.16,1,.3,1),transform .6s cubic-bezier(.16,1,.3,1);}
  .f-sent.is-in{opacity:1;transform:none;}
}

/* ── Paid Advertising: the growth ledger (.gl, #included) ──────────────────
   The 4-phase system as a stack of four white cards, one per phase, each
   card the phase's pill, its title and a progress bar on the left, the three
   things that phase delivers on the right. They began as four ruled
   rows inside one card; splitting them out made the section read lighter. It replaced the tabbed stepper
   (.gs, now gone) — four photos never arrived, and the step-by-step is the
   argument, so the deliverables carry the row instead of a frame.

   The bar is the sequence cue, and the only decoration in the card: a
   quarter, a half, three quarters, full, on a ramp that starts at the
   interaction yellow and lands on the brand orange at phase four — the phase
   that closes the loop back to phase one. The two middle stops are mixed from
   those two tokens the way the PULP tiles mix theirs, so no new hex exists.
   Cards are separated by open yellow-tint ground, not a rule.

   The default is the finished ledger: bars poured, every deliverable in
   place. script.js adds .gl-armed on load (bars empty, lists hidden) and
   .gl-live once the card is on screen, which pours the four bars in order,
   each row's deliverables sliding in behind its bar. Transform and opacity
   only, from an already-authored end state, so a blocked script or reduced
   motion leaves the card complete rather than empty. */
.gl{display:flex;flex-direction:column;gap:16px;}
.gl-row{--gl-i:0;--gl-w:25%;--gl-c:#FDDB0A;display:grid;grid-template-columns:1fr;gap:24px 48px;padding:28px 24px;background:#fff;border:2px solid #17140F;border-radius:22px;}
.gl-row:nth-child(2){--gl-i:1;--gl-w:50%;--gl-c:color-mix(in srgb,#FDDB0A 60%,#FF3B00);}
.gl-row:nth-child(3){--gl-i:2;--gl-w:75%;--gl-c:color-mix(in srgb,#FDDB0A 25%,#FF3B00);}
.gl-row:nth-child(4){--gl-i:3;--gl-w:100%;--gl-c:#FF3B00;}
.gl-head{min-width:0;}
/* The phase pill is .cs-beat without its orange dot — the dot would be a fifth
   accent in a card whose one accent is the bar. Even on the HNP-UI metrics the
   caps sit a shade high in an 11px line, so the padding is split 6/4 rather
   than 5/5 to put the label on the pill's centre line; measured at 4x. */
.gl-step{display:inline-flex;align-items:center;font-family:'HNP-UI','HNP','Helvetica Neue',Helvetica,Arial,sans-serif;border:2px solid #17140F;border-radius:9999px;background:#fff;padding:5px calc(14px - .12em) 5px 14px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;line-height:17px;color:#17140F;font-variant-numeric:tabular-nums;}
.gl-title{margin-top:16px;font-weight:700;text-transform:uppercase;font-size:1.375rem;line-height:1.1;text-wrap:balance;color:#17140F;}
.gl-bar{display:block;position:relative;height:8px;margin-top:20px;border-radius:9999px;background:rgba(23,20,15,.08);overflow:hidden;}
.gl-fill{position:absolute;left:0;top:0;bottom:0;width:var(--gl-w);border-radius:9999px;background:var(--gl-c);transform-origin:left center;}
.gl-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px;}
.gl-list li{--gl-j:0;display:flex;align-items:flex-start;gap:14px;font-size:1rem;line-height:1.5;color:rgba(23,20,15,.78);text-wrap:pretty;}
.gl-list li:nth-child(2){--gl-j:1;}
.gl-list li:nth-child(3){--gl-j:2;}
/* The arrow disc is the deliverables checklist's icon tile as a pill: yellow,
   2px ink, one drawn 2px-stroke arrow. Pulled up 2px so its optical centre
   sits on the first line's x-height rather than its cap line. */
.gl-arrow{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;margin-top:-2px;border:2px solid #17140F;border-radius:9999px;background:#FDDB0A;color:#17140F;}
.gl-arrow svg{width:14px;height:14px;}
/* The pour. Each bar takes .22s longer to start than the one above it, and a
   row's three lines follow its bar at .08s steps, so the eye reads the card
   top to bottom at the speed the sequence is meant to be read. Exponential
   ease-out, no overshoot. */
.gl-armed .gl-fill{transform:scaleX(0);}
.gl-armed .gl-list li{opacity:0;transform:translateX(-10px);}
.gl-live .gl-fill{animation:gl-pour .9s cubic-bezier(.2,.7,.2,1) both;animation-delay:calc(var(--gl-i) * .22s + .1s);}
.gl-live .gl-list li{animation:gl-in .5s cubic-bezier(.2,.7,.2,1) both;animation-delay:calc(var(--gl-i) * .22s + .3s + var(--gl-j) * .08s);}
@keyframes gl-pour{from{transform:scaleX(0);}to{transform:scaleX(1);}}
@keyframes gl-in{from{opacity:0;transform:translateX(-10px);}to{opacity:1;transform:none;}}
@media (min-width:640px){
  .gl{gap:20px;}
  .gl-row{padding:32px;}
  .gl-title{font-size:1.625rem;}
}
@media (min-width:1024px){
  .gl-row{grid-template-columns:minmax(0,9fr) minmax(0,11fr);gap:48px;padding:40px 48px;align-items:center;}
  .gl-title{margin-top:20px;font-size:1.75rem;}
  .gl-bar{margin-top:24px;}
  .gl-list{gap:16px;}
  .gl-list li{font-size:1.0625rem;}
}
@media (prefers-reduced-motion: reduce){
  .gl-armed .gl-fill{transform:none;}
  .gl-armed .gl-list li{opacity:1;transform:none;}
  .gl-live .gl-fill,.gl-live .gl-list li{animation:none;}
}
/* ── Ad Creatives: the creative engine (.ce, #system) ────────────────────
   The 4-step system as four white boxes stacked down the page. Each li is a
   two-column grid: the numbered disc in a narrow left column, the box in the
   rest. A rail (.ce-rail) runs down the disc column behind the discs, from
   the first disc's centre towards the last's. Its ends are CSS calc from the
   box padding (--ce-p) and the disc diameter (--ce-d): exact at the top, and
   at the bottom only if the last box were as tall as its title, so script.js
   measures the discs and pins both ends to their centres.

   The rail is the section's one motion, and it is the About Us juice rail
   in miniature: a 2px ink line at 15% that fills orange as the visitor
   scrolls, each disc flipping from yellow to orange the moment the fill
   passes it. DEFAULT STATE IS FULLY POURED. script.js adds .ce-live to take
   the rail back to pre-scroll and drives --fill and .is-reached from the
   scroll position, so a blocked script or reduced motion shows the finished
   section rather than an empty one, and tools/screenshot.mjs strips
   .ce-live to capture that same end state.

   Inside the box: the step's name and one line on what it is for, then the
   three things it delivers. From 1024px those are the box's two columns,
   with the list's first line centred on the disc like the name is. Hover
   lifts the box on the shared .card-hard offset and the disc grows a touch
   with it, so the step reads as one object.

   The disc sits on its own compositing layer (translateZ(0)). The fill is
   composited for the scroll, and as it grows past a disc Safari would
   otherwise promote and demote the disc on the fly, re-rasterising the
   digit a subpixel off each time, so the number visibly jumped inside its
   circle while scrolling. Pinning the layer keeps the digit still. */
.ce{--ce-d:36px;--ce-p:20px;--ce-gap:14px;--ce-stack:14px;position:relative;list-style:none;margin:0;padding:0;}
.ce-rail{position:absolute;left:calc(var(--ce-d) / 2 - 1px);top:calc(var(--ce-p) + 2px + var(--ce-d) / 2);bottom:calc(var(--ce-p) + 2px + var(--ce-d) / 2);width:2px;background:rgba(23,20,15,.15);pointer-events:none;overflow:hidden;}
.ce-fill{position:absolute;left:0;right:0;top:0;height:100%;background:#FF3B00;transform-origin:top center;transform:scaleY(1);will-change:transform;}
.ce-live .ce-fill{transform:scaleY(var(--fill,0));}
.ce-card{position:relative;display:grid;grid-template-columns:var(--ce-d) minmax(0,1fr);column-gap:var(--ce-gap);align-items:start;}
.ce-card + .ce-card{margin-top:var(--ce-stack);}
/* The disc: the ledger's yellow arrow disc grown to carry a numeral, orange
   once the fill has reached it. Its top edge sits on the box's padding line
   (2px border + --ce-p) so its centre is on the title's centre line. No
   padding nudge: with the balanced HNP-UI metrics the figure measures within
   .4px of the disc's centre in both Chrome and WebKit at 4x, and the 1px of
   top padding this once carried pushed it the better part of a pixel low. */
.ce-num{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;width:var(--ce-d);height:var(--ce-d);margin-top:calc(var(--ce-p) + 2px);border:2px solid #17140F;border-radius:9999px;background:#FF3B00;color:#17140F;font-family:'HNP-UI','HNP','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:700;font-size:1rem;line-height:1;font-variant-numeric:tabular-nums;transform:translateZ(0);backface-visibility:hidden;-webkit-backface-visibility:hidden;transition:background-color .3s ease,transform .35s cubic-bezier(.16,1,.3,1);}
.ce-live .ce-card:not(.is-reached) .ce-num{background:#FDDB0A;}
.ce-card:hover .ce-num{transform:translateZ(0) scale(1.08);}
.ce-box{min-width:0;background:#fff;border:2px solid #17140F;border-radius:22px;padding:var(--ce-p);display:grid;grid-template-columns:minmax(0,1fr);row-gap:18px;}
.ce-head{min-width:0;}
.ce-title{display:flex;align-items:center;min-height:var(--ce-d);font-weight:700;text-transform:uppercase;font-size:1.25rem;line-height:1.1;color:#17140F;text-wrap:balance;}
.ce-text{margin-top:8px;font-size:1rem;line-height:1.55;color:rgba(23,20,15,.7);text-wrap:pretty;max-width:34ch;}
/* The three deliverables, ticked off with the ledger's yellow arrow disc
   (.gl-arrow) so all three service pages list what the client gets the same
   way. Below 1024px the list sits under the name and line, divided by the
   2px ink rule at 15%; from 1024px it is the box's right column and the
   rule goes. */
.ce-list{list-style:none;margin:0;padding:16px 0 0;border-top:2px solid rgba(23,20,15,.15);display:flex;flex-direction:column;gap:12px;}
.ce-list li{display:flex;align-items:flex-start;gap:12px;font-size:.9375rem;line-height:1.5;color:rgba(23,20,15,.78);text-wrap:pretty;}
@media (min-width:640px){
  .ce{--ce-d:40px;--ce-p:24px;--ce-gap:18px;--ce-stack:16px;}
  .ce-title{font-size:1.5rem;}
  .ce-list li{font-size:1rem;}
}
@media (min-width:1024px){
  .ce{--ce-d:48px;--ce-p:28px;--ce-gap:28px;--ce-stack:18px;}
  .ce-num{font-size:1.125rem;}
  .ce-box{padding:var(--ce-p) 32px;grid-template-columns:minmax(0,1fr) minmax(0,2fr);column-gap:32px;row-gap:0;align-items:start;}
  .ce-title{font-size:1.75rem;}
  .ce-text{margin-top:10px;font-size:1rem;max-width:none;}
  .ce-list{padding-top:0;border-top:0;gap:14px;margin-top:calc((var(--ce-d) - 1rem * 1.5) / 2);}
  .ce-list li{font-size:1rem;}
}
@media (prefers-reduced-motion: reduce){
  .ce-live .ce-fill{transform:scaleY(1);}
  .ce-num{transition:none;}
  .ce-live .ce-card:not(.is-reached) .ce-num{background:#FF3B00;}
  .ce-card:hover .ce-num{transform:translateZ(0);}
}
/* ── How Organic Worlds works (/organic-worlds #how) ──
   Three white cards, each opening on a "Step N" pill (.gl-step, shared with
   the Paid Advertising phases) and a heading row with the yellow icon tile
   on the right. The only rule the cards need is the three-point list. */

/* The three-point list per step. The marker is the yellow arrow disc from
   the Paid Advertising phases (.gl-arrow), so both service pages tick off
   what the client gets the same way. */
.hw-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:12px;}
.hw-list li{display:flex;align-items:flex-start;gap:12px;font-size:.9375rem;line-height:1.6;color:rgba(23,20,15,.8);text-wrap:pretty;}
}

/* ── ambient motion ──
   The section renders complete, like everything else on the site — nothing here
   waits on a scroll position and there is no entrance to miss. It simply keeps
   running: the arc's dots march at a steady 12 units a second, an orange bead
   makes a slow pass along the line, each tile
   breathes, and every illustration carries one loop of its own — the magnifier
   drifts over what it is auditing, the play tile pulses, the coin bobs on its
   trajectory.

   Everything is transform, opacity or stroke-dashoffset — no shadow pulses, no
   filters. --gp-t is a phase offset, not a delay before anything appears: it is
   only what stops the four tiles breathing in lockstep. */
.gp-step{--gp-t:calc(var(--i) * .55s);}

@keyframes gp-march{to{stroke-dashoffset:-11.01;}}
@keyframes gp-trail{to{stroke-dashoffset:-9.01;}}
/* .gp-dash is the same march over 9/9 dashes rather than dots. The offset has
   to be one whole period of whatever pattern it drives, or the loop jumps. */
@keyframes gp-trail-dash{to{stroke-dashoffset:-18;}}
@keyframes gp-float{to{transform:translateY(-7px);}}
@keyframes gp-scan{0%,100%{transform:translate(0,0);}50%{transform:translate(5px,4px);}}
@keyframes gp-pulse{0%,100%{transform:scale(1);}50%{transform:scale(1.06);}}
@keyframes gp-bob{to{transform:translateY(-5px);}}
/* One slow pass along the whole line, then straight back to the start. The
   fades are only wide enough to hide the two terminal dots, so the bead is
   never seen to jump and never sits on top of a cap. */
@keyframes gp-run{
  0%  {stroke-dashoffset:0;opacity:0;}
  4%  {stroke-dashoffset:-40;opacity:1;}
  96% {stroke-dashoffset:-960;opacity:1;}
  100%{stroke-dashoffset:-1000;opacity:0;}
}

.gp-line{animation:gp-march .9s linear infinite;}
.gp-bead{animation:gp-run 11s linear infinite;}
.gp-card{animation:gp-float 3.6s ease-in-out var(--gp-t) infinite alternate;}
.gp-scan{animation:gp-scan 3.4s ease-in-out var(--gp-t) infinite;}
.gp-pulse{animation:gp-pulse 3.2s ease-in-out var(--gp-t) infinite;}
.gp-bob{animation:gp-bob 3s ease-in-out var(--gp-t) infinite alternate;}
.gp-trail{animation:gp-trail 2.6s linear infinite;}
.gp-dash{animation:gp-trail-dash 2.6s linear infinite;}

@media (prefers-reduced-motion: reduce){
  .gp-line,.gp-bead,.gp-card,.gp-scan,.gp-pulse,.gp-bob,.gp-trail,.gp-dash{animation:none;}
  /* gp-run ends invisible, so park the bead on the far terminal dot instead */
  .gp-bead{stroke-dashoffset:-1000;}
}


/* ── 404 (/404.html) ──
   The one page whose links are all root-absolute: Vercel serves this file for
   any path that doesn't resolve, so a relative href would resolve against
   whatever URL the visitor mistyped and fail a second time.

   The middle 0 is a citrus wheel rather than a numeral. The brandmark is
   already "a playful interpretation of juice and pulp mixing, derived from the
   hole in the P" and the guidelines allow it to rotate, so the substitution is
   the identity's own move rather than a new one — and the site's existing
   17.33s slowspin carries it, the single authored moment on a page that has
   nothing else to say. Its ink outline is drawn in viewBox units rather than
   pinned to the site's 2px — see the note on the mark in 404/content.html.

   The band sizes to its content: the yellow-tint services band below it fills
   out the first screen, so the 404 no longer needs a full-screen minimum
   height (which on a phone only pushed it down under a cream gap). */

/* The services band reuses the nav dropdown's rows, which start hidden until
   the dropdown opens; here they are always shown. */
.fof-svc .mega-row{opacity:1;transform:none;}

/* The wheel sits on the baseline, not in the middle of the flex line, because
   that is where a real 0 sits. `align-items:center` was the first attempt and
   it is the wrong tool here: it centres items on the flex line box, and with
   HNP at line-height .72 the baseline lands well below that box's centre — the
   glyphs actually overflow the box at the top — so the disc rode ~15px low at
   desktop and read as having slipped off the line. Baseline alignment
   synthesises the SVG's baseline from its bottom edge, which then tracks the
   text however the line-height or the font's metrics change.

   Sizes are quoted against cap height (HNP digits ink .70em), never the em.
   The element is .74em and the disc is 96% of it (r45.5 + 2.5 stroke of a 100
   viewBox), so the disc renders at ~.71em — cap height plus the sliver of
   overshoot a filled round shape needs to look the same size as a flat-sided
   one. Sitting the disc's bottom on the baseline would then leave its centre
   .02em above the cap box's centre, so it is nudged back down by exactly that.
   Both numbers are ratios of the same cap height, so they hold at every step
   of the clamp. Verify by pixel, not by font metrics: the ascent reported by
   canvas measureText does not predict where this baseline lands. */
.fof-num{display:flex;align-items:baseline;justify-content:center;gap:.04em;font-weight:700;line-height:.72;font-size:clamp(4.25rem,13vw,7.5rem);color:#17140F;}
.fof-wheel{width:.74em;height:.74em;flex:0 0 auto;display:block;position:relative;top:.02em;}

/* homepage services cards: arrow-led list rows. The marker is the yellow arrow
   disc from the service pages (.gl-arrow, defined with the Paid Advertising
   phases above), so the homepage ticks off what each service includes the same
   way those pages do. The list is set in HNP-UI (see the top of this file) at
   regular weight in the same .78 ink the .gl-list rows use, so the rows read
   like the service pages' rather than bolder; the 28px disc is pulled up 2px
   by its own rule to sit on the first line. */
.svc-list{font-family:'HNP-UI','HNP','Helvetica Neue',Helvetica,Arial,sans-serif;}
.svc-list li{display:flex;align-items:flex-start;gap:12px;font-weight:400;color:rgba(23,20,15,.78);}
/* The pill is the only link, but the whole card is the click target: the link's
   ::after stretches over the card, so the cursor is a hand anywhere on it and a
   click anywhere opens that service. The badge sits under the overlay. */
.svc-card a::after{content:"";position:absolute;inset:0;}
.svc-card > img{pointer-events:none;}

/* ── Privacy Policy (/privacy-policy/) ──────────────────────────────────────
   A Read page: one long document and a contents list. The type is set here
   rather than per paragraph in the markup so the policy stays plain HTML that
   can be re-pasted from the copy deck without re-classing every element.
   Headings are ALL CAPS per the guidelines; the short orange rule above each
   h2 is the only colour in the body, so the eye lands on section starts and
   nowhere else. Body sits at 1.7 leading on a ~68ch measure. */
.pp-body{max-width:68ch;}
.pp-body h2{font-weight:700;text-transform:uppercase;line-height:1.05;font-size:1.25rem;margin-top:3rem;padding-top:1.25rem;border-top:2px solid #17140F;scroll-margin-top:7rem;text-wrap:balance;}
/* The first heading sits right under the section's own rule, so it drops its
   rule rather than drawing a second one a few pixels below. */
.pp-body h2:first-child{margin-top:0;padding-top:0;border-top:0;}
.pp-body h2::before{content:"";display:block;width:2.5rem;height:.375rem;border-radius:9999px;background:#FF3B00;margin-bottom:1rem;}
.pp-body h3{font-weight:700;text-transform:uppercase;line-height:1.1;font-size:1rem;margin-top:2rem;scroll-margin-top:7rem;}
.pp-body p{margin-top:1rem;line-height:1.7;color:rgba(23,20,15,.8);}
.pp-body h2 + p,.pp-body h3 + p{margin-top:1rem;}
.pp-body strong{color:#17140F;font-weight:700;}
.pp-body em{font-style:italic;}
/* Links in the document carry no underline; they read as plain ink and go
   bold on hover, per the client's call. */
.pp-body a{color:#17140F;text-decoration:none;}
.pp-body a:hover,.pp-body a:focus-visible{font-weight:700;}
.pp-body ul{margin-top:1rem;display:flex;flex-direction:column;gap:.625rem;}
.pp-body li{position:relative;padding-left:1.75rem;line-height:1.7;color:rgba(23,20,15,.8);}
/* A drawn bullet in the brand's own idiom — a yellow disc with the 2px ink
   rule — rather than the browser's grey dot. */
.pp-body li::before{content:"";position:absolute;left:0;top:.55em;width:.75rem;height:.75rem;border-radius:9999px;background:#FDDB0A;border:2px solid #17140F;}
.pp-card-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;color:rgba(23,20,15,.55);}
.pp-card-value{margin-top:.5rem;line-height:1.6;color:#17140F;}
.pp-body address .pp-card-value{margin-top:.5rem;}

/* Contents list: one link per section, the current one marked in orange.
   script.js adds .is-current as the matching heading scrolls into view;
   without JS no link is marked, which is complete rather than wrong. */
.pp-toc-list{counter-reset:none;display:flex;flex-direction:column;}
.pp-toc-list a{display:block;padding:.4rem 0 .4rem .875rem;border-left:2px solid rgba(23,20,15,.15);font-size:.875rem;line-height:1.35;color:rgba(23,20,15,.7);transition:color .15s ease,border-color .15s ease;}
.pp-toc-list a:hover{color:#17140F;border-left-color:#17140F;}
.pp-toc-list a.is-current{color:#FF3B00;border-left-color:#FF3B00;font-weight:700;}
.pp-toc-box summary::-webkit-details-marker{display:none;}
.pp-toc-box[open] .pp-toc-chev{transform:rotate(180deg);}
.pp-toc-box .pp-toc-list{padding-top:1rem;}
.pp-toc-box .pp-toc-list a{padding-top:.5rem;padding-bottom:.5rem;}

/* INTRO LOADER (homepage only). The brandmark in cream on an orange sheet,
   centred and present from the first frame, takes one turn, then its cream
   body alone grows to the screen edges, which is the page's own ground, so
   the crossfade that follows is cream onto cream: the letters fade out as
   the page fades in. These rules draw the first
   frame and the resting states; the sequence runs in script.js with the Web
   Animations API so every duration lives in one place. `html.intro-skip` is
   set by the one-line gate in the homepage <head> before first paint, for a
   repeat visit in the same tab. Without JS the failsafe clears the overlay
   after 4s so the page is never trapped behind it. Scrolling is held off by
   script.js swallowing wheel, touch and key scrolls while it plays, not by
   overflow:hidden on <html>, so a desktop scrollbar never shifts the layout. */
.intro{position:fixed;inset:0;z-index:1000;display:grid;place-items:center;overflow:hidden;background-color:#FF3B00;animation:intro-failsafe 0s 4s forwards;}
@keyframes intro-failsafe{to{visibility:hidden;}}
html.intro-skip .intro{display:none;}
/* While the intro plays the page waits unseen behind the sheet, then crosses
   in as the sheet fades (script.js runs both fades together and drops the
   class). The delayed animation is the no-script floor: if script.js never
   gets to it, the page still arrives on its own at 4s. */
html.intro-live body > :not(#introLoader){opacity:0;animation:intro-page-in .9s cubic-bezier(.16,1,.3,1) 4s forwards;}
@keyframes intro-page-in{to{opacity:1;}}
/* Two stacked drawings of the same mark: the cream silhouette behind, which
   is what scales, and the orange letters in front, which never move. The turn
   is on the wrapper so both pivot together on the true centre. The letters
   are the in-flow layer and the body is absolute over the same box, so
   dropping the body later cannot change the box the letters are centred in
   (as the in-flow layer, it collapsed the box and the letters sagged). */
.intro-mark{width:min(200px,42vw);}
.intro-spin{position:relative;transform-origin:50% 50%;}
.intro-mark svg{display:block;width:100%;height:auto;overflow:visible;}
.intro-body{position:absolute;inset:0;transform-origin:50% 50%;}
.intro-body path{fill:#FFFBE7;}
.intro-letters{position:relative;}
.intro-letters path{fill:#FF3B00;}
/* Once the body has covered the screen the overlay takes over as the cream
   sheet and the 8000px silhouette is dropped, so the fade composites a flat
   layer with only the letters on it. */
.intro.is-flood{background-color:#FFFBE7;}
.intro.is-flood .intro-body{display:none;}
