/* =====================================================================
   HIGH TECH HEALTH — HEADER TRANSITION  (hth-header.css)
   v2.3 — ease Jevelin's own transition. Do not replace it.
   ---------------------------------------------------------------------
   Verified on /21697-2/ by injecting these rules into the live page and
   sampling every 100ms. See the table further down for the result.

   ---------------------------------------------------------------------
   THE ONE RULE THIS FILE IS BUILT ON

   Jevelin re-adds its sticky class on EVERY scroll event, not only when
   the state changes. So any attempt to hold a different opinion about
   that class — a later threshold, an earlier one, a dead band — is a
   fight that happens twice per frame, and every flip restarts every
   transition below. That is what "pulsing" looks like.

   There are exactly two safe positions: agree with Jevelin exactly, or
   unbind its handler and become the sole writer. This file agrees
   exactly. hth-header.js therefore never writes a state class; it only
   measures. Do not add a threshold to it.

   ---------------------------------------------------------------------
   THE TWO BARS

   Jevelin ships two independent headers with different class names,
   different logo pairs, and different structure. Both are handled here.

                    desktop                        mobile
     element        .sh-header.sh-sticky-header    .sh-header-mobile
     active class   .sh-sticky-header-active       .sh-sticky-mobile-header-active
     rest logo      .sh-light-logo                 .sh-light-logo
     sticky logo    .sh-sticky-logo                .sh-standard-logo
     background     transparent -> #fff            transparent -> #fff
     height change  100px -> 70px                  none (no .container/.sh-table)

   Note the logo pairs differ. That is why hth-header.js reads them from
   the live DOM rather than hardcoding class names, and why every logo
   rule below keys off data attributes rather than Jevelin's classes.

   ---------------------------------------------------------------------
   WHAT EARLIER VERSIONS GOT WRONG (kept so nobody re-derives it)

   v1 deferred Jevelin's whole state change until the header had scrolled
   off. It neutralised only the BACKGROUND half of the active state and
   left the logo swap and nav colours firing at Jevelin's threshold, so
   between 50px and 180px you got the dark sticky logo and grey nav text
   on a transparent bar. It also rested on a mis-measurement: the claimed
   "45px pop" came from comparing the active state at scrollY 900 against
   rest at scrollY 0. At the threshold the resting bar is already at
   top:27 (77 - 50) and fixed puts it at top:32 — a 5px shift. The
   position flip was never a problem.

   v2.0 declared `transition: height` on values that were `auto`, so it
   silently did nothing (obstacle A below), and enforced its own
   thresholds, which caused the pulse.

   v2.1 set .hth-hdr-ready before tagging ran, so our own display:none
   rule hid the logos from our own probe and tagging never happened —
   desktop logo vanished when the header went white (obstacle C).

   v2.2 left the logo rules unscoped, so they also hit the mobile header,
   which the JS was not calibrating — mobile logo vanished on scroll
   (obstacle C again, different victim). Hence the [data-hth-logos] gate
   in §3, which makes that class of bug structurally impossible rather
   than merely fixed.

   ---------------------------------------------------------------------
   THE ACTUAL FAULTS, measured by toggling the class directly:

     property                       resting        active
     ----------------------------   ------------   ------------
     .container/.sh-table height    100px          70px          <- jitter
     background                     transparent    #fff          <- snap
     rest logo    display           block          none          }  snap
     sticky logo  display           none           block         }
     nav link colour                #fff (.85)     #3d3d3d (.69) <- snap
     transition-duration            0s             0s            <- the cause

   All four are transitionable. Jevelin simply never gave any of them a
   duration.

   ---------------------------------------------------------------------
   FOUR NON-OBVIOUS OBSTACLES, all found by measurement, all fail SILENTLY

   A. THE HEIGHT IS NOT A SPECIFIED VALUE. Nothing in any stylesheet sets
      a height on .container / .sh-table / .sh-table-cell — the 100px and
      70px are `auto`, resolved by layout. getComputedStyle reports them
      in px, which makes them look transitionable, but `auto` -> `auto`
      cannot animate. Confirmed with getAnimations() (empty) and
      transitionrun (never fired for `height`).
      Fix: §2, fed by measured custom properties.

   B. SOMETHING SHIPS `a, img, h2 { transition: none !important }`.
      That kills the logo crossfade regardless of specificity. This is the
      one place in the design system where !important is correct — it is
      not fighting a normal declaration, it is restoring one that an
      existing !important removed. See §3.

   C. `img:not([data-hth-logo]) { display: none }` IS A LOADED GUN. It is
      what lets two logos stack without the third showing. Point it at a
      container the JS has not tagged and every logo in that container
      disappears — which is exactly what happened in v2.1 and again in
      v2.2. Every logo rule in §3 is therefore gated on
      .header-logo-container[data-hth-logos], an attribute written only
      alongside a complete, valid tag pair. An uncalibrated container
      cannot be touched regardless of execution order.

   D. CROSSFADES MUST BE LINEAR. Opacity composites as a weighted average,
      so a pair of layers only holds constant apparent density if the two
      curves sum to 1 at every instant. Linear does; ease-in-out does not
      — both layers sit near 0.5 through the middle, and the image visibly
      sags and recovers. That was the other half of the "pulse". Hence
      --hth-hdr-fade. The ease is kept only for height, where it describes
      motion rather than blending two images.

   ---------------------------------------------------------------------
   MEASURED RESULT, desktop (100ms samples, forward direction):

     t       height    white    rest logo   sticky logo
     0ms     100px     0.00     1.00        0.00
     100ms   92.5px    0.25     0.75        0.25
     200ms   76.3px    0.79     0.21        0.79
     300ms   70.7px    0.98     0.02        0.98
     550ms   70px      1.00     0.00        1.00

   Companion file: hth-header.js — measurement and tagging only.
   ===================================================================== */

:root {
	/* One duration for every change so they arrive together. 420ms reads
	   as deliberate without making a fast scroller wait. Under ~250ms it
	   still reads as a snap; over ~600ms the header feels like it is
	   lagging behind the page. */
	--hth-hdr-dur:  420ms;

	/* MOVEMENT — the height change. Standard material ease-in-out.
	   Deliberately not a spring or an overshoot; a header you notice is a
	   header that failed. */
	--hth-hdr-ease: cubic-bezier(.4, 0, .2, 1);

	/* FADES — linear, and this is not laziness. See obstacle D above.
	   Leave this alone unless you have re-read it. */
	--hth-hdr-fade: linear;
}


/* ---------------------------------------------------------------------
   1. BACKGROUND — fade an opacity layer, don't animate background-color
   ---------------------------------------------------------------------
   background-color is animatable, but it repaints the whole bar every
   frame and composites badly against the position:fixed switch happening
   in the same instant. An absolutely-positioned layer going 0 -> 1 is a
   compositor-only operation: no repaint, no layout, GPU through a fling.

   Painting order: the ::before is earlier in DOM order than the bar's
   content, and that content is positioned. Positioned descendants paint
   after non-positioned content in DOM order, so the layer lands
   underneath the logo and nav with no z-index and no stacking-context
   surgery.
   ------------------------------------------------------------------ */
body.hth-ds :is(.sh-header.sh-sticky-header, .sh-header-mobile) {
	position:   relative;
	box-shadow: 0 0 0 rgba(20, 20, 20, 0);
	transition: box-shadow var(--hth-hdr-dur) var(--hth-hdr-fade);
}

body.hth-ds .sh-header.sh-sticky-header > .container {
	position: relative;      /* keeps content above the fade layer */
}

body.hth-ds :is(.sh-header.sh-sticky-header, .sh-header-mobile)::before {
	content:        '';
	position:       absolute;
	inset:          0;
	background:     #fff;
	opacity:        0;
	pointer-events: none;
	transition:     opacity var(--hth-hdr-dur) var(--hth-hdr-fade);
}

body.hth-ds :is(
	.sh-header.sh-sticky-header.sh-sticky-header-active,
	.sh-header-mobile.sh-sticky-mobile-header-active
)::before {
	opacity: 1;
}

/* Jevelin paints its own instant white on the active class. Cancel it or
   it lands at 0s underneath our fade and there is nothing left to see. */
body.hth-ds :is(
	.sh-header.sh-sticky-header.sh-sticky-header-active,
	.sh-header-mobile.sh-sticky-mobile-header-active
) {
	background-color: transparent;
	box-shadow:       0 1px 0 rgba(20, 20, 20, .07),
	                  0 8px 28px -18px rgba(20, 20, 20, .45);
}


/* ---------------------------------------------------------------------
   2. HEIGHT — 100px -> 70px, eased. Desktop only.
   ---------------------------------------------------------------------
   This is the jitter: 30px of layout collapsing in one frame while the
   bar is simultaneously becoming position:fixed.

   Read obstacle A before touching this. The custom properties are written
   to the bar by hth-header.js, which measures both states at startup.
   Declaring real px values is what makes the transition run at all —
   without them these rules are inert, which is exactly how v2.0 shipped.

   The `auto` fallbacks are last-resort: if the JS has not run, height
   stays auto and Jevelin's original snap is what you get. Degraded, not
   broken.

   The mobile header has no .container/.sh-table structure and does not
   change height, so there is nothing here for it.

   Yes, this animates `height`, a layout property. Acceptable here for
   three specific reasons and only these:
     * the bar is position:fixed at this moment, so it is out of flow and
       the reflow cannot touch the page below it;
     * .sh-header-height holds the document's spacing regardless, so there
       is no cumulative layout shift;
     * the subtree is four levels deep and a few dozen nodes wide.
   Do not widen this to `all`. It will catch width and margin on the nav
   and hand the jitter straight back.
   ------------------------------------------------------------------ */
body.hth-ds.hth-hdr-ready .sh-header.sh-sticky-header > .container,
body.hth-ds.hth-hdr-ready .sh-header.sh-sticky-header .sh-table,
body.hth-ds.hth-hdr-ready .sh-header.sh-sticky-header .sh-table > .sh-table-cell {
	height:     var(--hth-hdr-h, auto);
	transition: height var(--hth-hdr-dur) var(--hth-hdr-ease);
}

body.hth-ds.hth-hdr-ready .sh-header.sh-sticky-header.sh-sticky-header-active > .container,
body.hth-ds.hth-hdr-ready .sh-header.sh-sticky-header.sh-sticky-header-active .sh-table,
body.hth-ds.hth-hdr-ready .sh-header.sh-sticky-header.sh-sticky-header-active .sh-table > .sh-table-cell {
	height: var(--hth-hdr-h-stuck, auto);
}

/* ---------------------------------------------------------------------
   2b. NO SHRINK — currently ENABLED
   ---------------------------------------------------------------------
   The bar holds one height for its whole life. Requested because the
   100 -> 70 collapse still reads as the header "calculating" even when
   eased: the eye tracks the logo and nav moving vertically while the
   page is also moving, and two simultaneous motions in the same axis
   never resolve into one intention.

   CORRECTION TO THE SNIPPET THAT USED TO BE HERE. It said:

	body.hth-ds .sh-header.sh-sticky-header { --hth-hdr-h-stuck: var(--hth-hdr-h); }

   That cannot work. hth-header.js publishes both heights with
   bar.style.setProperty(), so they are INLINE declarations:

	<div class="sh-header ..." style="--hth-hdr-h: 100px; --hth-hdr-h-stuck: 70px">

   An inline declaration outranks any stylesheet rule that does not carry
   !important, so the override would have been silently ignored — the
   custom property is the one thing here you cannot reach from CSS.

   So this overrides the HEIGHT rule instead, which the JS never writes.
   Same selector and specificity as the active-state rule above (0,6,1),
   placed after it, so it wins on source order alone. No !important
   needed, and nothing about the rest of the transition changes: the
   background fade, logo crossfade and nav colours all still run.

   TO RESTORE THE SHRINK: delete this block. Nothing else refers to it.

   THERE IS NO THEME OPTION FOR THIS. An earlier version of this comment
   claimed Theme Options -> Header -> Sticky -> Height was the cleaner
   fix. That control does not exist. Jevelin's Header Settings offers
   Sticky Header on/off and nothing else — no height, no threshold.

   Nor is 70px a value declared anywhere you can edit. It is the bar's
   natural content height once the active state applies its smaller logo
   and tighter padding, which is why hth-header.js has to MEASURE both
   states at startup rather than read a setting. `hthHeader.state()`
   reports what it found:

	hRest: "100px"   hStuck: "70px"

   So overriding the height rule is not a workaround for a missing
   option — it is the only lever there is. Treat this block as the
   permanent fix, not as something to revisit at rollout.
   ------------------------------------------------------------------ */
body.hth-ds.hth-hdr-ready .sh-header.sh-sticky-header.sh-sticky-header-active > .container,
body.hth-ds.hth-hdr-ready .sh-header.sh-sticky-header.sh-sticky-header-active .sh-table,
body.hth-ds.hth-hdr-ready .sh-header.sh-sticky-header.sh-sticky-header-active .sh-table > .sh-table-cell {
	height: var(--hth-hdr-h, auto);
}


/* ---------------------------------------------------------------------
   3. LOGO — crossfade instead of display:none
   ---------------------------------------------------------------------
   Jevelin ships three logos in the DOM and swaps them with display:none /
   display:block. `display` cannot be transitioned, and the swap reflows
   the logo cell while the incoming file decodes — the second half of the
   height jitter.

   All three files measure 443 x 29 (verified), so stacking costs nothing:
   the resting logo stays in flow and defines the box, the incoming logo
   is absolutely positioned on top, and opacity crossfades between them.

   EVERY RULE HERE IS GATED ON [data-hth-logos]. Read obstacle C. That
   attribute is written by hth-header.js only on a container whose logos
   were successfully tagged, at the same moment as the tags themselves. A
   container the JS did not calibrate — the mobile header before v2.3, or
   any header added later — is invisible to all of this and keeps
   Jevelin's stock behaviour. Do not relax that gate to make a selector
   shorter.

   THE !important IS DELIBERATE. See obstacle B. It restores a declaration
   another !important removed, rather than winning a normal cascade fight.
   If that upstream rule ever goes away, delete this one too.
   ------------------------------------------------------------------ */
body.hth-ds .header-logo-container[data-hth-logos] .sh-table-cell {
	position: relative;
}

body.hth-ds .header-logo-container[data-hth-logos] img[data-hth-logo] {
	display:    block;
	transition: opacity var(--hth-hdr-dur) var(--hth-hdr-fade) !important;
}

/* The third logo, the one this bar does not use. Only ever applied inside
   a container that already holds a valid tagged pair. */
body.hth-ds .header-logo-container[data-hth-logos] img:not([data-hth-logo]) {
	display: none;
}

body.hth-ds .header-logo-container[data-hth-logos] img[data-hth-logo="rest"] {
	position: relative;
	opacity:  1;
}

body.hth-ds .header-logo-container[data-hth-logos] img[data-hth-logo="sticky"] {
	position: absolute;
	top:      0;
	left:     0;
	opacity:  0;
}

body.hth-ds :is(.sh-sticky-header-active, .sh-sticky-mobile-header-active)
	.header-logo-container[data-hth-logos] img[data-hth-logo="rest"] {
	opacity: 0;
}

body.hth-ds :is(.sh-sticky-header-active, .sh-sticky-mobile-header-active)
	.header-logo-container[data-hth-logos] img[data-hth-logo="sticky"] {
	opacity: 1;
}


/* ---------------------------------------------------------------------
   4. NAV TEXT AND ICONS
   ---------------------------------------------------------------------
   rgba(255,255,255,.85) -> rgba(61,61,61,.69). Both endpoints are
   Jevelin's; all we add is the duration.

   Scoped to colour-ish properties on purpose. `transition: all` here
   picks up the nav's own width and padding as the header height animates,
   and you get a second, competing animation on every link.
   ------------------------------------------------------------------ */
body.hth-ds :is(.sh-header.sh-sticky-header, .sh-header-mobile) :is(a, i, svg),
body.hth-ds :is(.sh-header.sh-sticky-header, .sh-header-mobile) .sh-nav > li > a,
body.hth-ds :is(.sh-header.sh-sticky-header, .sh-header-mobile) .sh-nav-cart-count {
	transition:
		color            var(--hth-hdr-dur) var(--hth-hdr-fade),
		fill             var(--hth-hdr-dur) var(--hth-hdr-fade),
		border-color     var(--hth-hdr-dur) var(--hth-hdr-fade),
		background-color var(--hth-hdr-dur) var(--hth-hdr-fade);
}

/* Dropdown and mobile flyout panels have their own background and must
   not inherit the crossfade — they open and close on their own timing,
   and a 420ms fade on a menu makes it feel unresponsive. */
body.hth-ds :is(.sh-header.sh-sticky-header, .sh-header-mobile) .sh-nav ul,
body.hth-ds :is(.sh-header.sh-sticky-header, .sh-header-mobile) .sh-nav ul a,
body.hth-ds .sh-header-mobile-dropdown,
body.hth-ds .sh-header-mobile-dropdown a {
	transition: none;
}


/* ---------------------------------------------------------------------
   5. REDUCED MOTION
   ---------------------------------------------------------------------
   Back to Jevelin's instant switch, which is what someone who asked the
   OS for no animation is asking for. The logo still crossfades between
   two stacked images rather than reflowing, because that is a layout
   improvement, not motion.
   ------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
	body.hth-ds :is(.sh-header.sh-sticky-header, .sh-header-mobile),
	body.hth-ds :is(.sh-header.sh-sticky-header, .sh-header-mobile)::before,
	body.hth-ds :is(.sh-header.sh-sticky-header, .sh-header-mobile) *,
	body.hth-ds .header-logo-container[data-hth-logos] img[data-hth-logo] {
		transition: none !important;
	}
}


/* ---------------------------------------------------------------------
   6. OPTIONAL — FROSTED BAR INSTEAD OF FLAT WHITE
   ---------------------------------------------------------------------
   Uncomment to let the hero bleed faintly through the solid state. The
   cheapest single change here that makes a header read as expensive.
   Costs a compositor layer — check on a low-end Android before shipping,
   and keep the @supports guard so browsers without backdrop-filter get
   opaque white rather than a see-through nav.

	@supports (backdrop-filter: blur(1px)) {
		body.hth-ds :is(.sh-header.sh-sticky-header, .sh-header-mobile)::before {
			background:              rgba(255, 255, 255, .82);
			backdrop-filter:         saturate(180%) blur(18px);
			-webkit-backdrop-filter: saturate(180%) blur(18px);
		}
	}
   ------------------------------------------------------------------ */


/* =====================================================================
   7. THE HERO HAS TO START WHERE THE HEADER STARTS
   ---------------------------------------------------------------------
   Jevelin pads the top of the content container:

       #wrapper > .sh-page-layout-default { padding-top: 60px }

   which is right for a page that opens with a heading and wrong for one
   that opens with a full-bleed photograph. Measured on page 21923 at
   1628 x 1070:

       header bar          77 -> 178
       hero without this   137            <- 60px of bar over page background
       hero with this       77            <- bar sits wholly over the image

   White logo, white nav, white background. The header does not look
   broken; it looks absent, which is worse, because the first guess is
   always that something in the hero is at fault. It is not — the hero is
   simply 60px too low.

   WHY THIS IS NOT LEFT TO THE PAGE OPTION

   Jevelin has a per-page setting that zeroes the same padding, and the
   /infrared-saunas/ draft has it switched on. That is exactly why the
   heaters page looked like a hero-height bug: two pages, same blocks,
   one invisible checkbox apart. An option that has to be remembered on
   every page anyone ever builds will eventually not be, and the failure
   is silent. The theme knows whether the page opens with a hero, so the
   theme decides — see hth_hero_is_first() in functions.php.

   Specificity, deliberately: Jevelin's rule is (1,1,0) and this is
   (1,5,1), so it wins on its own merits. No !important — the page option
   sets the same 0 when it is on, and anything that genuinely needs the
   padding back has the hth_hero_first filter.

   NOT A NEGATIVE MARGIN. The /infrared-saunas/ draft also carries
   margin-top:-60px on its hero row, left over from before this rule
   existed. That is 60px too far: it slides the top of the photograph
   under the promo bar, where it cannot be seen. Zeroing the padding
   lands the hero exactly on the header's top edge, which is all that was
   ever needed. The row margin can be removed.
   ===================================================================== */
body.hth-ds.hth-hero-first #wrapper > .content-container.sh-page-layout-default {
	padding-top: 0;
}
