From 1abed66e56934fde8cbc23b688d9c922d58b1872 Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Tue, 24 Mar 2026 14:55:51 -0400 Subject: [PATCH 01/13] start bg image earlier in scripts.js --- scripts/scripts.js | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/scripts/scripts.js b/scripts/scripts.js index 85df236..dd26a3f 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -280,14 +280,18 @@ function decorateButtonGroups(element) { } /** - * Preload hero-heritage-cc LCP background image as soon as the DOM is ready, - * before the block JS loads, to reduce resource load delay (e.g. in Chrome Performance). - * Runs synchronously after decorateMain so the image request starts before loadSection. + * Preload hero-heritage-cc LCP background image before block JS and before template work, + * matching URL/row logic in blocks/hero-heritage-cc/hero-heritage-cc.js (intro = 2nd DIV row). + * Also sets fetchpriority/loading on the intro img so the request competes earlier than loadBlock. */ function preloadHeroHeritageCcLcpImage(main) { - const block = main?.querySelector('.hero-heritage-cc'); + if (!main || document.querySelector('main[data-aue-resource]')) { + return; + } + const block = main.querySelector('.hero-heritage-cc'); if (!block) return; - const introRow = block.children[1]; + const rows = [...block.children].filter((c) => c.tagName === 'DIV'); + const introRow = rows[1]; if (!introRow) return; const introContent = introRow.querySelector(':scope > div'); const firstPicture = introContent?.querySelector('picture'); @@ -297,13 +301,22 @@ function preloadHeroHeritageCcLcpImage(main) { let url = webpSource?.srcset?.split(',')[0]?.trim()?.split(' ')[0] || img?.src; if (!url) return; if (url.includes('optimize=medium')) url = url.replace('optimize=medium', 'optimize=large'); - const link = document.createElement('link'); - link.rel = 'preload'; - link.as = 'image'; - link.href = url; - link.fetchPriority = 'high'; - if (webpSource) link.type = 'image/webp'; - document.head.insertBefore(link, document.head.firstChild); + + const existingPreload = document.querySelector(`head > link[rel="preload"][as="image"][href="${url}"]`); + if (!existingPreload) { + const link = document.createElement('link'); + link.rel = 'preload'; + link.as = 'image'; + link.href = url; + link.fetchPriority = 'high'; + if (webpSource) link.type = 'image/webp'; + document.head.insertBefore(link, document.head.firstChild); + } + + if (img) { + img.setAttribute('fetchpriority', 'high'); + img.setAttribute('loading', 'eager'); + } } function prepareHeroForCLS(main) { @@ -1168,11 +1181,16 @@ async function loadEager(doc) { } } + const main = doc.querySelector('main'); + /* Start LCP image fetch before template CSS/JS; decorateMain runs a second pass below. */ + if (main) { + preloadHeroHeritageCcLcpImage(main); + } + const templateName = getMetadata('template'); if (templateName) { await loadTemplate(doc, templateName); } - const main = doc.querySelector('main'); if (main) { decorateMain(main); preloadHeroHeritageCcLcpImage(main); From ba48db7fe99975917c05f7942a55ed739edf54db Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Wed, 25 Mar 2026 15:02:49 -0400 Subject: [PATCH 02/13] trying to improve lcp --- blocks/hero-heritage-cc/hero-heritage-cc.css | 1 - blocks/hero-heritage-cc/hero-heritage-cc.js | 1 + head.html | 4 ++-- scripts/scripts.js | 12 +++++++----- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/blocks/hero-heritage-cc/hero-heritage-cc.css b/blocks/hero-heritage-cc/hero-heritage-cc.css index c3617f3..2fc00f3 100644 --- a/blocks/hero-heritage-cc/hero-heritage-cc.css +++ b/blocks/hero-heritage-cc/hero-heritage-cc.css @@ -269,7 +269,6 @@ /* Banner section - positioned behind intro, centered content */ .hero-heritage-cc-banner { position: absolute; - top: 0; left: 0; width: 100%; height: calc(100% - var(--spacing-xxl)); diff --git a/blocks/hero-heritage-cc/hero-heritage-cc.js b/blocks/hero-heritage-cc/hero-heritage-cc.js index a64b198..a33eef8 100644 --- a/blocks/hero-heritage-cc/hero-heritage-cc.js +++ b/blocks/hero-heritage-cc/hero-heritage-cc.js @@ -124,6 +124,7 @@ function applyIntroBackground(block, introContent, pictures) { const webpSource = bgPicture.querySelector('source[type="image/webp"]'); let bgUrl = webpSource?.srcset?.split(',')[0]?.trim()?.split(' ')[0] || bgImg?.src; + if (bgUrl?.includes('optimize=medium')) { bgUrl = bgUrl.replace('optimize=medium', 'optimize=large'); } diff --git a/head.html b/head.html index c613248..fe2e3b4 100644 --- a/head.html +++ b/head.html @@ -5,9 +5,9 @@ move-to-http-header="true" > - - + + diff --git a/scripts/scripts.js b/scripts/scripts.js index dd26a3f..2f8e4ad 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -300,6 +300,7 @@ function preloadHeroHeritageCcLcpImage(main) { const img = firstPicture.querySelector('img'); let url = webpSource?.srcset?.split(',')[0]?.trim()?.split(' ')[0] || img?.src; if (!url) return; + /* Match hero-heritage-cc.js: preload href must match the upgraded LCP URL. */ if (url.includes('optimize=medium')) url = url.replace('optimize=medium', 'optimize=large'); const existingPreload = document.querySelector(`head > link[rel="preload"][as="image"][href="${url}"]`); @@ -308,7 +309,9 @@ function preloadHeroHeritageCcLcpImage(main) { link.rel = 'preload'; link.as = 'image'; link.href = url; + /* Property + attribute: Lighthouse reads the DOM attribute on the preload hint. */ link.fetchPriority = 'high'; + link.setAttribute('fetchpriority', 'high'); if (webpSource) link.type = 'image/webp'; document.head.insertBefore(link, document.head.firstChild); } @@ -1182,17 +1185,13 @@ async function loadEager(doc) { } const main = doc.querySelector('main'); - /* Start LCP image fetch before template CSS/JS; decorateMain runs a second pass below. */ - if (main) { - preloadHeroHeritageCcLcpImage(main); - } - const templateName = getMetadata('template'); if (templateName) { await loadTemplate(doc, templateName); } if (main) { decorateMain(main); + /* Second pass if hero was missing at module init. Idempotent via preload dedupe. */ preloadHeroHeritageCcLcpImage(main); const h1Title = getMetadata('h1-title'); if (h1Title) { @@ -1830,4 +1829,7 @@ async function loadPage() { loadDelayed(); } +/* Earliest point in this module: before loadEager (fonts, template, decorateMain). */ +preloadHeroHeritageCcLcpImage(document.querySelector('main')); + loadPage(); From 0f043dc7d811933380d17656c0d6916ac4b0233b Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Wed, 25 Mar 2026 15:04:04 -0400 Subject: [PATCH 03/13] put back top 0 --- blocks/hero-heritage-cc/hero-heritage-cc.css | 1 + 1 file changed, 1 insertion(+) diff --git a/blocks/hero-heritage-cc/hero-heritage-cc.css b/blocks/hero-heritage-cc/hero-heritage-cc.css index 2fc00f3..c3617f3 100644 --- a/blocks/hero-heritage-cc/hero-heritage-cc.css +++ b/blocks/hero-heritage-cc/hero-heritage-cc.css @@ -269,6 +269,7 @@ /* Banner section - positioned behind intro, centered content */ .hero-heritage-cc-banner { position: absolute; + top: 0; left: 0; width: 100%; height: calc(100% - var(--spacing-xxl)); From 7f6ef6b9ba6da776025aaed946bff54c81a17253 Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Thu, 26 Mar 2026 10:42:40 -0400 Subject: [PATCH 04/13] fix CLS for desktop --- blocks/hero-heritage-cc/hero-heritage-cc.css | 27 +++++++---- blocks/hotspot/hotspot.css | 47 +++++++------------- styles/styles.css | 9 ++++ 3 files changed, 44 insertions(+), 39 deletions(-) diff --git a/blocks/hero-heritage-cc/hero-heritage-cc.css b/blocks/hero-heritage-cc/hero-heritage-cc.css index c3617f3..8330eb2 100644 --- a/blocks/hero-heritage-cc/hero-heritage-cc.css +++ b/blocks/hero-heritage-cc/hero-heritage-cc.css @@ -1,10 +1,6 @@ /* Section container - fixed height to prevent CLS */ .hero-heritage-cc-container { position: relative; - min-height: calc(100vh - var(--spacing-xl)); /* fallback for older browsers */ - min-height: calc(100dvh - var(--spacing-xl)); - height: calc(100vh - var(--spacing-xl)); - height: calc(100dvh - var(--spacing-xl)); contain: strict; overflow: hidden; } @@ -492,6 +488,7 @@ color: var(--color-pewter); .hero-heritage-cc p.hero-heritage-cc-banner-image { margin: 10px 0 0; transition: opacity 0.35s ease-out, transform 0.35s ease-out; + min-height: 226px; } .hero-heritage-cc .hero-heritage-cc-banner { @@ -536,6 +533,18 @@ color: var(--color-pewter); display: flex; } + /* Logo images at native size with reserved space to prevent CLS */ +.hero-heritage-cc-intro .hero-heritage-cc-intro-logo-hindi picture, +.hero-heritage-cc-intro .hero-heritage-cc-intro-logo-english picture { + min-height: 252px; +} + +.hero-heritage-cc-intro .hero-heritage-cc-intro-logo-hindi img, +.hero-heritage-cc-intro .hero-heritage-cc-intro-logo-english img { + width: auto; + min-height: 252px; +} + .hero-heritage-cc .hero-heritage-cc-banner-cta-group { margin-top: var(--spacing-l); } @@ -544,6 +553,10 @@ color: var(--color-pewter); font-size: var(--body-font-size-s); } + .hero-heritage-cc p.hero-heritage-cc-banner-image { + min-height: 291px; + } + .hero-heritage-cc-banner-cta .button { font-size: var(--body-font-size-l); } @@ -558,10 +571,8 @@ color: var(--color-pewter); } .hero-heritage-cc-container { - min-height: calc(100vh - 120px); - min-height: calc(100dvh - 120px); - height: calc(100vh - 120px); - height: calc(100dvh - 120px); + height: 100%; + width: 100%; } /* Decoration image - top right */ diff --git a/blocks/hotspot/hotspot.css b/blocks/hotspot/hotspot.css index 40a54cb..4f1847f 100644 --- a/blocks/hotspot/hotspot.css +++ b/blocks/hotspot/hotspot.css @@ -8,7 +8,6 @@ /* Prevent CLS: hide until fully decorated, reserve space */ .hotspot:not([data-block-status="loaded"]) { visibility: hidden; - min-height: 400px; } /* Two-column layout */ @@ -17,7 +16,6 @@ align-items: stretch; justify-self: start; margin: 0 auto; - min-height: 400px; position: relative; transition: opacity 0.1s ease; } @@ -227,38 +225,9 @@ opacity: 1; } -/* Responsive adjustments */ -@media (width <= 1024px) { - .hotspot:not([data-block-status="loaded"]) { - min-height: 350px; - } - - .hotspot-container { - min-height: 350px; - } - - .hotspot-tooltip-panel { - flex: 0 0 35%; - padding: 30px; - } - - .hotspot-tooltip-content { - font-size: 16px; - } - - .hotspot-image-section { - flex: 0 0 65%; - } -} - @media (width <= 768px) { - .hotspot:not([data-block-status="loaded"]) { - min-height: 350px; - } - .hotspot-container { flex-direction: column; - min-height: 350px; } .hotspot-tooltip-panel { @@ -285,6 +254,22 @@ } } +/* Responsive adjustments */ +@media (width <= 1024px) { + .hotspot-tooltip-panel { + flex: 0 0 35%; + padding: 30px; + } + + .hotspot-tooltip-content { + font-size: 16px; + } + + .hotspot-image-section { + flex: 0 0 65%; + } +} + /* Hide empty panel */ .hotspot-tooltip-panel:not(.visible) { pointer-events: none; diff --git a/styles/styles.css b/styles/styles.css index 39a302f..e080c47 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -531,6 +531,15 @@ main>.section>div { padding: 0 var(--grid-gutter-width); } +main > .section.hero-heritage-cc-container { + width: 100%; + height: 100vh; + + > div.hero-heritage-cc-wrapper { + max-width: 100%; + } +} + main>.section:first-of-type { margin-top: 0; } From 2df1a8d7604ec943824c67990b3a27bc3c1f64e6 Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Thu, 26 Mar 2026 13:51:32 -0400 Subject: [PATCH 05/13] trying a non-module script for lcp --- head.html | 1 + scripts/hero-intro-lcp-priority.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 scripts/hero-intro-lcp-priority.js diff --git a/head.html b/head.html index fe2e3b4..046a895 100644 --- a/head.html +++ b/head.html @@ -5,6 +5,7 @@ move-to-http-header="true" > + diff --git a/scripts/hero-intro-lcp-priority.js b/scripts/hero-intro-lcp-priority.js new file mode 100644 index 0000000..8eeb79d --- /dev/null +++ b/scripts/hero-intro-lcp-priority.js @@ -0,0 +1,26 @@ +/** + * Deferred (non-module) so it runs as soon as the HTML document is parsed, before + * aem.js / scripts.js modules. Upgrades hero intro from plain-HTML loading="lazy" + * to eager + fetchpriority=high so the LCP request is not deferred to script phase. + */ +(function heroIntroLcpPriority() { + if (document.querySelector('main[data-aue-resource]')) return; + + const main = document.querySelector('main'); + if (!main) return; + + const block = main.querySelector('.hero-heritage-cc'); + if (!block) return; + + const rows = [...block.children].filter((c) => c.tagName === 'DIV'); + const introRow = rows[1]; + if (!introRow) return; + + const introContent = introRow.querySelector(':scope > div'); + const pic = introContent?.querySelector('picture'); + const img = pic?.querySelector('img'); + if (!img) return; + + img.setAttribute('loading', 'eager'); + img.setAttribute('fetchpriority', 'high'); +}()); From 7a95c145e27680f95103f1d869a3a42108dfc05d Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Thu, 26 Mar 2026 14:47:14 -0400 Subject: [PATCH 06/13] 621-herolcp --- blocks/hero-heritage-cc/hero-heritage-cc.css | 5 +++ blocks/hero-heritage-cc/hero-heritage-cc.js | 36 ++++++++++++++++++++ blocks/hotspot/hotspot.js | 33 +++++++++++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/blocks/hero-heritage-cc/hero-heritage-cc.css b/blocks/hero-heritage-cc/hero-heritage-cc.css index 8330eb2..a14a95b 100644 --- a/blocks/hero-heritage-cc/hero-heritage-cc.css +++ b/blocks/hero-heritage-cc/hero-heritage-cc.css @@ -515,6 +515,11 @@ color: var(--color-pewter); .hero-heritage-cc p { font-size: var(--body-font-size-m); } + + .hero-heritage-cc-intro .hero-heritage-cc-intro-logo-hindi, + .hero-heritage-cc-intro .hero-heritage-cc-intro-logo-english { + min-height: 133px; + } } /* Desktop: 900px and up */ diff --git a/blocks/hero-heritage-cc/hero-heritage-cc.js b/blocks/hero-heritage-cc/hero-heritage-cc.js index a33eef8..6207833 100644 --- a/blocks/hero-heritage-cc/hero-heritage-cc.js +++ b/blocks/hero-heritage-cc/hero-heritage-cc.js @@ -68,6 +68,41 @@ function normalizeCssColorValue(value) { return trimmed; } +/** + * HTML width/height on imgs prevent "unsized image" CLS; CSS min-height alone is not enough for DevTools. + */ +function ensureMissingImgDimensions(img, defaultW, defaultH) { + if (!img) return; + if (img.hasAttribute('width') && img.hasAttribute('height')) return; + let w = parseInt(img.getAttribute('width'), 10); + let h = parseInt(img.getAttribute('height'), 10); + const hasW = Number.isFinite(w) && w > 0; + const hasH = Number.isFinite(h) && h > 0; + if (hasW && hasH) return; + if (hasW && !hasH) { + img.setAttribute('height', String(Math.round((w * defaultH) / defaultW))); + return; + } + if (hasH && !hasW) { + img.setAttribute('width', String(Math.round((h * defaultW) / defaultH))); + return; + } + img.setAttribute('width', String(defaultW)); + img.setAttribute('height', String(defaultH)); +} + +/** Set explicit dimensions on hero images that often ship without width/height in markup. */ +function ensureHeroImagesHaveDimensions(block) { + block + .querySelectorAll('.hero-heritage-cc-intro-logo-hindi img, .hero-heritage-cc-intro-logo-english img') + .forEach((img) => ensureMissingImgDimensions(img, 400, 252)); + block.querySelectorAll('.hero-heritage-cc-banner-top-logo img').forEach((img) => { + ensureMissingImgDimensions(img, 135, 84); + }); + const bannerImg = block.querySelector('.hero-heritage-cc-banner-image img'); + ensureMissingImgDimensions(bannerImg, 600, 391); +} + /** Collapse hero and show first hotspot block (when "The Concept" is clicked). */ function showConceptHotspotBlock() { const hero = document.querySelector('.hero-heritage-cc'); @@ -370,4 +405,5 @@ export default function decorate(block) { if (rows[0]) decorateHeaderCta(block, rows[0]); if (rows[1]) decorateIntro(block, rows[1], hasAueResource); if (rows[2]) decorateBanner(block, rows[2]); + ensureHeroImagesHaveDimensions(block); } diff --git a/blocks/hotspot/hotspot.js b/blocks/hotspot/hotspot.js index 47a5d3e..927e0e4 100644 --- a/blocks/hotspot/hotspot.js +++ b/blocks/hotspot/hotspot.js @@ -646,6 +646,35 @@ function setupConnectorLine(container, currentBlockId = null) { * @param {HTMLElement[]} rows - Block row elements * @returns {{ imageElement: HTMLPictureElement|null, blockId: string, hotspotText: string }} */ +/** + * Set width/height on img when missing so Chrome does not treat it as "unsized" (CLS). + * Uses 16:10 to match .hotspot-image-wrapper { aspect-ratio: 16 / 10 }. + * Preserves or completes attrs when only one of width/height is authored. + */ +function ensureHotspotMainImageDimensions(picture) { + const img = picture.querySelector('img'); + if (!img) return; + const aw = 16; + const ah = 10; + const defW = 600; + const defH = 375; + let w = parseInt(img.getAttribute('width'), 10); + let h = parseInt(img.getAttribute('height'), 10); + const hasW = Number.isFinite(w) && w > 0; + const hasH = Number.isFinite(h) && h > 0; + if (hasW && hasH) return; + if (hasW && !hasH) { + img.setAttribute('height', String(Math.round((w * ah) / aw))); + return; + } + if (hasH && !hasW) { + img.setAttribute('width', String(Math.round((h * aw) / ah))); + return; + } + img.setAttribute('width', String(defW)); + img.setAttribute('height', String(defH)); +} + function parseBlockMetadata(rows) { let imageElement = null; let blockId = ''; @@ -746,7 +775,9 @@ export default async function decorate(block) { // Add the image if (imageElement) { - imageWrapper.appendChild(imageElement.cloneNode(true)); + const picClone = imageElement.cloneNode(true); + ensureHotspotMainImageDimensions(picClone); + imageWrapper.appendChild(picClone); } imageSection.appendChild(imageWrapper); From fc0e3244c5b905b747c2c86f26e9638e5507cdf2 Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Thu, 26 Mar 2026 14:59:01 -0400 Subject: [PATCH 07/13] rolling back changes that don't help --- blocks/hero-heritage-cc/hero-heritage-cc.js | 36 --------------------- blocks/hotspot/hotspot.js | 33 +------------------ head.html | 1 - scripts/hero-intro-lcp-priority.js | 26 --------------- 4 files changed, 1 insertion(+), 95 deletions(-) delete mode 100644 scripts/hero-intro-lcp-priority.js diff --git a/blocks/hero-heritage-cc/hero-heritage-cc.js b/blocks/hero-heritage-cc/hero-heritage-cc.js index 6207833..a33eef8 100644 --- a/blocks/hero-heritage-cc/hero-heritage-cc.js +++ b/blocks/hero-heritage-cc/hero-heritage-cc.js @@ -68,41 +68,6 @@ function normalizeCssColorValue(value) { return trimmed; } -/** - * HTML width/height on imgs prevent "unsized image" CLS; CSS min-height alone is not enough for DevTools. - */ -function ensureMissingImgDimensions(img, defaultW, defaultH) { - if (!img) return; - if (img.hasAttribute('width') && img.hasAttribute('height')) return; - let w = parseInt(img.getAttribute('width'), 10); - let h = parseInt(img.getAttribute('height'), 10); - const hasW = Number.isFinite(w) && w > 0; - const hasH = Number.isFinite(h) && h > 0; - if (hasW && hasH) return; - if (hasW && !hasH) { - img.setAttribute('height', String(Math.round((w * defaultH) / defaultW))); - return; - } - if (hasH && !hasW) { - img.setAttribute('width', String(Math.round((h * defaultW) / defaultH))); - return; - } - img.setAttribute('width', String(defaultW)); - img.setAttribute('height', String(defaultH)); -} - -/** Set explicit dimensions on hero images that often ship without width/height in markup. */ -function ensureHeroImagesHaveDimensions(block) { - block - .querySelectorAll('.hero-heritage-cc-intro-logo-hindi img, .hero-heritage-cc-intro-logo-english img') - .forEach((img) => ensureMissingImgDimensions(img, 400, 252)); - block.querySelectorAll('.hero-heritage-cc-banner-top-logo img').forEach((img) => { - ensureMissingImgDimensions(img, 135, 84); - }); - const bannerImg = block.querySelector('.hero-heritage-cc-banner-image img'); - ensureMissingImgDimensions(bannerImg, 600, 391); -} - /** Collapse hero and show first hotspot block (when "The Concept" is clicked). */ function showConceptHotspotBlock() { const hero = document.querySelector('.hero-heritage-cc'); @@ -405,5 +370,4 @@ export default function decorate(block) { if (rows[0]) decorateHeaderCta(block, rows[0]); if (rows[1]) decorateIntro(block, rows[1], hasAueResource); if (rows[2]) decorateBanner(block, rows[2]); - ensureHeroImagesHaveDimensions(block); } diff --git a/blocks/hotspot/hotspot.js b/blocks/hotspot/hotspot.js index 927e0e4..47a5d3e 100644 --- a/blocks/hotspot/hotspot.js +++ b/blocks/hotspot/hotspot.js @@ -646,35 +646,6 @@ function setupConnectorLine(container, currentBlockId = null) { * @param {HTMLElement[]} rows - Block row elements * @returns {{ imageElement: HTMLPictureElement|null, blockId: string, hotspotText: string }} */ -/** - * Set width/height on img when missing so Chrome does not treat it as "unsized" (CLS). - * Uses 16:10 to match .hotspot-image-wrapper { aspect-ratio: 16 / 10 }. - * Preserves or completes attrs when only one of width/height is authored. - */ -function ensureHotspotMainImageDimensions(picture) { - const img = picture.querySelector('img'); - if (!img) return; - const aw = 16; - const ah = 10; - const defW = 600; - const defH = 375; - let w = parseInt(img.getAttribute('width'), 10); - let h = parseInt(img.getAttribute('height'), 10); - const hasW = Number.isFinite(w) && w > 0; - const hasH = Number.isFinite(h) && h > 0; - if (hasW && hasH) return; - if (hasW && !hasH) { - img.setAttribute('height', String(Math.round((w * ah) / aw))); - return; - } - if (hasH && !hasW) { - img.setAttribute('width', String(Math.round((h * aw) / ah))); - return; - } - img.setAttribute('width', String(defW)); - img.setAttribute('height', String(defH)); -} - function parseBlockMetadata(rows) { let imageElement = null; let blockId = ''; @@ -775,9 +746,7 @@ export default async function decorate(block) { // Add the image if (imageElement) { - const picClone = imageElement.cloneNode(true); - ensureHotspotMainImageDimensions(picClone); - imageWrapper.appendChild(picClone); + imageWrapper.appendChild(imageElement.cloneNode(true)); } imageSection.appendChild(imageWrapper); diff --git a/head.html b/head.html index 046a895..fe2e3b4 100644 --- a/head.html +++ b/head.html @@ -5,7 +5,6 @@ move-to-http-header="true" > - diff --git a/scripts/hero-intro-lcp-priority.js b/scripts/hero-intro-lcp-priority.js deleted file mode 100644 index 8eeb79d..0000000 --- a/scripts/hero-intro-lcp-priority.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Deferred (non-module) so it runs as soon as the HTML document is parsed, before - * aem.js / scripts.js modules. Upgrades hero intro from plain-HTML loading="lazy" - * to eager + fetchpriority=high so the LCP request is not deferred to script phase. - */ -(function heroIntroLcpPriority() { - if (document.querySelector('main[data-aue-resource]')) return; - - const main = document.querySelector('main'); - if (!main) return; - - const block = main.querySelector('.hero-heritage-cc'); - if (!block) return; - - const rows = [...block.children].filter((c) => c.tagName === 'DIV'); - const introRow = rows[1]; - if (!introRow) return; - - const introContent = introRow.querySelector(':scope > div'); - const pic = introContent?.querySelector('picture'); - const img = pic?.querySelector('img'); - if (!img) return; - - img.setAttribute('loading', 'eager'); - img.setAttribute('fetchpriority', 'high'); -}()); From e01a94eb8cbff10f9d5027ef78ee049cce22d6d7 Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Thu, 26 Mar 2026 15:36:15 -0400 Subject: [PATCH 08/13] preload smaller image on mobile --- blocks/hero-heritage-cc/hero-heritage-cc.js | 34 ++++++++++----------- scripts/aem.js | 29 ++++++++++++++++++ scripts/scripts.js | 10 +++--- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/blocks/hero-heritage-cc/hero-heritage-cc.js b/blocks/hero-heritage-cc/hero-heritage-cc.js index a33eef8..054e0b1 100644 --- a/blocks/hero-heritage-cc/hero-heritage-cc.js +++ b/blocks/hero-heritage-cc/hero-heritage-cc.js @@ -1,4 +1,4 @@ -import { decorateButtons } from '../../scripts/aem.js'; +import { decorateButtons, pickPicturePreloadUrl } from '../../scripts/aem.js'; import { loadFragment } from '../../scripts/scripts.js'; /* eslint-disable secure-coding/no-hardcoded-credentials -- CSS classes/style props only */ @@ -107,6 +107,18 @@ function decorateHeaderCta(block, row) { decorateButtons(row); } +/** Insert hero intro image preload if missing (href must match viewport-selected picture). */ +function appendHeroIntroImagePreload(href, matchedWebpSource) { + if (document.querySelector(`head > link[rel="preload"][as="image"][href="${href}"]`)) return; + const preloadLink = document.createElement('link'); + preloadLink.rel = 'preload'; + preloadLink.as = 'image'; + preloadLink.href = href; + preloadLink.fetchPriority = 'high'; + if (matchedWebpSource || href.includes('format=webply')) preloadLink.type = 'image/webp'; + document.head.insertBefore(preloadLink, document.head.firstChild); +} + /** Apply background image from first picture to section (or UE preview). Uses an img for LCP. */ function applyIntroBackground(block, introContent, pictures) { const bgPicture = pictures[0]; @@ -122,12 +134,8 @@ function applyIntroBackground(block, introContent, pictures) { return; } - const webpSource = bgPicture.querySelector('source[type="image/webp"]'); - let bgUrl = webpSource?.srcset?.split(',')[0]?.trim()?.split(' ')[0] || bgImg?.src; - - if (bgUrl?.includes('optimize=medium')) { - bgUrl = bgUrl.replace('optimize=medium', 'optimize=large'); - } + const { url: bgUrlRaw, source: matchedWebpSource } = pickPicturePreloadUrl(bgPicture, 'image/webp'); + const bgUrl = bgUrlRaw || bgImg?.src || ''; const sectionContainer = block.closest('.section'); if (!sectionContainer || !bgUrl) { @@ -136,17 +144,7 @@ function applyIntroBackground(block, introContent, pictures) { return; } - /* Preload only if not already added by scripts.js (early LCP preload) */ - const existingPreload = document.querySelector(`head > link[rel="preload"][as="image"][href="${bgUrl}"]`); - if (!existingPreload) { - const preloadLink = document.createElement('link'); - preloadLink.rel = 'preload'; - preloadLink.as = 'image'; - preloadLink.href = bgUrl; - preloadLink.fetchPriority = 'high'; - if (webpSource) preloadLink.type = 'image/webp'; - document.head.insertBefore(preloadLink, document.head.firstChild); - } + appendHeroIntroImagePreload(bgUrl, matchedWebpSource); /* Keep an img in the DOM as the LCP element (better than CSS background for PageSpeed) */ const layer = document.createElement('div'); diff --git a/scripts/aem.js b/scripts/aem.js index 669c284..b63d286 100644 --- a/scripts/aem.js +++ b/scripts/aem.js @@ -356,6 +356,34 @@ function createOptimizedPicture( return picture; } +/** + * First matching URL for type + media (document order), else src. + * Matches browser behavior so preloads use the same URL as the chosen art direction + * (e.g. width=750 on mobile vs width=2000 for (min-width: 600px)). + * @param {HTMLPictureElement} picture + * @param {string} [typePrefix] e.g. image/webp + * @returns {{ url: string, source: Element|null, img: HTMLImageElement|null }} + */ +function pickPicturePreloadUrl(picture, typePrefix = 'image/webp') { + const needle = typePrefix.toLowerCase(); + const img = picture?.querySelector('img') ?? null; + if (!picture) { + return { url: '', source: null, img }; + } + for (const source of picture.querySelectorAll('source')) { + const t = (source.getAttribute('type') || '').toLowerCase(); + if (!t.startsWith(needle)) continue; + const media = source.getAttribute('media'); + if (media && !window.matchMedia(media).matches) continue; + const srcset = source.getAttribute('srcset'); + if (!srcset) continue; + const url = srcset.split(',')[0].trim().split(/\s+/)[0]; + if (url) return { url, source, img }; + } + const fallback = img?.getAttribute('src') || img?.src || ''; + return { url: fallback, source: null, img }; +} + /** * Set template (page structure) and theme (page styles). */ @@ -787,6 +815,7 @@ export { loadScript, loadSection, loadSections, + pickPicturePreloadUrl, readBlockConfig, sampleRUM, setup, diff --git a/scripts/scripts.js b/scripts/scripts.js index 2f8e4ad..84c5a11 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -16,6 +16,7 @@ import { getMetadata, DOMPURIFY, // readBlockConfig, + pickPicturePreloadUrl, toCamelCase, } from './aem.js'; @@ -296,13 +297,10 @@ function preloadHeroHeritageCcLcpImage(main) { const introContent = introRow.querySelector(':scope > div'); const firstPicture = introContent?.querySelector('picture'); if (!firstPicture) return; - const webpSource = firstPicture.querySelector('source[type="image/webp"]'); const img = firstPicture.querySelector('img'); - let url = webpSource?.srcset?.split(',')[0]?.trim()?.split(' ')[0] || img?.src; + const { url: urlRaw, source: matchedWebpSource } = pickPicturePreloadUrl(firstPicture, 'image/webp'); + const url = urlRaw || img?.src || ''; if (!url) return; - /* Match hero-heritage-cc.js: preload href must match the upgraded LCP URL. */ - if (url.includes('optimize=medium')) url = url.replace('optimize=medium', 'optimize=large'); - const existingPreload = document.querySelector(`head > link[rel="preload"][as="image"][href="${url}"]`); if (!existingPreload) { const link = document.createElement('link'); @@ -312,7 +310,7 @@ function preloadHeroHeritageCcLcpImage(main) { /* Property + attribute: Lighthouse reads the DOM attribute on the preload hint. */ link.fetchPriority = 'high'; link.setAttribute('fetchpriority', 'high'); - if (webpSource) link.type = 'image/webp'; + if (matchedWebpSource || url.includes('format=webply')) link.type = 'image/webp'; document.head.insertBefore(link, document.head.firstChild); } From 9cdacad583176cfc1ac176deca2dc4c98c2416e4 Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Fri, 27 Mar 2026 10:05:52 -0400 Subject: [PATCH 09/13] footer scroll fix --- blocks/anchor-nav/anchor-nav.js | 4 ++-- blocks/footer/footer.css | 2 -- styles/styles.css | 7 +++++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/blocks/anchor-nav/anchor-nav.js b/blocks/anchor-nav/anchor-nav.js index 7f5236d..daea308 100644 --- a/blocks/anchor-nav/anchor-nav.js +++ b/blocks/anchor-nav/anchor-nav.js @@ -88,7 +88,7 @@ export default function decorate(block) { e.preventDefault(); const target = document.getElementById(href.substring(1)); if (target) { - // Swapna: Move active class to clicked link for red underline + // Move active class to clicked link for red underline block.querySelectorAll('.anchor-nav-link:not(.primary, .secondary)').forEach((l) => l.classList.remove('active')); link.classList.add('active'); @@ -100,7 +100,7 @@ export default function decorate(block) { }); }); - // Swapna: Set first anchor link as active by default on page load + // Set first anchor link as active by default on page load const firstAnchorLink = block.querySelector('.anchor-nav-link:not(.primary, .secondary)'); if (firstAnchorLink) { firstAnchorLink.classList.add('active'); diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css index 230279b..2660170 100644 --- a/blocks/footer/footer.css +++ b/blocks/footer/footer.css @@ -1,7 +1,6 @@ footer { font-size: var(--body-font-size-xs); box-sizing: border-box; - -webkit-font-smoothing: antialiased; } .footer a { @@ -188,7 +187,6 @@ footer .footer>div .section>div { .footer>div>div .accordion-wrapper .accordion .accordion-item-body ul li { margin-top: var(--spacing-xs); list-style: none; - line-height: 1; } .footer>div>div .accordion-wrapper .accordion>div>div:nth-child(2) ul li a, diff --git a/styles/styles.css b/styles/styles.css index e080c47..59baef7 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -162,10 +162,17 @@ html { /* -webkit-font-smoothing: antialiased; */ + scroll-behavior: smooth; text-size-adjust: 100%; -webkit-tap-highlight-color: rgb(0 0 0 / 0%); } +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +} + body { display: none; margin: 0; From 2909ae3ccb8fdf9f5b72700983ab42af22b4980b Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Fri, 27 Mar 2026 10:12:42 -0400 Subject: [PATCH 10/13] removing strict-dynamic since they don't have it --- head.html | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/head.html b/head.html index fe2e3b4..5001fbf 100644 --- a/head.html +++ b/head.html @@ -1,13 +1,12 @@ - + - - + + - + From 01bc8677c2f50e562a3fe81d82a399d15211df34 Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Fri, 27 Mar 2026 10:48:49 -0400 Subject: [PATCH 11/13] testing the move of swiper-bundle files --- head.html | 2 -- scripts/scripts.js | 7 +++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/head.html b/head.html index 5001fbf..0afc988 100644 --- a/head.html +++ b/head.html @@ -7,6 +7,4 @@ - - diff --git a/scripts/scripts.js b/scripts/scripts.js index 84c5a11..99e178c 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -1169,6 +1169,12 @@ async function loadEager(doc) { decorateTemplateAndTheme(); loadThemeSpreadSheetConfig(); + /* Swiper: defer from head; load in eager phase so carousel blocks have CSS + global. */ + const swiperBundlePromise = Promise.all([ + loadCSS('/scripts/swiperjs/swiper-bundle.min.css'), + loadScript('/scripts/swiperjs/swiper-bundle.min.js'), + ]); + const getAppBanner = sessionStorage.getItem('getAppBanner'); const header = doc.querySelector('header'); if (header && !MEDIA_QUERIES.desktop.matches) { @@ -1213,6 +1219,7 @@ async function loadEager(doc) { } document.body.classList.add('appear'); + await swiperBundlePromise; await loadSection(main.querySelector('.section'), waitForFirstImage); } From ee72849435b279afb0022c6870033147ca6c3a0a Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Fri, 27 Mar 2026 10:54:08 -0400 Subject: [PATCH 12/13] reverting --- head.html | 2 ++ scripts/scripts.js | 7 ------- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/head.html b/head.html index 0afc988..5001fbf 100644 --- a/head.html +++ b/head.html @@ -7,4 +7,6 @@ + + diff --git a/scripts/scripts.js b/scripts/scripts.js index 99e178c..84c5a11 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -1169,12 +1169,6 @@ async function loadEager(doc) { decorateTemplateAndTheme(); loadThemeSpreadSheetConfig(); - /* Swiper: defer from head; load in eager phase so carousel blocks have CSS + global. */ - const swiperBundlePromise = Promise.all([ - loadCSS('/scripts/swiperjs/swiper-bundle.min.css'), - loadScript('/scripts/swiperjs/swiper-bundle.min.js'), - ]); - const getAppBanner = sessionStorage.getItem('getAppBanner'); const header = doc.querySelector('header'); if (header && !MEDIA_QUERIES.desktop.matches) { @@ -1219,7 +1213,6 @@ async function loadEager(doc) { } document.body.classList.add('appear'); - await swiperBundlePromise; await loadSection(main.querySelector('.section'), waitForFirstImage); } From b4ca57e517bec6760106a7690ab86351de29e4c0 Mon Sep 17 00:00:00 2001 From: Charity Helms Date: Fri, 27 Mar 2026 11:47:03 -0400 Subject: [PATCH 13/13] linting --- scripts/aem.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/scripts/aem.js b/scripts/aem.js index b63d286..71deade 100644 --- a/scripts/aem.js +++ b/scripts/aem.js @@ -356,6 +356,9 @@ function createOptimizedPicture( return picture; } +/** Cap scans (legitimate pictures use few sources; limits loop work for CWE-400). */ +const MAX_PICTURE_SOURCES_PRELOAD = 64; + /** * First matching URL for type + media (document order), else src. * Matches browser behavior so preloads use the same URL as the chosen art direction @@ -370,15 +373,21 @@ function pickPicturePreloadUrl(picture, typePrefix = 'image/webp') { if (!picture) { return { url: '', source: null, img }; } - for (const source of picture.querySelectorAll('source')) { + const sources = picture.querySelectorAll('source'); + const n = Math.min(sources.length, MAX_PICTURE_SOURCES_PRELOAD); + for (let i = 0; i < n; i += 1) { + const source = sources[i]; const t = (source.getAttribute('type') || '').toLowerCase(); - if (!t.startsWith(needle)) continue; - const media = source.getAttribute('media'); - if (media && !window.matchMedia(media).matches) continue; - const srcset = source.getAttribute('srcset'); - if (!srcset) continue; - const url = srcset.split(',')[0].trim().split(/\s+/)[0]; - if (url) return { url, source, img }; + if (t.startsWith(needle)) { + const media = source.getAttribute('media'); + if (!media || window.matchMedia(media).matches) { + const srcset = source.getAttribute('srcset'); + if (srcset) { + const url = srcset.split(',')[0].trim().split(/\s+/)[0]; + if (url) return { url, source, img }; + } + } + } } const fallback = img?.getAttribute('src') || img?.src || ''; return { url: fallback, source: null, img };