PageSourceSearch

https://smartinnovationnorway.com/wp-content/themes/wp-theme-sin-n…?ver=2.117.0&sb-version=1790065487

js smartinnovationnorway.com collected 2026-09-26 00:45:47 UTC 7,506 bytes, 243 lines download raw bytes

1/**
2 * Overlay header: find the element that should actually be sticky, publish the
3 * header height, keep the hero clear of the menu, and pick a readable colour
4 * scheme for whatever is underneath.
5 *
6 * Why any of this needs JS — measured on the live front page rather than
7 * assumed. WordPress renders a template part wrapped in its area tag, so the
8 * markup is:
9 *
10 *     <div class="wp-site-blocks">
11 *       <header class="wp-block-template-part">
12 *         <div class="wp-block-group sin-header sin-header--overlay">   ← ours
13 *       </header>
14 *       <p><!-- Hero Section --></p>
15 *       <hr class="wp-block-separator">
16 *       <div class="wp-block-group sin-hero-manifest">                  ← the hero
17 *
18 * Three things follow from that, and all three were live bugs:
19 *
20 *   1. position:sticky is bounded by the element's PARENT box. Putting it on
21 *      the group meant the parent was the <header> wrapper, exactly as tall as
22 *      the header itself — zero travel, so it unstuck the moment you scrolled.
23 *      The sticky behaviour has to sit on the .wp-site-blocks child.
24 *
25 *   2. The header group has no next sibling at all, so every rule and lookup
26 *      written as "the thing after the header" silently did nothing.
27 *
28 *   3. Even one level up, the next sibling is a stray <p> holding an HTML
29 *      comment, then an <hr>. The hero is the third sibling. Anything that
30 *      assumes "next element = hero" pads a comment.
31 *
32 * Below the nav's overlay breakpoint none of this applies: the header is an
33 * ordinary solid bar in normal flow, so there is nothing to compensate for and
34 * nothing to adapt to. See the matching media query in style.css.
35 */
36( function () {
37	'use strict';
38
39	var header = document.querySelector( '.sin-header--overlay' );
40
41	if ( ! header ) {
42		return;
43	}
44
45	// Matches core's overlayMenu:"mobile" breakpoint, which is where the
46	// hamburger takes over. style.css switches the overlay off at the same
47	// point — keep the two in step.
48	var MOBILE = '(max-width: 599.98px)';
49
50	// How far you have to scroll before the bar goes frosted. Small enough to
51	// feel immediate, large enough that a rubber-band or a jittery trackpad
52	// doesn't flicker it.
53	var SCROLLED_AT = 24;
54
55	/**
56	 * Walk up to the element that is a direct child of .wp-site-blocks. That is
57	 * the box sticky has to live on, whatever wrappers WordPress puts between.
58	 */
59	function findShell( el ) {
60		var node = el;
61
62		while ( node.parentElement && node.parentElement !== document.body ) {
63			if ( node.parentElement.classList.contains( 'wp-site-blocks' ) ) {
64				return node;
65			}
66			node = node.parentElement;
67		}
68
69		return el;
70	}
71
72	var shell = findShell( header );
73	shell.classList.add( 'sin-header-shell' );
74
75	/**
76	 * The hero is the first following sibling with real height. Empty
77	 * paragraphs, separators and comment wrappers are skipped — a quarter of
78	 * the viewport is well above anything incidental and well below any hero.
79	 */
80	function findHero() {
81		var node = shell.nextElementSibling;
82		var floor = window.innerHeight * 0.25;
83		var firstVisible = null;
84
85		while ( node ) {
86			if ( node.offsetHeight > 0 && ! firstVisible ) {
87				firstVisible = node;
88			}
89			if ( node.offsetHeight >= floor ) {
90				return node;
91			}
92			node = node.nextElementSibling;
93		}
94
95		return firstVisible;
96	}
97
98	var hero = findHero();
99
100	function isMobile() {
101		return window.matchMedia( MOBILE ).matches;
102	}
103
104	/**
105	 * Raise the hero's top padding only if it isn't already clear of the menu.
106	 *
107	 * This used to be a CSS rule with !important, which had to shout because
108	 * hero blocks carry inline padding. Reading the computed value instead
109	 * means the common case — a hero that already has generous top padding —
110	 * is left completely alone.
111	 */
112	function clearHero() {
113		if ( ! hero ) {
114			return;
115		}
116
117		// Always clear our own previous value first: on mobile the header is in
118		// normal flow and needs no compensation at all, and on desktop each
119		// pass would otherwise read back what the last one wrote.
120		hero.style.paddingTop = '';
121
122		if ( isMobile() ) {
123			return;
124		}
125
126		var needed = header.offsetHeight + 32;
127		var current = parseFloat( window.getComputedStyle( hero ).paddingTop ) || 0;
128
129		if ( current < needed ) {
130			hero.style.paddingTop = needed + 'px';
131		}
132	}
133
134	function parseRgb( value ) {
135		var parts = /rgba?\(([^)]+)\)/.exec( value || '' );
136
137		if ( ! parts ) {
138			return null;
139		}
140
141		var n = parts[ 1 ].split( /[\s,\/]+/ ).filter( Boolean ).map( parseFloat );
142
143		return {
144			r: n[ 0 ],
145			g: n[ 1 ],
146			b: n[ 2 ],
147			a: n.length > 3 ? n[ 3 ] : 1,
148		};
149	}
150
151	/**
152	 * Is the thing under the header light or dark?
153	 *
154	 * The overlay header's white logo and menu only work over a dark hero. The
155	 * front page's manifest hero is white, so the header rendered white on
156	 * white — invisible until you selected the text. Rather than keeping a list
157	 * of which heroes are dark, measure it.
158	 *
159	 * Walks up from the hero until it finds a background colour that is
160	 * actually opaque, because a block with no colour of its own is showing
161	 * whatever is behind it. A cover block keeps its overlay colour on a child
162	 * span, so that is checked first.
163	 */
164	function backdropIsLight() {
165		var node = hero || document.body;
166		var found = null;
167
168		var dim = hero && hero.querySelector( '.wp-block-cover__background' );
169		if ( dim ) {
170			var dimColor = parseRgb( window.getComputedStyle( dim ).backgroundColor );
171			if ( dimColor && dimColor.a > 0.4 ) {
172				found = dimColor;
173			}
174		}
175
176		while ( ! found && node ) {
177			var color = parseRgb( window.getComputedStyle( node ).backgroundColor );
178			if ( color && color.a > 0.5 ) {
179				found = color;
180			}
181			node = node.parentElement;
182		}
183
184		// Nothing opaque anywhere up the chain means the page background, which
185		// is white.
186		if ( ! found ) {
187			return true;
188		}
189
190		// Rec. 709 luma. The threshold sits above 0.5 on purpose: in the
191		// ambiguous middle, dark text on a mid tone stays readable where white
192		// text does not.
193		return ( 0.2126 * found.r + 0.7152 * found.g + 0.0722 * found.b ) / 255 > 0.55;
194	}
195
196	/**
197	 * Frosted bar as soon as the page leaves the top.
198	 *
199	 * This was an IntersectionObserver watching the hero's bottom edge, from
200	 * when the scrolled state was navy and could only be shown over light
201	 * content. It's a translucent white bar now, which is readable over
202	 * anything, so waiting for a whole section to pass just made the header
203	 * look broken for the first screenful or two.
204	 */
205	function updateScrolled() {
206		header.classList.toggle( 'is-scrolled', window.scrollY > SCROLLED_AT );
207	}
208
209	function refresh() {
210		document.documentElement.style.setProperty(
211			'--sin-header-h',
212			header.offsetHeight + 'px'
213		);
214
215		clearHero();
216
217		// On mobile the bar is solid white, so the light treatment is always
218		// the right one — no measuring needed.
219		header.classList.toggle( 'is-on-light', isMobile() || backdropIsLight() );
220
221		updateScrolled();
222	}
223
224	refresh();
225
226	window.addEventListener( 'scroll', updateScrolled, { passive: true } );
227
228	if ( 'ResizeObserver' in window ) {
229		new ResizeObserver( refresh ).observe( header );
230	} else {
231		window.addEventListener( 'resize', refresh );
232	}
233
234	/*
235	 * Re-resolve once images have loaded. Heights measured before then can pick
236	 * the wrong sibling as the hero — an image-led hero is 0px tall until its
237	 * image arrives.
238	 */
239	window.addEventListener( 'load', function () {
240		hero = findHero();
241		refresh();
242	} );
243}() );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.