/* ============================================================================
   HIP PORTFOLIO — SHARED FOUNDATION
   Brand-neutral reliability + accessibility layer. Loaded FIRST on every brand,
   before that brand's art direction. Contains no brand colours and no brand
   typefaces: it defines the SHAPE of things, never the personality.

   Rules of engagement (these pages are production markup, not a prototype):
     * never change layout structure, only its rendering
     * never hide content a customer needs
     * never touch the upload widget's or checkout's behavioural classes
     * every value here must degrade safely if a brand file omits its token
   ========================================================================= */

:root{
  /* Fluid rhythm. The portfolio's defect was UNIFORM 64/64 padding on every
     section, which flattens hierarchy. Brands compose rhythm from these three
     rather than repeating one value. */
  --rhythm-tight: clamp(28px, 4.2vw, 48px);
  --rhythm-base:  clamp(44px, 6.5vw, 84px);
  --rhythm-open:  clamp(64px, 9vw, 124px);

  --measure: 68ch;          /* reading width for body copy            */
  --measure-tight: 46ch;    /* reading width for leads and captions   */
  --rule: 1px;

  /* Motion. One duration, one easing, so unrelated components can't drift. */
  --ease: cubic-bezier(.22,.61,.36,1);
  --dur: .34s;
}

/* --- overflow guard --------------------------------------------------------
   A single wide child (a table, a marquee, an absolutely-placed decoration)
   silently gives the whole document a horizontal scrollbar on mobile. Contain
   it at the root rather than hunting the offender on ten brands. */
html{ overflow-x: hidden; }
body{ overflow-x: clip; }

/* --- typography rhythm -----------------------------------------------------
   Headings balance; body copy gets a real measure and a pretty last line. */
h1,h2,h3,h4{ text-wrap: balance; }
p, li{ text-wrap: pretty; }
p{ max-width: var(--measure); }
/* ...but never constrain a paragraph that a layout has already sized, or that
   is acting as a centred lead inside a hero. */
[class*="grid"] p, [class*="col"] p, [class*="card"] p,
[class*="__copy"] p, td p, li p{ max-width: none; }

/* Numbers that sit in columns, prices, tracking codes and specs align. */
table, .price, [class*="price"], [class*="tracking"], time, .num{
  font-variant-numeric: tabular-nums;
}

/* --- controls --------------------------------------------------------------
   WCAG 2.5.8 Target Size (Minimum) is 24x24 CSS px. Inline links in prose are
   exempt, so this deliberately targets controls only. */
button, .btn, [role="button"], input[type="submit"], input[type="button"],
select, summary, nav a, .nav a, footer a.btn{
  min-height: 24px;
  min-width: 24px;
}
/* Primary actions get a comfortable touch target on phones, where the whole
   portfolio takes the large majority of its traffic. */
@media (max-width: 767px){
  .btn, button:not([class*="close"]):not([aria-label*="lose"]),
  input[type="submit"]{ min-height: 44px; }
}

button, .btn, [role="button"], a, input, select, textarea, summary{
  transition: background-color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}

/* --- focus -----------------------------------------------------------------
   WCAG 2.4.7. One visible, high-contrast ring everywhere, including on the
   file-input label the upload zone uses (which is a label, not a button, and
   therefore missed by naive :focus-visible rules). */
/* NOTE: :where() has ZERO specificity, so an existing `outline:none` in the
   shipped reset beats it and the ring never paints. These need real specificity. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
textarea:focus-visible, summary:focus-visible, [tabindex]:focus-visible,
.btn:focus-visible, [role="button"]:focus-visible{
  outline: 3px solid var(--focus-ring, #1978c6) !important;
  outline-offset: 3px !important;
  border-radius: 3px;
}
label:has(input[type="file"]:focus-visible),
[class*="dropzone"]:has(input:focus-visible){
  outline: 3px solid var(--focus-ring, #1978c6);
  outline-offset: 3px;
}
/* Remove the UA ring only where we have supplied our own. */
a:focus:not(:focus-visible), button:focus:not(:focus-visible),
input:focus:not(:focus-visible), select:focus:not(:focus-visible),
textarea:focus:not(:focus-visible), summary:focus:not(:focus-visible){
  outline: none;
}

/* --- images ----------------------------------------------------------------
   Art direction needs directed cropping, not stretched fills. Brands set
   --focal on a specific image to move the crop's centre of interest. */
img{ max-width: 100%; height: auto; }
[class*="hero"] img, .media img, figure img{
  object-fit: cover;
  object-position: var(--focal, 50% 50%);
}

/* --- scrim -----------------------------------------------------------------
   Text over photography is the portfolio's most common contrast risk. This
   gives a brand a guaranteed-contrast ground without darkening the whole
   image: a directional gradient anchored where the type actually sits. */
.has-scrim{ position: relative; isolation: isolate; }
.has-scrim::after{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
  background: var(--scrim, linear-gradient(180deg,
              rgba(8,12,28,.78) 0%, rgba(8,12,28,.62) 46%, rgba(8,12,28,.80) 100%));
}
.has-scrim > *{ position: relative; z-index: 1; }

/* --- wide content ----------------------------------------------------------
   Tables and code scroll inside their own container so the page body doesn't. */
table{ display: block; max-width: 100%; overflow-x: auto; }

/* --- reduced motion --------------------------------------------------------
   NOTE: `.reveal` must be named explicitly. A `*` rule loses to `.reveal`'s
   class specificity even with !important, so a universal reset alone leaves
   scroll-reveal content stuck at opacity:0 for anyone who asks for less
   motion -- i.e. the content becomes invisible, not merely static. */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .reveal, .reveal.in, [class*="reveal"]{
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* --- gradient headline text ------------------------------------------------
   The portfolio sets headlines with
       background:<gradient>; background-clip:text; -webkit-text-fill-color:transparent
   which makes `color` inert -- the visible colour comes from the gradient. On
   SentInstantly that gradient is a pale lavender sitting on a pale photograph,
   which is the portfolio's worst contrast failure and cannot be fixed by
   setting `color` at any specificity. It also degrades badly: if the gradient
   fails to paint, the text is transparent, i.e. invisible.

   Text is restored to a real, single, inspectable colour. Brands then set that
   colour per context (white on photography, brand ink on paper). */
/* Scoping note: neutralising only .grad-text/.grad-text-dark was NOT enough.
   The same background-clip:text pattern is repeated by at least three more
   rule sets -- .hero-cro-2026__headline, and descendant selectors such as
   `.location-use-cases > .page-width > h2` which apply to LOCATION pages, the
   bulk of the portfolio. Enumerating them is fragile, so every heading is
   restored to a painted colour instead. Headings in this system take their
   emphasis from weight, scale and rhythm, never from a clipped gradient. */
.grad-text, .grad-text-dark, [class*="grad-text"],
h1, h2, h3, h4,
.display, [class*="headline"], [class*="__heading"]{
  background-image: none !important;
  -webkit-background-clip: border-box !important;
  background-clip: border-box !important;
  -webkit-text-fill-color: currentColor !important;
}
.grad-text, .grad-text-dark, [class*="grad-text"]{ color: inherit; }

/* Removing the gradient is only half the fix. Those rules set BOTH
   `-webkit-text-fill-color:transparent` AND `color:transparent`, so restoring
   the fill to `currentColor` resolves it straight back to a transparent colour
   and the heading renders invisible -- strictly worse than the gradient it
   replaced. (Measured on labelsbyhand: .how-it-works-hip__heading came back
   color:rgba(0,0,0,0) with background-image:none.)

   So headings take their colour from a token instead. It is context-aware by
   inheritance -- dark sections redefine it -- and brands set the light value to
   their own ink, so one !important here cannot flatten brand identity. */
:root{ --heading-ink: #1d1d1f; --body-ink: #55606F; }
section.section--dark, .section--dark, [class*="--dark"]{
  --heading-ink: #ffffff; --body-ink: rgba(255,255,255,.86);
}

/* SCOPE, and why it is narrow.
   A first version applied this to every h1-h4 on the page. That repainted
   .checkout-title -- which the checkout page deliberately sets to WHITE over a
   dark fixed overlay -- to the brand ink, leaving "Secure Checkout" almost
   invisible. (Measured: live rgb(255,255,255) vs rgb(14,27,77) locally.)

   The gradient-clip pattern only ever appears (a) on elements carrying a
   gradient class, and (b) on plain headings INSIDE a <section> -- e.g.
   `.location-use-cases > .page-width > h2` and `.how-it-works-hip__heading`.
   Standalone page headings outside a section keep whatever colour their own
   page gave them, so transactional surfaces are never touched. */
.grad-text, .grad-text-dark, [class*="grad-text"],
[class*="headline"], [class*="__heading"],
[class*="location-"] > .page-width > h2,
[class*="benefits"] > .page-width > h2{
  color: var(--heading-ink) !important;
}

/* DARK-GROUND CONTAINERS.
   Every surface here paints light text on a dark photograph or overlay. The
   originals achieved that with a light background-clip gradient; once that is
   removed, both the heading AND the body copy need light values or they turn
   dark-on-dark and vanish.

   This bit me three times. First .checkout-title. Then .hero-cro-2026__headline
   -- the hero of HIP's 194 location pages -- rendering navy-on-navy. Then the
   sub-brands' 1,629 location heroes, whose <section> carries an INLINE
   color:#fff that a blanket `main section h1..h4 !important` silently beat.

   The lesson: the override is now aimed ONLY at the selectors that actually
   carry the background-clip gradient, so nothing else inherits a colour it
   never asked for. And "is it transparent?" is not a legibility check -- every
   dark-ground heading is now measured against its OWN rendered background. */
.checkout-page, .checkout-page__overlay, .checkout-container,
.success-state, .loading-state, .error-state, [class*="page__overlay"],
.hero-cro-2026, .hero-cro-2026__overlay, .hero-cro-2026__content,
.hip-uphero, .hip-uphero__copy, .vhero, .vhero__overlay,
.mission-hero, .mission-hero-inner,
.blog-hero, .about-hero, .contact-hero, .track-hero,
[class$="-hero"], [class*="-hero "]{
  --heading-ink: #ffffff;
  --body-ink: rgba(255,255,255,.90);
}

/* --- location hero on the nine sub-brands ----------------------------------
   PRE-EXISTING DEFECT (verified identical on live, so not introduced here).
   The sub-brand location hero is a <section> with an inline
       background:linear-gradient(...brand dark...); color:#fff
   but a site rule paints its <h1> #0E1B4D, so the city headline renders dark
   navy on a dark brand gradient. Measured worst-case ratios against the real
   rendered background: professionalthanks 1.36, labelsbyhand 2.09,
   voterthanks 2.51, hospitalitythankyou 2.57 -- all far below the 3.0 minimum
   for large text, on roughly 1,600 location pages.

   The section's own inline color:#fff is the honest signal that it is a dark
   ground, so headings and copy inside it inherit that instead. */
main section[style*="color:#fff"] h1,
main section[style*="color:#fff"] h2,
main section[style*="color:#fff"] h3,
main section[style*="color:#fff"] p,
main section[style*="color: #fff"] h1,
main section[style*="color: #fff"] p{
  color: #ffffff !important;
}

/* --- above-the-fold reveal --------------------------------------------------
   The portfolio gates scroll-reveal content behind a JS-applied `.in` class,
   including the FIRST section -- which is, by definition, already on screen.
   That makes the hero headline and the upload card depend on JavaScript to be
   visible at all: if the script is slow, blocked by an extension, or throws,
   the page's primary action renders as an empty box. Scroll-reveal is only
   meaningful for content that starts off-screen, so the first section opts out
   and paints immediately. Everything below the fold keeps its animation. */
main > section:first-of-type .reveal,
main > section:first-of-type [class*="reveal"],
.hero .reveal, [class*="uphero"] .reveal{
  opacity: 1;
  transform: none;
}

/* --- consent banner ---------------------------------------------------------
   MEASURED, not assumed (fresh browser context, library actually initialised):

     desktop 1440   152px = 17% of the viewport
                    Accept + Reject inline, Manage on its own row -- the
                    library's own `.cm--inline` rule already does this well.
                    Left alone.

     mobile 390     345px = 41%  BEFORE this block
                    293px = 35%  after  (-52px)
     mobile 320     311px = 40%

   ARRANGEMENT: full-width STACKED buttons at mobile, deliberately.
   An earlier attempt forced Accept/Reject side by side. It is not worth it:
   at 320px each button drops to ~140px, "Manage preferences" wraps to two
   lines, and the saving is ~50px. Stacked full-width controls are unambiguous,
   comfortably tappable, and keep every label on one line. Height is reduced by
   tightening padding and type instead of by cramping the controls.

   Nothing is removed: all three choices remain, wording is untouched, and
   consent behaviour is unchanged. */
@media (max-width: 767px){
  #cc-main .cm.cm--bar{ border-radius: 12px 12px 0 0; }
  #cc-main .cm--bar .cm__body{ padding: 13px 16px 8px; }
  #cc-main .cm--bar .cm__title{ font-size: .96rem; margin: 0 0 .22em; }
  #cc-main .cm--bar .cm__desc{ font-size: .78rem; line-height: 1.4; padding: 0; }
  #cc-main .cm--bar .cm__btns{ padding: 8px 16px 10px; border-top: 0; row-gap: 6px; }
  #cc-main .cm--bar .cm__btn{
    min-height: 44px;              /* comfortable target, WCAG 2.5.5 territory */
    padding: .5rem .75rem;
    font-size: .87rem;
    border-radius: 6px;
    width: 100%;
  }
  #cc-main .cm--bar .cm__btn + .cm__btn{ margin-top: 6px; }
  #cc-main .cm--bar .cm__footer{ padding: 5px 16px 9px; font-size: .73rem; }
}

/* --- transactional surfaces -------------------------------------------------
   The checkout sits on a fixed dark photographic background while the site nav
   stays translucent white, so the bar reads as washed grey and the wordmark
   loses its ground. (Verified identical on live -- pre-existing, not introduced
   here -- but it is exactly the "poor presentation" this pass is meant to fix.)
   Solid ground only on the transactional pages; the marketing pages keep the
   translucent treatment they were designed with. */
body:has(.checkout-page) header,
body:has(.checkout-page) .hip-nav,
body:has(.checkout-page) .site-header{
  background: #ffffff !important;
  border-bottom: 1px solid rgba(15,23,42,.10);
  backdrop-filter: none;
}

/* TRANSACTION-SURFACE OVERLAYS.
   Three of them are hardcoded and none of them is brand-aware:
       .track-page__overlay    background-color:#0e1b4d          (HIP navy)
       .success-page__overlay  background-color:#0e1b4d          (HIP navy)
       .checkout-page__overlay linear-gradient(#000000d9,#667eeabf)
   The last is a periwinkle that belongs to no brand in the portfolio. So a
   customer paying on any of the nine sister brands left that brand's colour
   behind at exactly the moment the site should feel most trustworthy.
   Each now takes the brand's own dark. --color-primary-dark is defined per
   brand in that brand's stylesheet. */
.track-page__overlay,
.success-page__overlay{ background-color: var(--color-primary-dark, #0e1b4d) !important; }
.checkout-page__overlay{
  background: linear-gradient(135deg,
              rgba(0,0,0,.86),
              color-mix(in srgb, var(--color-primary-dark, #0e1b4d) 75%, transparent)) !important;
}

/* Order summary: give the file cards room to breathe and keep specs aligned. */
.order-summary{ margin-block: 6px 2px; }
.order-summary__label{ letter-spacing: .12em; }
#order-files{ display: grid; gap: 12px; }
.file-card__specs .spec{ display: flex; flex-direction: column; gap: 2px; }
.file-card__specs .spec__label{ font-size: .68rem; letter-spacing: .07em;
  text-transform: uppercase; opacity: .7; }
.notes-section{ margin-top: 14px; }
.notes-label{ display: block; margin-bottom: 6px; font-weight: 600; }

/* --- editorial pages: blog index, empty states ------------------------------
   The blog index centres its H1 but leaves .blog-hero__subtitle left-aligned,
   and the "no articles yet" state is a rounded card floating in a large empty
   band -- the generic pattern this redesign is removing. An empty state should
   read as a calm, deliberate note, not as a missing component. */
/* Doubled classes: the shipped rules are Astro-scoped
   (.coming-soon[data-astro-cid-...] = 0,2,0) and outrank a plain class. */
.blog-hero .page-width{ text-align: center; }
.blog-hero__subtitle.blog-hero__subtitle{
  margin: 10px auto 0; max-width: 46ch; text-align: center;
}
.coming-soon.coming-soon{
  background: transparent;
  border: 0;
  border-top: 1px solid rgba(0,0,0,.12);
  border-radius: 0;
  box-shadow: none;
  max-width: 46ch;
  margin: var(--rhythm-base) auto 0;
  padding: var(--rhythm-tight) 0 0;
  text-align: center;
}
.coming-soon__icon.coming-soon__icon{ display: none; }   /* decorative; read as a broken tile */
.coming-soon h2, .coming-soon h3{ margin-bottom: .4em; }
.coming-soon p{ margin-inline: auto; }

/* Article and policy prose: a real measure, and headings that group with the
   text they introduce rather than floating between paragraphs. */
.blog-article, .policy-content, .legal-content, article{ max-width: var(--measure); }
.blog-article h2, .policy-content h2, article h2{ margin-top: 1.6em; margin-bottom: .45em; }
.blog-article h3, .policy-content h3, article h3{ margin-top: 1.3em; margin-bottom: .35em; }
.blog-article p + p, .policy-content p + p, article p + p{ margin-top: .9em; }
.blog-article ul, .policy-content ul, article ul{ padding-left: 1.15em; }
.blog-article li + li, .policy-content li + li{ margin-top: .35em; }

/* --- print -----------------------------------------------------------------
   Customers print confirmations and tracking pages. Make that not embarrassing. */
@media print{
  .announcement-bar, .announcement-bar-dynamic, nav, footer,
  [class*="cookie"], [id*="cookie"], [class*="popup"], [class*="modal"]{ display:none !important; }
  body{ background:#fff !important; color:#000 !important; }
  a[href^="http"]::after{ content:" (" attr(href) ")"; font-size:.85em; }
}

/* ============================================================================
   ERROR PAGES — shared component (.hip-err*)
   One authoritative source for the 404/500 family on all ten brands. The class
   namespace is new and used nowhere else, so these rules cannot reach any other
   template. Every colour comes from a brand token with a neutral fallback, so a
   brand that omits a token still renders a legible page.

   The pages these style REPLACED a forced 5-second redirect to the homepage.
   That timer moved the reader off the page before they could read it, copy the
   URL or choose where to go, and it is a WCAG 2.2.1 (Timing Adjustable) failure.
   The replacement keeps the reader in control and offers real destinations.
   ========================================================================= */
.hip-err{ padding: clamp(48px,8vw,96px) var(--inline,24px) clamp(56px,9vw,104px); }
.hip-err__wrap{ max-width: 60rem; margin: 0 auto; }
.hip-err__code{
  font-size: clamp(64px,13vw,132px); font-weight: 900; line-height: .9;
  letter-spacing: -.03em; color: var(--bx-accent, var(--color-primary,#334155));
  opacity: .22; margin: 0 0 .1em; font-variant-numeric: tabular-nums;
}
.hip-err h1{ margin: 0 0 .5rem; font-size: clamp(26px,4.2vw,40px); line-height: 1.12; text-wrap: balance; }
.hip-err__msg{ margin: 0 0 2rem; max-width: var(--measure,42rem); font-size: 1.05rem; line-height: 1.65; color: var(--body-ink,#4b5563); }
.hip-err__lead{ font-size: .78rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--bx-ash,#6b7280); margin: 0 0 .9rem; }
.hip-err__grid{ display: grid; grid-template-columns: repeat(auto-fit,minmax(230px,1fr)); gap: 14px; margin: 0 0 2rem; }
.hip-err__card{
  display: block; padding: 18px 20px; border: 1px solid var(--bx-rule,var(--rule,#e5e7eb));
  border-radius: 14px; background: var(--bx-paper,#fff); text-decoration: none; color: inherit;
  transition: border-color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.hip-err__card:hover{ border-color: var(--bx-accent,var(--color-primary,#334155));
  transform: translateY(-2px); box-shadow: 0 8px 22px rgb(0 0 0 / .07); }
.hip-err__card:focus-visible{ outline: var(--focus-ring,2px solid currentColor); outline-offset: 3px; }
.hip-err__card b{ display: block; font-size: 1rem; margin-bottom: 3px; color: var(--heading-ink,inherit); }
.hip-err__card span{ display: block; font-size: .88rem; line-height: 1.5; color: var(--body-ink,#6b7280); }
.hip-err__foot{ margin: 0; padding-top: 1.25rem; border-top: 1px solid var(--bx-rule,var(--rule,#e5e7eb));
  font-size: .93rem; color: var(--body-ink,#6b7280); }
.hip-err__foot a{ color: var(--bx-accent,var(--color-primary,#334155)); font-weight: 600; }
@media (max-width: 640px){
  .hip-err__grid{ grid-template-columns: 1fr; }
  .hip-err__code{ opacity: .18; }
}
@media (prefers-reduced-motion: reduce){
  .hip-err__card{ transition: none; }
  .hip-err__card:hover{ transform: none; }
}

/* ---------------------------------------------------------------------------
   FOOTER LIST LINKS — target size (WCAG 2.5.8, AA)
   Footer nav links rendered at 19px tall on every brand. They are discrete list
   targets, not links inside a sentence, so the inline exception does not apply.
   Vertical padding lifts the hit area past 24px without moving anything
   horizontally; the visual position of the text is unchanged.
   Deliberately scoped to `footer li a` so it cannot reach body copy, the legal
   nav (already 24px block links) or the brand wordmark.
   ------------------------------------------------------------------------ */
footer li a{ display: inline-block; padding-block: 4px; }

/* Announcement-bar phone link: a tel: target rendered 12px tall on every brand.
   Padding lifts the hit area to 24px; the bar's own line-height keeps its height,
   so the bar does not grow. Scoped to the one class. */
.hip-usa-bar__phone{ display: inline-block; padding-block: 6px; }

/* ---------------------------------------------------------------------------
   MOBILE MENU OPEN STATE — the panel could not open on eight brands.
   Reproduced locally AND against live markup: clicking the toggle adds `.active`
   to #mobileMenu, but the panel stays at `right:-100%` and never slides in, so
   mobile navigation is unreachable. a11y-nav.js was correctly reporting
   aria-expanded="false" because the menu genuinely was not open.

   The brand bundle ships `.mobile-menu[data-astro-cid-mfb2cvlj].active{right:0}`,
   an Astro-SCOPED rule. Two things defeat it:
     1. the overlay is created in JS (`document.createElement('div')`) so it can
        never carry a cid attribute, and its scoped rule therefore never matches;
     2. the scoped selector is per-build and cannot be relied on from here.
   These unscoped rules restore the open state for every brand. `!important` is
   used deliberately and narrowly: it must beat the base `right:-100%` regardless
   of which cid a given brand's bundle was compiled with.
   ------------------------------------------------------------------------ */
.mobile-menu.active{ right: 0 !important; }
.mobile-menu-overlay{ position: fixed; inset: 0; background: rgb(0 0 0 / .5);
  z-index: 199; opacity: 0; pointer-events: none; transition: opacity .25s ease; }
.mobile-menu-overlay.active{ opacity: 1 !important; pointer-events: auto !important; }
@media (prefers-reduced-motion: reduce){
  .mobile-menu, .mobile-menu-overlay{ transition: none !important; }
}
