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/blocks/hero-heritage-cc/hero-heritage-cc.css b/blocks/hero-heritage-cc/hero-heritage-cc.css
index c3617f3..a14a95b 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 {
@@ -518,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 */
@@ -536,6 +538,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 +558,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 +576,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/hero-heritage-cc/hero-heritage-cc.js b/blocks/hero-heritage-cc/hero-heritage-cc.js
index a64b198..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,11 +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) {
@@ -135,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/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/head.html b/head.html
index c613248..5001fbf 100644
--- a/head.html
+++ b/head.html
@@ -1,13 +1,12 @@
-
+
-
-
-
-
+
+
+
+
diff --git a/scripts/aem.js b/scripts/aem.js
index 669c284..71deade 100644
--- a/scripts/aem.js
+++ b/scripts/aem.js
@@ -356,6 +356,43 @@ 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
+ * (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 };
+ }
+ 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)) {
+ 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 };
+}
+
/**
* Set template (page structure) and theme (page styles).
*/
@@ -787,6 +824,7 @@ export {
loadScript,
loadSection,
loadSections,
+ pickPicturePreloadUrl,
readBlockConfig,
sampleRUM,
setup,
diff --git a/scripts/scripts.js b/scripts/scripts.js
index 85df236..84c5a11 100644
--- a/scripts/scripts.js
+++ b/scripts/scripts.js
@@ -16,6 +16,7 @@ import {
getMetadata,
DOMPURIFY,
// readBlockConfig,
+ pickPicturePreloadUrl,
toCamelCase,
} from './aem.js';
@@ -280,30 +281,43 @@ 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');
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;
- 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;
+ /* Property + attribute: Lighthouse reads the DOM attribute on the preload hint. */
+ link.fetchPriority = 'high';
+ link.setAttribute('fetchpriority', 'high');
+ if (matchedWebpSource || url.includes('format=webply')) 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,13 +1182,14 @@ async function loadEager(doc) {
}
}
+ const main = doc.querySelector('main');
const templateName = getMetadata('template');
if (templateName) {
await loadTemplate(doc, templateName);
}
- const main = doc.querySelector('main');
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) {
@@ -1812,4 +1827,7 @@ async function loadPage() {
loadDelayed();
}
+/* Earliest point in this module: before loadEager (fonts, template, decorateMain). */
+preloadHeroHeritageCcLcpImage(document.querySelector('main'));
+
loadPage();
diff --git a/styles/styles.css b/styles/styles.css
index 39a302f..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;
@@ -531,6 +538,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;
}