diff --git a/.gitignore b/.gitignore index 55fcf8e..b2a5530 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ helix-importer-ui !AGENTS.md tasks/lessons.md tasks/todo.md +migration-work/navigation-validation/ +import-work/ +.skills/**/node_modules/ diff --git a/.migration/project.json b/.migration/project.json new file mode 100644 index 0000000..5e4c44d --- /dev/null +++ b/.migration/project.json @@ -0,0 +1,9 @@ +{ + "type": "da", + "sites": { + "publicissapient": { + "previewOrg": "jsalazaratadobe", + "previewSite": "publicissapient" + } + } +} \ No newline at end of file diff --git a/.skills/scrape-webpage/scripts/package-lock.json b/.skills/scrape-webpage/scripts/package-lock.json index 7171151..fa19dd3 100644 --- a/.skills/scrape-webpage/scripts/package-lock.json +++ b/.skills/scrape-webpage/scripts/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "dependencies": { + "playwright": "^1.61.1", "sharp": "^0.33.0" } }, @@ -429,12 +430,56 @@ "node": ">=8" } }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/is-arrayish": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", "license": "MIT" }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", diff --git a/.skills/scrape-webpage/scripts/package.json b/.skills/scrape-webpage/scripts/package.json index 6aa4586..e379088 100644 --- a/.skills/scrape-webpage/scripts/package.json +++ b/.skills/scrape-webpage/scripts/package.json @@ -1,6 +1,7 @@ { "type": "module", "dependencies": { + "playwright": "^1.61.1", "sharp": "^0.33.0" } } diff --git a/blocks/cards/cards.css b/blocks/cards/cards.css index 5cc2c9a..fbdca01 100644 --- a/blocks/cards/cards.css +++ b/blocks/cards/cards.css @@ -1,4 +1,9 @@ -/* Default cards */ +/* ========================================================================== + Cards — Publicis Sapient design system + Source: publicissapient.com/industries/financial-services + Minimalist content cards: white rounded surface, no heavy borders/shadows, + typographic hierarchy (eyebrow / title / body) and generous spacing. + ========================================================================== */ .cards > ul { list-style: none; @@ -7,34 +12,30 @@ display: grid; grid-template-columns: 1fr; gap: var(--space-l); - max-width: 340px; - margin-inline: auto; } .cards > ul > li { - border: 1px solid light-dark(var(--color-gray-200), rgb(245 242 237 / 10%)); - background: light-dark(var(--color-light), var(--color-card-dark)); display: flex; flex-direction: column; - border-radius: 12px; + background-color: light-dark(var(--color-light), var(--color-card-dark)); + border: 1px solid light-dark(var(--color-gray-200), rgb(255 255 255 / 10%)); + border-radius: 16px; overflow: hidden; - box-shadow: 0 2px 8px light-dark(rgb(0 0 0 / 5%), rgb(0 0 0 / 40%)), - 0 1px 2px light-dark(rgb(0 0 0 / 6%), rgb(0 0 0 / 25%)); - transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.35s ease, border-color 0.35s ease; + transition: transform 0.3s var(--ease-out-smooth), box-shadow 0.3s ease, border-color 0.3s ease; } .cards > ul > li:hover { - transform: translateY(-8px); - box-shadow: 0 24px 48px light-dark(rgb(161 79 43 / 18%), rgb(197 160 89 / 18%)); - border-color: light-dark(var(--color-gold), rgb(197 160 89 / 45%)); + transform: translateY(-4px); + border-color: light-dark(var(--color-rust), var(--color-rust-bright)); + box-shadow: 0 12px 28px light-dark(rgb(0 0 0 / 10%), rgb(0 0 0 / 45%)); } -/* Article wrapper — layout-invisible so the card link remains the direct flex child */ +/* Article wrapper is layout-invisible so the link/body stay direct children */ .cards > ul > li > article { display: contents; } -/* Card link */ +/* Card link (whole-card clickable) */ .cards .cards-card-link { display: flex; flex-direction: column; @@ -45,212 +46,454 @@ cursor: pointer; } -/* Image */ -.cards .cards-card-image { - line-height: 0; - flex-shrink: 0; - overflow: hidden; +/* ===== Text body ===== */ +.cards .cards-card-body { + padding: var(--space-l); + flex-grow: 1; + display: flex; + flex-direction: column; } -.cards .cards-card-image img { - display: block; - height: auto; - max-width: 100%; - margin-inline: auto; - transition: transform 0.55s cubic-bezier(0.16, 1, 0.3, 1); +/* ===== Image / icon cell ===== */ +.cards .cards-card-body:has(img), +.cards .cards-card-body:has(picture) { + padding: var(--space-l) var(--space-l) 0; + line-height: 0; } -.cards > ul > li:hover .cards-card-image img { - transform: scale(1.05); +/* Empty authored cell (image slot with no image) — collapse it */ +.cards .cards-card-body:empty { + display: none; } -/* Body */ -.cards .cards-card-body { - padding: var(--space-m); - flex-grow: 1; +.cards .cards-card-body img { + display: block; + width: 48px; + height: 48px; + object-fit: contain; } .cards .cards-card-body :is(h2, h3, h4) { - margin-top: 0; - line-height: 1.2; + margin: 0 0 var(--space-s); + font-family: var(--font-family); + font-size: 20px; + font-weight: 500; + line-height: 1.3; + color: light-dark(var(--color-ink), var(--color-parchment)); } .cards .cards-card-body p { - color: light-dark(var(--color-gray-700), var(--color-muted)); - font-size: var(--body-font-size-s); + margin: 0 0 var(--space-s); + font-family: var(--font-family-body); + font-size: 18px; line-height: 1.55; + color: light-dark(rgb(68 68 68), var(--color-gray-300)); +} + +/* ===== CTA container pushes to the bottom of the card ===== */ +.cards .cards-card-body p:has(> a), +.cards .cards-card-body p.button-container { + margin-top: auto; + padding-top: var(--space-s); +} + +.cards .cards-card-body p:last-child { + margin-bottom: 0; +} + +/* ===== CTA link (Learn more) — plain arrow-style link, undo button decoration ===== */ +.cards .cards-card-body a.button:any-link, +.cards .cards-card-body a:any-link { + display: inline-flex; + align-items: center; + gap: 6px; + margin: 0; + padding: 0; + border: 0; + border-radius: 0; + background: none; + box-shadow: none; + font-family: var(--font-family-body); + font-size: var(--body-font-size-m); + font-weight: 600; + color: light-dark(var(--color-rust), var(--color-rust-bright)); + text-decoration: none; + text-transform: none; + letter-spacing: normal; + white-space: normal; + overflow: visible; +} + +.cards .cards-card-body a.button:any-link::after, +.cards .cards-card-body a:any-link::after { + content: '→'; + transition: transform 0.25s ease; +} + +.cards .cards-card-body a:hover { + color: light-dark(var(--color-gold), var(--color-rust)); + text-decoration: none; + background: none; + box-shadow: none; +} + +/* Title links inherit color (higher specificity, placed after base link rules) */ +.cards .cards-card-body :is(h2, h3, h4) a:any-link { + color: inherit; + text-decoration: none; } +.cards .cards-card-body :is(h2, h3, h4) a:hover { + color: var(--color-rust); + text-decoration: none; +} + +/* ===== Eyebrow label (first

immediately before the title) ===== */ +.cards .cards-card-body > p:first-child:has(+ h2), +.cards .cards-card-body > p:first-child:has(+ h3), +.cards .cards-card-body > p:first-child:has(+ h4) { + margin-bottom: var(--space-m); + font-family: var(--font-family-mono); + font-size: 14px; + font-weight: 500; + line-height: 1.3; + letter-spacing: 0.02em; + color: light-dark(var(--color-rust), var(--color-rust-bright)); + text-transform: none; +} + +.cards .cards-card-body a:hover::after { + transform: translateX(4px); +} + +/* ========================================================================== + Responsive grid + ========================================================================== */ + /* Tablet: 2 columns */ @media (width >= 600px) { .cards > ul { grid-template-columns: repeat(2, 1fr); - max-width: none; + gap: var(--space-xl); } } -/* Desktop */ +/* Desktop: 3 columns */ @media (width >= 900px) { .cards > ul { - grid-template-columns: repeat(auto-fill, minmax(257px, 1fr)); + grid-template-columns: repeat(3, 1fr); } - .cards .cards-card-body { - padding: var(--space-l); + .cards .cards-card-body :is(h2, h3, h4) { + font-size: 22px; } } -/* Links variant empty state */ -.cards .cards-links-empty { +/* ========================================================================== + icon-lg variant — use-case cards ordered heading → centered graphic → body. + cards.js lifts the heading above the image cell; the graphic is enlarged + from the default 48px icon to a prominent centered illustration. + ========================================================================== */ + +/* Uniform 32px padding around the card content, matching the source. The base + cards rule sets `article { display: contents }`, which drops any padding on + the article — so restore it as a real flex column and pad it here. */ +.cards.icon-lg > ul > li > article { + display: flex; + flex-direction: column; + align-items: center; + padding: 32px; + text-align: center; +} + +/* Lifted heading (now a direct child of article, above the image) */ +.cards.icon-lg > ul > li > article > :is(h2, h3, h4) { margin: 0; - padding: var(--space-m); - color: light-dark(var(--color-gray-600), var(--color-gray-400)); + padding: 0; + font-family: var(--font-family); + font-size: 24px; + font-weight: 500; + line-height: 1.3; + color: light-dark(var(--color-ink), var(--color-parchment)); +} + +.cards.icon-lg .cards-card-body { + padding: 0; +} + +/* Centered graphic cell, ~47px below heading and ~34px above the body. */ +.cards.icon-lg .cards-card-body:has(img), +.cards.icon-lg .cards-card-body:has(picture) { + display: flex; + justify-content: center; + padding: 47px 0 34px; +} + +.cards.icon-lg .cards-card-body img { + width: 180px; + height: 180px; +} + +/* stylelint-disable-next-line no-descending-specificity -- icon-lg paragraph size is an intentional variant override */ +.cards.icon-lg .cards-card-body p { + font-size: 18px; + line-height: 28px; } /* ========================================================================== - Bento variant + Accent variant — Bodhi-style icon cards on a warm gradient backdrop + Applied when the section carries Style=accent (icon + text cards). + Falls back gracefully: also triggered for image (icon) cards. ========================================================================== */ -.cards.bento > ul { - max-width: 1400px; - gap: 24px; +.cards.accent { + --card-icon-size: 24px; } -.cards.bento > ul > li { - border: var(--border-heritage); - border-radius: var(--border-radius-heritage); - padding: 45px; - background: light-dark(var(--color-parchment), var(--color-card-dark)); - transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.5s ease, border-color 0.4s ease, background-color 0.4s ease; +/* Warm gradient frame behind the scrolling card list (source red→white wash) */ +.cards.accent > ul { + display: block; + padding: var(--space-l); + border-radius: 24px; + background: linear-gradient(160deg, + light-dark(rgb(233 1 48 / 12%), rgb(233 1 48 / 22%)) 0%, + light-dark(rgb(233 1 48 / 4%), rgb(233 1 48 / 6%)) 45%, + transparent 100%); } -.cards.bento > ul > li:hover { - transform: translateY(-10px); - box-shadow: 0 32px 64px -12px light-dark(rgb(161 79 43 / 25%), rgb(197 160 89 / 20%)), 0 0 0 1px light-dark(var(--color-gold), rgb(197 160 89 / 40%)); - border-color: var(--color-gold); +/* Stack the agent cards vertically with a gap between them */ +.cards.accent > ul > li { + margin-bottom: var(--space-m); + border: 0; + border-radius: 16px; + box-shadow: 0 1px 3px rgb(0 0 0 / 6%); } -/* Featured card */ -.cards.bento > ul > li.cards-card-featured { - border: none; - background-color: var(--color-rust); - color: var(--color-parchment); +.cards.accent > ul > li:last-child { + margin-bottom: 0; } -/* Tag */ -.cards.bento .cards-card-tag { - font-family: var(--font-family-mono); - font-size: 0.75rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 3px; - color: var(--color-gold); - margin: 0 0 30px; +.cards.accent > ul > li:hover { + transform: none; + border-color: transparent; + box-shadow: 0 1px 3px rgb(0 0 0 / 6%); } -/* Headings */ -.cards.bento .cards-card-body :is(h2, h3) { - font-family: var(--font-family-serif); - font-weight: 500; - font-size: 2.2rem; - line-height: 1.1; - letter-spacing: -1px; - margin: 0; - color: light-dark(var(--color-ink), var(--color-parchment)); +/* Accent cards: white surface, icon left of text (flex row) */ +.cards.accent > ul > li > article { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: var(--space-m); + padding: var(--space-l); +} + +/* stylelint-disable-next-line no-descending-specificity -- accent/icon-lg variants are unrelated; source order is intentional */ +.cards.accent .cards-card-body { + padding: 0; +} + +.cards.accent .cards-card-body:has(img), +.cards.accent .cards-card-body:has(picture) { + flex: 0 0 auto; + padding-top: 4px; } -.cards.bento .cards-card-featured .cards-card-body :is(h2, h3) { - font-size: 3.2rem; +.cards.accent .cards-card-body img { + width: var(--card-icon-size); + height: var(--card-icon-size); + object-fit: contain; } -/* Body text */ -.cards.bento .cards-card-body p:not(.cards-card-tag) { - line-height: 1.5; +.cards.accent .cards-card-body :is(h2, h3, h4) { + margin-bottom: 4px; + font-size: 20px; + color: light-dark(#000, var(--color-parchment)); } -.cards.bento .cards-card-featured .cards-card-body p:not(.cards-card-tag) { - font-family: var(--font-family-serif); - font-size: 1.2rem; - opacity: 0.9; - margin-top: 35px; - max-width: 550px; +/* stylelint-disable-next-line no-descending-specificity -- accent desc overrides base body paragraph intentionally */ +.cards.accent .cards-card-body p { + margin-bottom: 0; + font-size: 18px; + line-height: 28px; } -.cards.bento > ul > li:not(.cards-card-featured) .cards-card-body p:not(.cards-card-tag) { - font-size: 0.95rem; - opacity: 0.8; - color: light-dark(var(--color-ink), var(--color-muted)); +/* Desktop: cap the list height and scroll, matching the source's fixed frame */ +@media (width >= 900px) { + .cards.accent > ul { + max-height: 640px; + overflow-y: auto; + scrollbar-width: thin; + } } -/* Focus */ -.cards.bento .cards-card-link:focus-visible { - outline: 2px solid var(--color-gold); - outline-offset: 3px; - border-radius: var(--border-radius-heritage); - box-shadow: 0 0 0 4px rgb(197 160 89 / 20%); +/* ========================================================================== + Large variant — carousel of big landscape cards, one in view with the next + peeking, prev/next arrow controls and a "Slide X of N" status. Each card is + a full-bleed image with a frosted-glass text panel in the bottom-left. + Matches the "realities" section on the source: + publicissapient.com/industries/financial-services + ========================================================================== */ + +.cards.large { + position: relative; } -/* Image — stretch to card edges */ -.cards.bento .cards-card-image { - margin: -45px -45px 24px; - width: calc(100% + 90px); - border-radius: var(--border-radius-heritage); +/* ----- Track (the scroll-snap rail) ----- */ +.cards.large .cards-carousel-viewport { + overflow: hidden; } -/* Body — no extra padding, card has its own */ -.cards.bento .cards-card-body { +.cards.large > .cards-carousel-viewport > ul.cards-track { + display: flex; + grid-template-columns: none; + gap: var(--space-l); + margin: 0; padding: 0; + overflow-x: auto; + scroll-snap-type: x mandatory; + scrollbar-width: none; } -/* Medium desktop: 2-column equal cards */ -@media (width >= 900px) { - .cards.bento > ul { - grid-template-columns: repeat(2, 1fr); - grid-auto-rows: auto; - } +.cards.large ul.cards-track::-webkit-scrollbar { + display: none; +} - .cards.bento > ul > li { - padding: 60px; - } +.cards.large > .cards-carousel-viewport > ul.cards-track > li { + position: relative; + flex: 0 0 100%; + scroll-snap-align: start; + border: 0; + border-radius: 16px; + overflow: hidden; + aspect-ratio: 16 / 9; +} - .cards.bento .cards-card-image { - margin: -60px -60px 30px; - width: calc(100% + 120px); - aspect-ratio: 16 / 9; - } +.cards.large > .cards-carousel-viewport > ul.cards-track > li:hover { + transform: none; + box-shadow: none; +} - .cards.bento .cards-card-image img { - width: 100%; - height: 100%; - object-fit: cover; - object-position: center; - } +/* Image cell fills the whole card */ +.cards.large .cards-card-body:has(img), +.cards.large .cards-card-body:has(picture) { + position: absolute; + inset: 0; + padding: 0; + line-height: 0; } -/* Large desktop: 12-column bento grid */ -@media (width >= 1200px) { - .cards.bento > ul { - grid-template-columns: repeat(12, 1fr); - } +.cards.large .cards-card-body:has(img) p { + margin: 0; + height: 100%; +} - .cards.bento > ul > li { - grid-column: span 3; - } +.cards.large .cards-card-body picture { + display: block; + width: 100%; + height: 100%; +} - .cards.bento > ul > li.cards-card-featured { - grid-column: span 6; - } +.cards.large .cards-card-body img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: 50% 50%; +} - /* Text-only cards (no image) span double width */ - .cards.bento > ul > li.cards-card-text-only { - grid-column: span 6; - } +/* Text cell becomes the frosted-glass panel, anchored bottom-left */ +.cards.large .cards-card-body:not(:has(img), :has(picture)) { + position: absolute; + bottom: var(--space-l); + left: var(--space-l); + width: min(454px, 80%); + gap: var(--space-m); + padding: 24px 20px; + background-color: rgb(255 255 255 / 22%); + backdrop-filter: blur(12px); + + /* stylelint-disable-next-line property-no-vendor-prefix -- Safari needs the prefix for backdrop-filter */ + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgb(255 255 255 / 30%); + border-radius: 16px; +} + +.cards.large .cards-card-body :is(h2, h3, h4) { + margin: 0; + font-size: 24px; + font-weight: 500; + line-height: 1.2; + color: #fff; +} - .cards.bento .cards-card-image { - aspect-ratio: 4 / 3; +/* stylelint-disable-next-line no-descending-specificity -- variant overrides base card body styles intentionally */ +.cards.large .cards-card-body p { + margin: 0; + font-size: 18px; + line-height: 28px; + color: #fff; +} + +/* ----- Carousel controls (prev/next arrows, top-right) ----- */ +.cards.large .cards-carousel-controls { + display: flex; + justify-content: flex-end; + gap: var(--space-s); + margin-bottom: var(--space-l); +} + +.cards.large .cards-carousel-controls button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + padding: 0; + background: light-dark(#fff, var(--color-card-dark)); + color: light-dark(var(--color-ink), var(--color-parchment)); + border: 1px solid light-dark(var(--color-ink), rgb(255 255 255 / 40%)); + border-radius: 9999px; + cursor: pointer; + transition: background 0.2s ease, color 0.2s ease, opacity 0.2s ease; +} + +.cards.large .cards-carousel-controls button::before { + font-size: 20px; + line-height: 1; +} + +.cards.large .cards-carousel-prev::before { + content: '←'; +} + +.cards.large .cards-carousel-next::before { + content: '→'; +} + +.cards.large .cards-carousel-controls button:disabled { + opacity: 0.35; + cursor: default; +} + +.cards.large .cards-carousel-controls button:hover:not(:disabled) { + background: light-dark(var(--color-ink), var(--color-parchment)); + color: light-dark(#fff, var(--color-ink)); +} + +.cards.large .cards-carousel-status { + margin-top: var(--space-m); + font-family: var(--font-family-body); + font-size: var(--body-font-size-s); + color: light-dark(rgb(68 68 68), var(--color-gray-300)); +} + +/* Desktop: reveal the next card peeking at the right edge */ +@media (width >= 900px) { + .cards.large > .cards-carousel-viewport > ul.cards-track > li { + flex-basis: calc(100% - var(--space-l) - 32px); } - .cards.bento .cards-card-featured .cards-card-image { - max-height: 300px; + .cards.large .cards-card-body :is(h2, h3, h4) { + font-size: 24px; } } diff --git a/blocks/cards/cards.js b/blocks/cards/cards.js index 13314e3..2fc0f15 100644 --- a/blocks/cards/cards.js +++ b/blocks/cards/cards.js @@ -182,6 +182,109 @@ function decorateDefault(block) { block.replaceChildren(ul); } +/** + * Mirror the section's `accent` style onto the block so the accent card + * treatment can be scoped to `.cards.accent`. Reads the section class (the + * standard EDS section-metadata path) and falls back to the authored + * section-metadata table when the section-metadata block is unavailable. + */ +function applyAccentVariant(block) { + const section = block.closest('.section'); + if (!section) return; + if (section.classList.contains('accent')) { + block.classList.add('accent'); + return; + } + const meta = section.querySelector('.section-metadata'); + if (!meta) return; + const isAccent = [...meta.querySelectorAll(':scope > div')].some((row) => { + const cells = [...row.children]; + return cells.length === 2 + && cells[0].textContent.trim().toLowerCase() === 'style' + && /\baccent\b/i.test(cells[1].textContent); + }); + if (isAccent) { + section.classList.add('accent'); + block.classList.add('accent'); + } +} + +/** + * Turn the decorated `large` cards into a one-at-a-time carousel of big + * landscape tiles (matches the "realities" section on the source): a scroll- + * snapping track with the next card peeking, prev/next arrow controls and a + * "Slide X of N" status. Content-first: the cards are already authored/rendered + * by decorateDefault; this only adds the carousel chrome and behavior. + */ +function decorateLargeCarousel(block) { + const track = block.querySelector(':scope > ul'); + if (!track) return; + const slides = [...track.children]; + if (slides.length < 2) return; + + track.classList.add('cards-track'); + + const viewport = createTag('div', { class: 'cards-carousel-viewport' }); + track.before(viewport); + viewport.append(track); + + const prev = createTag('button', { type: 'button', class: 'cards-carousel-prev', 'aria-label': 'Previous slide' }); + const next = createTag('button', { type: 'button', class: 'cards-carousel-next', 'aria-label': 'Next slide' }); + const controls = createTag('div', { class: 'cards-carousel-controls' }, [prev, next]); + block.prepend(controls); + + const status = createTag('div', { class: 'cards-carousel-status', 'aria-live': 'polite' }); + block.append(status); + + let index = 0; + const clamp = (i) => Math.max(0, Math.min(i, slides.length - 1)); + + const update = () => { + prev.disabled = index === 0; + next.disabled = index === slides.length - 1; + status.textContent = `Slide ${index + 1} of ${slides.length}`; + }; + + const goTo = (i) => { + index = clamp(i); + slides[index].scrollIntoView({ behavior: 'smooth', inline: 'start', block: 'nearest' }); + update(); + }; + + prev.addEventListener('click', () => goTo(index - 1)); + next.addEventListener('click', () => goTo(index + 1)); + + // Keep the index/status in sync when the user scrolls/swipes the track. + let scrollTimer; + track.addEventListener('scroll', () => { + clearTimeout(scrollTimer); + scrollTimer = setTimeout(() => { + const nearest = slides.reduce((best, slide, i) => { + const dist = Math.abs(slide.offsetLeft - track.scrollLeft); + return dist < best.dist ? { i, dist } : best; + }, { i: 0, dist: Infinity }); + index = nearest.i; + update(); + }, 100); + }, { passive: true }); + + update(); +} + +/** + * icon-lg variant: reorder each card to heading → image → details, matching + * the use-case cards on the source. The default decoration renders an image + * cell followed by a body cell (heading + paragraph); this lifts the heading + * above the image so the order reads title, graphic, then description. + */ +function decorateIconLg(block) { + block.querySelectorAll(':scope > ul > li > article').forEach((article) => { + const imageCell = [...article.children].find((el) => el.querySelector('img, picture')); + const heading = article.querySelector('h2, h3, h4, h5, h6'); + if (imageCell && heading) article.insertBefore(heading, imageCell); + }); +} + export default async function decorate(block) { if (block.classList.contains('links')) { await decorateLinks(block); @@ -189,5 +292,8 @@ export default async function decorate(block) { decorateBento(block); } else { decorateDefault(block); + applyAccentVariant(block); + if (block.classList.contains('large')) decorateLargeCarousel(block); + if (block.classList.contains('icon-lg')) decorateIconLg(block); } } diff --git a/blocks/context-graph/context-graph.css b/blocks/context-graph/context-graph.css new file mode 100644 index 0000000..fd06dd8 --- /dev/null +++ b/blocks/context-graph/context-graph.css @@ -0,0 +1,275 @@ +/* ========================================================================== + Context graph — central "Enterprise Context Graph" dome ringed by selectable + platform nodes. Selecting a node highlights it, dims the others, and shows + that platform's detail panel below. + Source: publicissapient.com/industries/financial-services + ========================================================================== */ + +.context-graph.block { + --node-active: var(--color-rust); + --node-muted: light-dark(#9a9a9a, #8a8a8a); + + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-3xl); +} + +/* ----- Stage: center image + surrounding nodes ----- */ +.context-graph .context-graph-stage { + position: relative; + width: 100%; + max-width: 1060px; + margin: 0 auto; +} + +.context-graph .context-graph-center { + width: 62%; + margin: 0 auto; +} + +.context-graph .context-graph-center picture, +.context-graph .context-graph-center img { + display: block; + width: 100%; + height: auto; +} + +/* ----- Platform nodes ----- */ +.context-graph .context-graph-node { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + + /* Background matches the section so the label masks (breaks) the arc line + that passes behind it — the text sits centered over the arc. */ + padding: 6px 16px; + background: var(--background-color, #f5f5f5); + border: 0; + cursor: pointer; + text-align: center; + color: inherit; + transition: opacity 0.3s ease, transform 0.3s ease; +} + +.context-graph .context-graph-node :is(h2, h3, h4) { + margin: 0; + font-family: var(--font-family); + font-size: 20px; + font-weight: 500; + line-height: 1.2; + color: var(--node-muted); + transition: color 0.3s ease; +} + +.context-graph .context-graph-node p { + margin: 0; + font-family: var(--font-family-body); + font-size: 14px; + line-height: 1.4; + color: var(--node-muted); + transition: color 0.3s ease; +} + +/* Active node: brand red, full opacity */ +.context-graph .context-graph-node.is-active :is(h2, h3, h4) { + color: var(--node-active); +} + +.context-graph .context-graph-node.is-active p { + color: light-dark(var(--color-ink), var(--color-parchment)); +} + +/* Dimmed (non-selected) nodes fade their text back — but keep the node + background fully opaque so it still masks the arc line passing behind it. */ +.context-graph .context-graph-node.is-dimmed :is(h2, h3, h4), +.context-graph .context-graph-node.is-dimmed p { + opacity: 0.5; +} + +.context-graph .context-graph-node:hover :is(h2, h3, h4), +.context-graph .context-graph-node:hover p { + opacity: 1; +} + +.context-graph .context-graph-node:focus-visible { + outline: 2px solid var(--node-active); + outline-offset: 4px; + border-radius: 8px; +} + +/* ----- Platform tiles (persistent 3-up row below the graph) ----- */ +.context-graph .context-graph-tiles { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-l); + width: 100%; + max-width: 1060px; +} + +.context-graph .context-graph-tile { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + padding: 24px 16px; + background: transparent; + border: 0; + border-top: 4px solid light-dark(var(--color-gray-200, #ddd), rgb(255 255 255 / 18%)); + cursor: pointer; + text-align: center; + color: inherit; + transition: opacity 0.3s ease, border-color 0.3s ease; +} + +.context-graph .context-graph-tile h3 { + margin: 0; + font-family: var(--font-family); + font-size: 24px; + font-weight: 400; + line-height: 30px; + color: light-dark(var(--color-ink), var(--color-parchment)); +} + +/* stylelint-disable-next-line no-descending-specificity -- tile and node paragraph rules are unrelated; source order is intentional */ +.context-graph .context-graph-tile p { + margin: 0; + font-family: var(--font-family-body); + font-size: 18px; + line-height: 28px; + color: light-dark(rgb(68 68 68), var(--color-gray-300)); +} + +/* Active tile: brand-red top rule only (title stays black, matching source) */ +.context-graph .context-graph-tile.is-active { + border-top-color: var(--node-active); +} + +/* Dimmed (non-selected) tiles fade back */ +.context-graph .context-graph-tile.is-dimmed { + opacity: 0.55; +} + +.context-graph .context-graph-tile:hover { + opacity: 1; + border-top-color: var(--node-active); +} + +.context-graph .context-graph-tile:focus-visible { + outline: 2px solid var(--node-active); + outline-offset: 4px; +} + +/* Undo EDS button auto-decoration — render "Learn more" as an inline black arrow link */ +.context-graph .context-graph-tile a.button:any-link, +.context-graph .context-graph-tile a:any-link { + display: inline-flex; + align-items: center; + gap: 6px; + margin: 0; + padding: 0; + border: 0; + border-radius: 0; + background: none; + box-shadow: none; + font-family: var(--font-family-body); + font-size: 18px; + font-weight: 600; + color: light-dark(var(--color-ink), var(--color-parchment)); + text-decoration: none; + text-transform: none; + letter-spacing: normal; +} + +.context-graph .context-graph-tile .button-container { + margin: 0; +} + +.context-graph .context-graph-tile a::after { + content: '→'; + transition: transform 0.25s ease; +} + +.context-graph .context-graph-tile a:hover::after { + transform: translateX(4px); +} + +/* ----- Mobile: image on top, nodes stack below (default flow) ----- */ +@media (width < 900px) { + .context-graph .context-graph-stage { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-m); + } + + .context-graph .context-graph-center { + order: -1; + width: 90%; + } +} + +/* ----- Desktop: absolutely position nodes around the dome ----- */ +@media (width >= 900px) { + /* Dome is ~half as tall as wide (16/9-ish arc). At 54% width the dome height + is ~ (0.54 * maxwidth / 2). Reserve just enough height for the top node + label to sit above the apex. */ + .context-graph .context-graph-stage { + min-height: 440px; + } + + /* Faint outer arc connecting the three platform nodes (behind the dome). + Sized so the ellipse passes through each node's vertical centre — the + side nodes sit lower than the apex, so the arc dips to meet them and each + label's background masks (breaks) the line where it crosses. */ + .context-graph .context-graph-stage::before { + content: ''; + position: absolute; + bottom: 0; + left: 11%; + width: 78%; + height: 87%; + border: 1px solid light-dark(var(--color-gray-200, #e0e0e0), rgb(255 255 255 / 18%)); + border-bottom: 0; + border-radius: 50% 50% 0 0 / 100% 100% 0 0; + pointer-events: none; + z-index: 0; + } + + .context-graph .context-graph-node { + position: absolute; + width: 210px; + z-index: 1; + } + + .context-graph .context-graph-node[data-position='top'] { + top: 8px; + left: 50%; + transform: translateX(-50%); + } + + .context-graph .context-graph-node[data-position='left'] { + bottom: 40px; + left: 2%; + } + + .context-graph .context-graph-node[data-position='right'] { + bottom: 40px; + right: 2%; + } + + .context-graph .context-graph-center { + position: absolute; + bottom: 0; + left: 50%; + width: 56%; + transform: translateX(-50%); + z-index: 1; + } + + .context-graph .context-graph-tiles { + grid-template-columns: repeat(3, 1fr); + gap: var(--space-xl); + } +} diff --git a/blocks/context-graph/context-graph.js b/blocks/context-graph/context-graph.js new file mode 100644 index 0000000..ef710ba --- /dev/null +++ b/blocks/context-graph/context-graph.js @@ -0,0 +1,119 @@ +/* + * Context graph — a central "Enterprise Context Graph" image ringed by + * selectable platform nodes (Bodhi / Slingshot / Sustain), with a persistent + * row of platform tiles below. Selecting a platform (via a ring node OR a tile) + * highlights it and dims the others; the ring nodes and tiles stay in sync. + * + * Authoring model (one row per part): + * Row 1 → single cell containing the center graph image. + * Rows 2+ → one platform each, two cells: + * • cell A: node label — heading (name) + optional tagline paragraph + * • cell B: detail — description paragraph(s) + optional "Learn more" link + * Platforms are placed around the dome by order: 1st=left, 2nd=top, 3rd=right. + */ + +const POSITIONS = ['left', 'top', 'right']; + +export default function decorate(block) { + const rows = [...block.children]; + if (rows.length < 2) return; + + const [centerRow, ...platformRows] = rows; + + const stage = document.createElement('div'); + stage.className = 'context-graph-stage'; + stage.setAttribute('role', 'tablist'); + stage.setAttribute('aria-label', 'Platforms'); + + // Center image — kept as the authored , served as-is (no optimization + // resize, no cropping) so the graph renders at full fidelity. + const center = document.createElement('div'); + center.className = 'context-graph-center'; + const img = centerRow.querySelector('img'); + if (img) center.append(img); + stage.append(center); + + const tiles = document.createElement('div'); + tiles.className = 'context-graph-tiles'; + + const nodes = []; + const tileEls = []; + + platformRows.forEach((row, i) => { + const cells = [...row.children]; + const labelCell = cells[0]; + const detailCell = cells[1]; + + const heading = labelCell?.querySelector('h1, h2, h3, h4, h5, h6'); + const name = heading?.textContent?.trim() || labelCell?.textContent?.trim() || `Platform ${i + 1}`; + const id = `ctx-graph-${name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')}`; + + // Ring node (label around the dome): keeps the name + tagline. + const node = document.createElement('button'); + node.type = 'button'; + node.className = 'context-graph-node'; + node.dataset.position = POSITIONS[i] || 'top'; + node.setAttribute('role', 'tab'); + node.setAttribute('aria-controls', `${id}-tile`); + node.id = `${id}-node`; + while (labelCell?.firstChild) node.append(labelCell.firstChild); + stage.append(node); + nodes.push(node); + + // Tile below (always visible): name heading + description + link. + const tile = document.createElement('button'); + tile.type = 'button'; + tile.className = 'context-graph-tile'; + tile.id = `${id}-tile`; + const tileHeading = document.createElement('h3'); + tileHeading.textContent = name; + tile.append(tileHeading); + if (detailCell) { + while (detailCell.firstChild) tile.append(detailCell.firstChild); + } + tiles.append(tile); + tileEls.push(tile); + }); + + block.replaceChildren(stage, tiles); + + const select = (index) => { + nodes.forEach((node, i) => { + const active = i === index; + node.classList.toggle('is-active', active); + node.classList.toggle('is-dimmed', !active); + node.setAttribute('aria-selected', active ? 'true' : 'false'); + node.tabIndex = active ? 0 : -1; + }); + tileEls.forEach((tile, i) => { + const active = i === index; + tile.classList.toggle('is-active', active); + tile.classList.toggle('is-dimmed', !active); + }); + }; + + const bindKeys = (els, i) => (e) => { + if (e.key === 'ArrowRight' || e.key === 'ArrowDown') { + e.preventDefault(); + const next = (i + 1) % els.length; + els[next].focus(); + select(next); + } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') { + e.preventDefault(); + const prev = (i - 1 + els.length) % els.length; + els[prev].focus(); + select(prev); + } + }; + + nodes.forEach((node, i) => { + node.addEventListener('click', () => select(i)); + node.addEventListener('keydown', bindKeys(nodes, i)); + }); + tileEls.forEach((tile, i) => { + tile.addEventListener('click', () => select(i)); + tile.addEventListener('keydown', bindKeys(tileEls, i)); + }); + + select(0); +} diff --git a/blocks/customer-story/customer-story.css b/blocks/customer-story/customer-story.css new file mode 100644 index 0000000..5de348b --- /dev/null +++ b/blocks/customer-story/customer-story.css @@ -0,0 +1,186 @@ +/* ========================================================================== + Customer story — "stacking cards" showcase. + Full-bleed photo card with an overlaid text panel (eyebrow, large light + headline, description, three stats, "Learn more") on the left. + Source: publicissapient.com/industries/financial-services + ========================================================================== */ + +.customer-story.block { + display: flex; + flex-direction: column; + gap: var(--space-l); +} + +/* Cards stack on scroll: each pins near the top so the next slides up and + covers it. Pinned cards scale down slightly (set via --card-scale by JS as + the next card approaches), so the stack recedes behind the active card. */ +.customer-story .customer-story-card { + position: sticky; + top: 100px; + display: flex; + align-items: flex-start; + min-height: 480px; + border-radius: 16px; + overflow: hidden; + box-shadow: 0 -4px 24px rgb(0 0 0 / 8%); + transform: scale(var(--card-scale, 1)); + transform-origin: center top; + transition: transform 0.1s linear; +} + +/* ----- Full-bleed background photo ----- */ +.customer-story .customer-story-media { + position: absolute; + inset: 0; + z-index: 0; +} + +/* The image may be wrapped in a

(EDS default). Force every wrapper and the + image itself to fill the card so the photo covers it edge to edge. */ +.customer-story .customer-story-media, +.customer-story .customer-story-media p, +.customer-story .customer-story-media picture, +.customer-story .customer-story-media img { + width: 100%; + height: 100%; + margin: 0; +} + +.customer-story .customer-story-media picture, +.customer-story .customer-story-media img { + display: block; + object-fit: cover; + object-position: 50% 50%; +} + +/* ----- Overlaid text panel ----- */ +.customer-story .customer-story-content { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + gap: var(--space-m); + width: 100%; + padding: var(--space-xl); + color: light-dark(var(--color-ink), var(--color-parchment)); + + /* Legibility scrim so text stays readable over the photo */ + background: linear-gradient( + to right, + light-dark(rgb(255 255 255 / 92%), rgb(20 20 20 / 88%)) 0%, + light-dark(rgb(255 255 255 / 82%), rgb(20 20 20 / 72%)) 55%, + transparent 100% + ); +} + +/* ----- Eyebrow pill ----- */ +.customer-story .customer-story-eyebrow { + align-self: flex-start; + margin: 0; + padding: 6px 14px; + border-radius: 9999px; + background: light-dark(rgb(0 0 0 / 6%), rgb(255 255 255 / 12%)); + font-family: var(--font-family-mono); + font-size: 14px; + font-weight: 500; + line-height: 1.2; + color: light-dark(rgb(68 68 68), var(--color-gray-300)); +} + +/* ----- Headline ----- */ +.customer-story .customer-story-content :is(h2, h3, h4) { + margin: 0; + font-family: var(--font-family); + font-size: 30px; + font-weight: 300; + line-height: 1.15; + color: light-dark(#000, var(--color-parchment)); +} + +/* ----- Description ----- */ +.customer-story .customer-story-content p:not(.customer-story-eyebrow, .customer-story-stat-label) { + margin: 0; + font-family: var(--font-family-body); + font-size: 18px; + line-height: 28px; + color: light-dark(rgb(68 68 68), var(--color-gray-300)); +} + +/* ----- Stats row ----- */ +.customer-story .customer-story-stats { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-m); + margin-top: var(--space-m); + padding-top: var(--space-l); + border-top: 1px solid light-dark(rgb(0 0 0 / 15%), rgb(255 255 255 / 25%)); +} + +.customer-story .customer-story-stat-num { + display: block; + font-family: var(--font-family); + font-size: 36px; + font-weight: 400; + line-height: 1.1; + color: light-dark(#000, var(--color-parchment)); +} + +.customer-story .customer-story-stat-label { + margin: 4px 0 0; + font-size: 16px; + line-height: 1.4; +} + +/* ----- Learn more link ----- */ +.customer-story .customer-story-link:any-link { + display: inline-flex; + align-items: center; + gap: 6px; + align-self: flex-start; + margin-top: var(--space-s); + font-family: var(--font-family-body); + font-size: 18px; + font-weight: 600; + color: light-dark(#000, var(--color-parchment)); + text-decoration: none; +} + +.customer-story .customer-story-link::after { + content: '→'; + transition: transform 0.25s ease; +} + +.customer-story .customer-story-link:hover::after { + transform: translateX(4px); +} + +/* ========================================================================== + Desktop: taller cards, text panel constrained to the left, wider stat row + ========================================================================== */ +@media (width >= 900px) { + .customer-story .customer-story-card { + min-height: 620px; + } + + .customer-story .customer-story-content { + width: 58%; + padding: 48px 40px; + } + + .customer-story .customer-story-content :is(h2, h3, h4) { + font-size: 42px; + } + + .customer-story .customer-story-stats { + grid-template-columns: repeat(3, 1fr); + gap: var(--space-l); + } + + .customer-story .customer-story-stat-num { + font-size: 42px; + } + + .customer-story .customer-story-stat-label { + font-size: 18px; + } +} diff --git a/blocks/customer-story/customer-story.js b/blocks/customer-story/customer-story.js new file mode 100644 index 0000000..7970fed --- /dev/null +++ b/blocks/customer-story/customer-story.js @@ -0,0 +1,98 @@ +import { createOptimizedPicture } from '../../scripts/aem.js'; +import { createTag } from '../../scripts/shared.js'; + +/* + * Customer story — "stacking cards" showcase. + * Each authored row is one story with two cells: + * 1. Image — the full-bleed background photo + * 2. Content — eyebrow paragraph, heading, description paragraph(s), an + * optional bulleted list of stats (each "NUMBER label"), + * and an optional "Learn more" link. + * The card renders the photo as a full-bleed background with the text content + * overlaid in a panel on the left. + */ + +function decorateStats(list) { + const stats = createTag('div', { class: 'customer-story-stats' }); + [...list.children].forEach((li) => { + const strong = li.querySelector('strong'); + const stat = createTag('div', { class: 'customer-story-stat' }); + if (strong) { + stat.append(createTag('span', { class: 'customer-story-stat-num' }, strong.textContent.trim())); + strong.remove(); + } + const label = li.textContent.trim(); + if (label) stat.append(createTag('p', { class: 'customer-story-stat-label' }, label)); + stats.append(stat); + }); + list.replaceWith(stats); +} + +export default function decorate(block) { + [...block.children].forEach((row) => { + row.classList.add('customer-story-card'); + const [imageCell, contentCell] = row.children; + + // Image cell → full-bleed background layer + if (imageCell) { + imageCell.classList.add('customer-story-media'); + const img = imageCell.querySelector('img'); + if (img) { + img.closest('picture')?.replaceWith( + createOptimizedPicture(img.src, img.alt || '', false, [{ width: '1200' }]), + ); + } + } + + // Content cell → overlaid text panel + if (contentCell) { + contentCell.classList.add('customer-story-content'); + + // Eyebrow = first paragraph (before the heading) + const heading = contentCell.querySelector('h1, h2, h3, h4, h5, h6'); + const firstP = contentCell.querySelector('p'); + if (heading && firstP && firstP.compareDocumentPosition(heading) + & Node.DOCUMENT_POSITION_FOLLOWING) { + firstP.classList.add('customer-story-eyebrow'); + } + + // Stats list + const list = contentCell.querySelector('ul, ol'); + if (list) decorateStats(list); + + // "Learn more" link — strip EDS button decoration, render as arrow link + const link = contentCell.querySelector('a'); + if (link) { + link.classList.remove('button'); + link.classList.add('customer-story-link'); + link.closest('.button-container')?.classList.remove('button-container'); + } + } + }); + + // Scroll-driven stacking: as each card scrolls up and pins, shrink it a + // little so the incoming card sits proudly on top and the stack recedes. + const cards = [...block.children]; + if (cards.length > 1) { + const MIN_SCALE = 0.9; + const update = () => { + const pinTop = cards[0].getBoundingClientRect().top; + cards.forEach((card, i) => { + const next = cards[i + 1]; + if (!next) { + card.style.setProperty('--card-scale', '1'); + return; + } + // Progress: 0 while the next card is far below, 1 once it has fully + // covered this pinned card (its top reaches the pin line). + const gap = next.getBoundingClientRect().top - pinTop; + const progress = Math.min(Math.max(1 - gap / card.offsetHeight, 0), 1); + const scale = 1 - (1 - MIN_SCALE) * progress; + card.style.setProperty('--card-scale', scale.toFixed(4)); + }); + }; + update(); + window.addEventListener('scroll', update, { passive: true }); + window.addEventListener('resize', update, { passive: true }); + } +} diff --git a/blocks/customer-testimonial/customer-testimonial.css b/blocks/customer-testimonial/customer-testimonial.css new file mode 100644 index 0000000..050dcbb --- /dev/null +++ b/blocks/customer-testimonial/customer-testimonial.css @@ -0,0 +1,69 @@ +/* ========================================================================== + Customer testimonial — a single monospace pull-quote with a left accent bar. + Source: publicissapient.com/industries/financial-services + ========================================================================== */ + +.customer-testimonial.block { + display: flex; + flex-direction: column; + gap: var(--space-l); +} + +/* ----- Eyebrow ----- */ +.customer-testimonial .customer-testimonial-eyebrow { + margin: 0; + font-family: var(--font-family-mono); + font-size: 16px; + font-weight: 500; + line-height: 1.3; + color: light-dark(rgb(102 102 102), var(--color-gray-300)); +} + +/* ----- Quote ----- */ +.customer-testimonial .customer-testimonial-quote { + margin: 0; + padding-left: var(--space-xl); + border-left: 1px solid light-dark(var(--color-gray-200, #ddd), rgb(255 255 255 / 25%)); + font-family: var(--font-family-mono); + font-size: 28px; + font-weight: 400; + line-height: 1.5; + color: light-dark(#000, var(--color-parchment)); +} + +.customer-testimonial .customer-testimonial-quote p { + margin: 0; +} + +/* ----- Attribution ----- */ +.customer-testimonial .customer-testimonial-attribution { + padding-left: var(--space-xl); +} + +.customer-testimonial .customer-testimonial-role { + margin: 0; + font-family: var(--font-family-body); + font-size: 18px; + font-weight: 500; + line-height: 1.4; + color: light-dark(#000, var(--color-parchment)); +} + +.customer-testimonial .customer-testimonial-firm { + margin: 4px 0 0; + font-family: var(--font-family-body); + font-size: 16px; + font-weight: 400; + line-height: 1.4; + color: light-dark(rgb(68 68 68), var(--color-gray-300)); +} + +/* ========================================================================== + Desktop: larger quote type matching the source (42px / 64px). + ========================================================================== */ +@media (width >= 900px) { + .customer-testimonial .customer-testimonial-quote { + font-size: 42px; + line-height: 64px; + } +} diff --git a/blocks/customer-testimonial/customer-testimonial.js b/blocks/customer-testimonial/customer-testimonial.js new file mode 100644 index 0000000..e2a334d --- /dev/null +++ b/blocks/customer-testimonial/customer-testimonial.js @@ -0,0 +1,29 @@ +/* + * Customer testimonial — a single pull-quote with a monospace eyebrow, a large + * quoted statement framed by a left accent bar, and an attribution (speaker + * role + company). Authored as three rows: + * 1. Eyebrow (e.g. "What our customers say") + * 2. Quote (the testimonial text) + * 3. Attribution (role on the first line, company on the second) + * Source: publicissapient.com/industries/financial-services + */ + +export default function decorate(block) { + const rows = [...block.children]; + const [eyebrowRow, quoteRow, attribRow] = rows; + + if (eyebrowRow) { + eyebrowRow.classList.add('customer-testimonial-eyebrow'); + } + + if (quoteRow) { + quoteRow.classList.add('customer-testimonial-quote'); + } + + if (attribRow) { + attribRow.classList.add('customer-testimonial-attribution'); + const lines = attribRow.querySelectorAll('p'); + lines[0]?.classList.add('customer-testimonial-role'); + lines[1]?.classList.add('customer-testimonial-firm'); + } +} diff --git a/blocks/explore-resources/explore-resources.css b/blocks/explore-resources/explore-resources.css new file mode 100644 index 0000000..7efe172 --- /dev/null +++ b/blocks/explore-resources/explore-resources.css @@ -0,0 +1,190 @@ +/* ========================================================================== + Explore resources — intro column beside a row of resource cards. + Source: publicissapient.com/industries/financial-services + ========================================================================== */ + +.explore-resources.block { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-xl); + align-items: start; +} + +/* ----- Full-bleed background image (optional first row) ----- */ +.explore-resources .explore-resources-bg { + position: absolute; + inset: 0; + z-index: -1; + overflow: hidden; + border-radius: 24px; +} + +.explore-resources .explore-resources-bg picture, +.explore-resources .explore-resources-bg img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + object-position: 50% 50%; +} + +/* When a background is present, become a positioning context and lift the + real content above the image. */ +.explore-resources.has-bg { + position: relative; + padding: var(--space-xl); +} + +/* ----- Intro column ----- */ +.explore-resources .explore-resources-intro { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: var(--space-l); +} + +.explore-resources .explore-resources-intro :is(h2, h3) { + margin: 0; + font-family: var(--font-family); + font-size: 36px; + font-weight: 300; + line-height: 1.1; + color: light-dark(var(--color-ink), var(--color-parchment)); +} + +.explore-resources .explore-resources-intro p { + margin: 0; + font-family: var(--font-family-body); + font-size: 18px; + line-height: 28px; + color: light-dark(rgb(68 68 68), var(--color-gray-300)); +} + +/* "Learn more" arrow pill */ +.explore-resources .explore-resources-cta:any-link { + display: inline-flex; + align-items: center; + gap: 8px; + margin: 0; + padding: 12px 24px; + border: 1px solid light-dark(var(--color-ink), var(--color-parchment)); + border-radius: 9999px; + background: none; + font-family: var(--font-family-body); + font-size: 16px; + font-weight: 600; + color: light-dark(var(--color-ink), var(--color-parchment)); + text-decoration: none; + transition: background 0.2s ease, color 0.2s ease; +} + +.explore-resources .explore-resources-cta::after { + content: '→'; + transition: transform 0.25s ease; +} + +.explore-resources .explore-resources-cta:hover { + background: light-dark(var(--color-ink), var(--color-parchment)); + color: light-dark(#fff, var(--color-ink)); +} + +.explore-resources .explore-resources-cta:hover::after { + transform: translateX(4px); +} + +/* ----- Cards ----- */ +.explore-resources .explore-resources-cards { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-l); + margin: 0; + padding: 0; + list-style: none; +} + +.explore-resources .explore-resources-card { + display: flex; + flex-direction: column; + min-height: 320px; + padding: 32px; + background: light-dark(var(--color-light), var(--color-card-dark)); + border-radius: 16px; + color: inherit; + text-decoration: none; + transition: transform 0.3s var(--ease-out-smooth), box-shadow 0.3s ease; +} + +a.explore-resources-card:hover { + transform: translateY(-4px); + box-shadow: 0 12px 28px light-dark(rgb(0 0 0 / 10%), rgb(0 0 0 / 45%)); +} + +/* Category eyebrow with a magnifier glyph */ +.explore-resources .explore-resources-category { + display: flex; + align-items: center; + gap: 8px; + margin: 0 0 var(--space-m); + font-family: var(--font-family-mono); + font-size: 14px; + font-weight: 500; + line-height: 1.3; + color: light-dark(var(--color-ink), var(--color-parchment)); +} + +.explore-resources .explore-resources-category::before { + content: ''; + width: 16px; + height: 16px; + background-color: currentcolor; + mask: url('data:image/svg+xml;utf8,') no-repeat center / contain; +} + +.explore-resources .explore-resources-title { + margin: 0; + font-family: var(--font-family); + font-size: 24px; + font-weight: 400; + line-height: 1.25; + color: light-dark(var(--color-ink), var(--color-parchment)); +} + +/* Date pinned to the bottom, above a full-width top rule */ +.explore-resources .explore-resources-date { + margin: auto 0 0; + padding-top: var(--space-m); + border-top: 1px solid light-dark(var(--color-gray-200, #ddd), rgb(255 255 255 / 18%)); + font-family: var(--font-family-mono); + font-size: 14px; + font-weight: 500; + line-height: 1.3; + color: light-dark(rgb(68 68 68), var(--color-gray-300)); +} + +/* ========================================================================== + Tablet: cards side by side. + ========================================================================== */ +@media (width >= 600px) { + .explore-resources .explore-resources-cards { + grid-template-columns: repeat(2, 1fr); + gap: var(--space-xl); + } +} + +/* ========================================================================== + Desktop: intro column beside the cards; larger heading. + ========================================================================== */ +@media (width >= 900px) { + .explore-resources.block { + grid-template-columns: 1fr 2fr; + gap: var(--space-2xl); + } + + .explore-resources .explore-resources-intro :is(h2, h3) { + font-size: 50px; + } + + .explore-resources .explore-resources-title { + font-size: 28px; + } +} diff --git a/blocks/explore-resources/explore-resources.js b/blocks/explore-resources/explore-resources.js new file mode 100644 index 0000000..767e768 --- /dev/null +++ b/blocks/explore-resources/explore-resources.js @@ -0,0 +1,87 @@ +/* + * Explore resources — a two-part layout: an intro column (heading, description, + * "Learn more" link) beside a row of resource cards (category eyebrow, title + * link, date). Fully authorable: + * Row 1 → intro: heading + description + optional "Learn more" link + * Row 2+ → one card each, three cells: category | title (link) | date + * Source: publicissapient.com/industries/financial-services + */ + +export default function decorate(block) { + let rows = [...block.children]; + + // Optional first row: a lone image → the section's full-bleed background. + const first = rows[0]; + const firstImg = first?.querySelector('img'); + if (firstImg && !first.textContent.trim()) { + first.classList.add('explore-resources-bg'); + const cell = first.firstElementChild; + if (cell) { + while (cell.firstChild) first.append(cell.firstChild); + cell.remove(); + } + block.classList.add('has-bg'); + rows = rows.slice(1); + } + + const [introRow, ...cardRows] = rows; + + // ----- Intro column ----- + if (introRow) { + introRow.classList.add('explore-resources-intro'); + const cell = introRow.firstElementChild; + if (cell) { + while (cell.firstChild) introRow.append(cell.firstChild); + cell.remove(); + } + // Render the last link as the arrow "Learn more" CTA (strip EDS button). + const link = introRow.querySelector('a'); + if (link) { + link.classList.remove('button'); + link.classList.add('explore-resources-cta'); + link.closest('.button-container')?.classList.remove('button-container'); + } + } + + // ----- Cards ----- + const list = document.createElement('ul'); + list.className = 'explore-resources-cards'; + + cardRows.forEach((row) => { + const cells = [...row.children]; + const [categoryCell, titleCell, dateCell] = cells; + const link = titleCell?.querySelector('a'); + + const li = document.createElement('li'); + const card = document.createElement(link ? 'a' : 'div'); + card.className = 'explore-resources-card'; + if (link) card.href = link.getAttribute('href'); + + if (categoryCell) { + const cat = document.createElement('p'); + cat.className = 'explore-resources-category'; + cat.textContent = categoryCell.textContent.trim(); + card.append(cat); + } + + if (titleCell) { + const title = document.createElement('h3'); + title.className = 'explore-resources-title'; + title.textContent = titleCell.textContent.trim(); + card.append(title); + } + + if (dateCell && dateCell.textContent.trim()) { + const date = document.createElement('p'); + date.className = 'explore-resources-date'; + date.textContent = dateCell.textContent.trim(); + card.append(date); + } + + li.append(card); + list.append(li); + row.remove(); + }); + + if (list.children.length) block.append(list); +} diff --git a/blocks/header/header.css b/blocks/header/header.css index 4659b15..5326237 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -1,1330 +1,430 @@ -/* header – mobile first, 600/900/1200px breakpoints */ +/* + * Header block — Publicis Sapient style. + * Two bands: red announcement carousel + solid white sticky main nav. + */ + /* stylelint-disable no-descending-specificity -- header cascade order is intentional across breakpoints */ -header { - --header-hover-bg: transparent; - --header-link-hover-bg: transparent; - --header-drop-shadow: none; - --header-drop-border: transparent; - - /* typography: brand (strongest) > primary nav > dropdown headings > dropdown items */ - --nav-brand-size: 1.7rem; - --nav-brand-weight: 700; - --nav-primary-size: 0.8rem; - --nav-primary-weight: 700; - --nav-heading-size: var(--body-font-size-s); - --nav-heading-weight: 700; - --nav-item-size: var(--body-font-size-s); - --nav-item-weight: 400; - - /* spacing: use space tokens */ - --nav-padding: var(--space-m); - --nav-gap: var(--space-s); - --nav-item-padding: var(--space-m); - --nav-dropdown-padding: var(--space-m); - --nav-dropdown-item-padding: var(--space-s) var(--space-m); - --nav-dropdown-offset: var(--space-s); - --nav-radius: var(--space-s); - - /* backgrounds */ - --nav-bg: var(--background-color); - --nav-bg-hover: var(--header-hover-bg); - --nav-bg-link-hover: var(--header-link-hover-bg); - --nav-bg-dropdown: light-dark(var(--background-color), var(--color-card-dark)); - --nav-bg-dropdown-mobile: light-dark(var(--color-gray-100), var(--color-card-dark)); - --nav-bg-mega: light-dark(var(--color-parchment), var(--color-obsidian)); - --nav-bg-hover-item: light-dark(var(--light-color), var(--color-card-dark)); - - /* header uses neutral hover color */ - --nav-hover-color: light-dark(var(--color-rust), var(--color-gold)); - --nav-focus-color: light-dark(var(--color-rust), var(--color-gold)); - - /* desktop-only (overridden in @media 900px) */ - --nav-desktop-max-width: 1264px; - --nav-desktop-dropdown-min: 220px; - --nav-desktop-dropdown-max: 320px; - --nav-desktop-mega-min-col: 200px; - --nav-desktop-arrow-size: 8px; - --nav-mega-promo-max-width: 280px; - --nav-mega-promo-img-height: 140px; - --nav-mega-promo-max-height: 260px; + +/* ---------- Wrapper / layout ---------- */ + +/* Reserve the header's real height (announcement band + main nav) up front so + the header never collapses to 0 before the nav fragment loads. Using + min-height (not height:auto) keeps the space reserved on first paint, which + prevents the ~190px downward shift of the page content once the header fills + in. It still grows if the content ends up taller. */ +header.header-wrapper { + min-height: 190px; } header .nav-wrapper { - background-color: light-dark(#faf8f5, var(--color-obsidian)); - width: 100%; - z-index: 1000; - position: fixed; + position: sticky; top: 0; - left: 0; - right: 0; - border-bottom: var(--border-heritage); - box-shadow: none; + z-index: 20; + background: #fff; } -/* mobile: base layout */ header nav { - box-sizing: border-box; display: grid; grid-template: - 'hamburger brand tools' var(--nav-height) - 'sections sections sections' 1fr / auto 1fr auto; + 'announcement announcement announcement' auto + 'brand sections tools' 134px + / auto 1fr auto; align-items: center; - gap: 0 var(--nav-gap); + max-width: var(--content-max-width); margin: 0 auto; - max-width: 100%; - min-height: var(--nav-height); - padding: 0 var(--nav-padding); font-family: var(--body-font-family); } -header nav[aria-expanded='true'] { - grid-template: - 'hamburger brand tools' var(--nav-height) - 'sections sections sections' 1fr / auto 1fr auto; - overflow: hidden; - min-height: 100dvh; -} +/* ---------- Announcement carousel ---------- */ -header nav p { - margin: 0; - line-height: 1; -} +header nav .nav-announcement { + grid-area: announcement; -header nav a:any-link { - color: currentcolor; - transition: color 0.18s ease, opacity 0.18s ease; + /* Break out of the centered nav container so the red band spans the full + viewport width, edge to edge, regardless of the nav's max-width. */ + width: 100vw; + margin-left: calc(50% - 50vw); + margin-right: calc(50% - 50vw); + background: var(--brand-red); + color: #fff; } -/* hamburger – mobile */ -header nav .nav-hamburger { - grid-area: hamburger; - justify-self: start; - height: 22px; +header nav .nav-announcement-inner { display: flex; align-items: center; + justify-content: center; + gap: 12px; + max-width: var(--content-max-width); + height: 56px; + margin: 0 auto; + padding: 0 24px; } -header nav .nav-hamburger button { - height: 22px; +header nav .nav-announcement ul { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + list-style: none; margin: 0; - border: 0; - border-radius: var(--nav-radius); padding: 0; - background-color: transparent; - color: inherit; - cursor: pointer; - transition: background-color 0.18s ease, opacity 0.18s ease; + text-align: center; } -header nav .nav-hamburger button:hover, -header nav .nav-hamburger button:focus-visible { - opacity: 0.85; - background-color: var(--nav-bg-hover); +header nav .nav-announcement-slide { + font-size: 15px; + font-weight: 400; } -header nav .nav-hamburger-icon, -header nav .nav-hamburger-icon::before, -header nav .nav-hamburger-icon::after { - box-sizing: border-box; - display: block; - position: relative; - width: 20px; +header nav .nav-announcement-slide[hidden] { + display: none; } -header nav .nav-hamburger-icon::before, -header nav .nav-hamburger-icon::after { - content: ''; - position: absolute; - background: currentcolor; +header nav .nav-announcement-slide a { + color: #fff; + font-weight: 600; + text-decoration: underline; + text-underline-offset: 3px; + margin-left: 6px; + white-space: nowrap; } -header nav[aria-expanded='false'] .nav-hamburger-icon, -header nav[aria-expanded='false'] .nav-hamburger-icon::before, -header nav[aria-expanded='false'] .nav-hamburger-icon::after { - height: 2px; - border-radius: 2px; - background: currentcolor; +header nav .nav-announcement-slide a:hover { + text-decoration: none; } -header nav[aria-expanded='false'] .nav-hamburger-icon::before { - top: -6px; +header nav .nav-announcement button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + border: 0; + background: transparent; + color: #fff; + cursor: pointer; + border-radius: 50%; } -header nav[aria-expanded='false'] .nav-hamburger-icon::after { - top: 6px; +header nav .nav-announcement button:hover { + background: rgb(255 255 255 / 20%); } -header nav[aria-expanded='true'] .nav-hamburger-icon { - height: 22px; - background: transparent; +header nav .nav-announcement-prev::before { + content: '‹'; + font-size: 22px; + line-height: 1; } -header nav[aria-expanded='true'] .nav-hamburger-icon::before, -header nav[aria-expanded='true'] .nav-hamburger-icon::after { - inset: 0; - width: 2px; - height: 20px; - margin: auto; - background: currentcolor; - border-radius: 2px; +header nav .nav-announcement-next::before { + content: '›'; + font-size: 22px; + line-height: 1; } -header nav[aria-expanded='true'] .nav-hamburger-icon::before { - transform: rotate(45deg); +header nav .nav-announcement-pause::before { + content: '❚❚'; + font-size: 11px; + letter-spacing: -1px; } -header nav[aria-expanded='true'] .nav-hamburger-icon::after { - transform: rotate(-45deg); +header nav .nav-announcement-pause.is-paused::before { + content: '▶'; + font-size: 11px; } -/* brand */ -header .nav-brand { +/* ---------- Brand ---------- */ + +header nav .nav-brand { grid-area: brand; - display: flex; - align-items: center; - justify-content: center; - justify-self: stretch; - min-width: 0; - font-family: var(--font-family-serif); - font-size: var(--nav-brand-size); - font-weight: var(--nav-brand-weight); - line-height: 1; - letter-spacing: -0.5px; - color: light-dark(var(--color-rust), var(--color-rust-bright)); + padding: 0 24px; } -header nav .nav-brand a:any-link { +header nav .nav-brand p { margin: 0; } + +header nav .nav-brand a { display: inline-flex; align-items: center; - transition: opacity 0.18s ease; } -header nav .nav-brand a:any-link:hover, -header nav .nav-brand a:any-link:focus-visible { - opacity: 0.85; +header nav .nav-brand img { + width: 100px; + height: 54px; } -header nav .nav-brand .icon.icon-logo { - position: relative; - top: 5px; - } - -header nav .nav-brand :is(img, svg) { - width: min(200px, 42vw); - height: 45px; - object-fit: contain; -} +/* ---------- Primary nav ---------- */ -/* sections – mobile: accordion when expanded */ header nav .nav-sections { grid-area: sections; - display: none; - visibility: hidden; - align-self: start; - width: 100%; } -header nav[aria-expanded='true'] .nav-sections { - display: block; - visibility: visible; - padding: var(--nav-item-padding) 0; -} - -header nav .nav-sections ul { +header nav .nav-sections > ul { + display: flex; + align-items: center; + gap: 40px; list-style: none; - padding: 0; margin: 0; - font-size: var(--nav-primary-size); - text-transform: uppercase; - letter-spacing: 1.5px; -} - -@media (width <= 899px) { - header nav .nav-sections { - overflow-y: auto; - -webkit-overflow-scrolling: touch; - } - - header nav[aria-expanded='true'] .nav-sections { - max-height: calc(var(--nav-open-height, 100dvh) - var(--nav-height)); - } - - header nav .nav-sections ul, - header nav .nav-sections .default-content-wrapper > ul > li, - header nav .nav-sections .default-content-wrapper > ul > li > ul { - text-align: center; - } - - header nav .nav-sections .default-content-wrapper > ul > li > p { - justify-content: center; - } - - header nav .nav-sections .default-content-wrapper > ul > li > p > a { - justify-content: center; - text-align: center; - } -} - -header nav .nav-sections .default-content-wrapper > ul > li { - font-weight: var(--nav-primary-weight); - border-bottom: 1px solid light-dark(var(--color-gray-200), var(--color-gray-600)); + padding: 0; } -header nav .nav-sections .default-content-wrapper > ul > li:last-child { - border-bottom: 0; +header nav .nav-sections > ul > li { + position: relative; } -header nav .nav-sections .default-content-wrapper > ul > li > p { - margin: 0; - padding: var(--nav-item-padding); +header nav .nav-sections > ul > li > a, +header nav .nav-sections > ul > li > .nav-drop-toggle { position: relative; - min-height: 44px; - display: flex; + display: inline-flex; align-items: center; -} - -header nav .nav-sections .default-content-wrapper > ul > li.nav-drop { + padding: 6px 0; + font-family: var(--body-font-family); + font-size: 16px; + font-weight: 500; + line-height: 1.5; + color: #000; + background: transparent; + border: 0; cursor: pointer; + text-decoration: none; + white-space: nowrap; } -header nav .nav-sections .default-content-wrapper > ul > li.nav-drop > p > a { - flex: 1; - min-height: 44px; - display: flex; - align-items: center; -} - -/* chevron: mobile only (p::after); desktop uses li::after below */ -@media (width <= 899px) { - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop > p { - padding-right: calc(var(--nav-item-padding) + 26px); - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop > p > .nav-submenu-toggle { - position: absolute; - right: var(--nav-item-padding); - top: 50%; - width: 24px; - height: 24px; - transform: translateY(-50%); - border: 0; - border-radius: 999px; - background: transparent; - color: inherit; - padding: 0; - cursor: pointer; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop > p > .nav-submenu-toggle::before { - content: ''; - display: block; - width: 6px; - height: 6px; - margin: auto; - border: 2px solid currentcolor; - border-radius: 0 1px 0 0; - border-width: 2px 2px 0 0; - opacity: 0.6; - transform: rotate(135deg); - transition: transform 0.2s ease; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop[aria-expanded='true'] > p > .nav-submenu-toggle::before { - transform: rotate(-45deg); - } +/* Animated red underline (source pattern): 2px bar under the text that scales + in from the left on hover / when the dropdown is open. No carets. */ +header nav .nav-sections > ul > li > a::after, +header nav .nav-sections > ul > li > .nav-drop-toggle::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: -2px; + height: 2px; + background: var(--brand-red); + transform: scaleX(0); + transform-origin: left center; + transition: transform 0.3s ease-in-out; } -header nav .nav-sections .default-content-wrapper > ul > li > p > a { - display: block; - color: inherit; - text-decoration: none; +header nav .nav-sections > ul > li > a:hover::after, +header nav .nav-sections > ul > li > .nav-drop-toggle:hover::after, +header nav .nav-sections .nav-drop[aria-expanded='true'] .nav-drop-toggle::after { + transform: scaleX(1); } -header nav .nav-sections .default-content-wrapper > ul > li > ul { +/* dropdown panel */ +header nav .nav-sections .nav-drop > ul { + position: absolute; + top: calc(100% + 8px); + left: 0; + z-index: 30; display: none; - padding: 0 var(--nav-dropdown-padding) var(--nav-dropdown-padding); + min-width: 304px; + padding: 12px; + list-style: none; margin: 0; - border-radius: var(--nav-radius); + background: #fff; + border: 1px solid #ececec; + border-radius: 8px; + box-shadow: 0 12px 32px rgb(0 0 0 / 12%); } -header nav .nav-sections .default-content-wrapper > ul > li[aria-expanded='true'] > ul { +header nav .nav-sections .nav-drop[aria-expanded='true'] > ul { display: block; } -/* mega menu groups: heading (e.g. BEGINNERS) + nested ul with items */ -header nav .nav-sections .default-content-wrapper > ul > li > ul > li.nav-mega-heading { - padding: var(--space-l) 0 var(--space-s); - font-size: var(--nav-heading-size); - font-weight: var(--nav-heading-weight); - color: var(--dark-color); - border-bottom: 0; -} - -header nav .nav-sections .default-content-wrapper > ul > li > ul > li.nav-mega-heading:first-child { - padding-top: 0; -} - -header nav .nav-sections .default-content-wrapper > ul > li > ul > li.nav-mega-heading > p { +header nav .nav-sections .nav-drop > ul > li { margin: 0; - padding: 0; - font-size: inherit; - font-weight: var(--nav-heading-weight); -} - -/* sub-items under headings: align with heading, normal case */ -header nav .nav-sections .default-content-wrapper > ul > li > ul > li.nav-mega-heading > ul { - padding: 0; - margin: 0; - background: none; - border-radius: 0; -} - -header nav .nav-sections .default-content-wrapper > ul > li > ul > li.nav-mega-heading > ul a { - font-size: var(--nav-item-size); - text-transform: none; - font-weight: var(--nav-item-weight); - padding: var(--space-xs) 0; -} - -header nav .nav-sections .default-content-wrapper > ul > li > ul > li.nav-mega-heading > ul > li.nav-mega-item { - padding: var(--space-s) 0; - font-weight: var(--nav-item-weight); - border-bottom: 0; - margin-left: 0; - border-left: 0; -} - -header nav .nav-sections .default-content-wrapper > ul > li > ul > li.nav-mega-heading > ul > li.nav-mega-item > a { - padding: 0; -} - -header nav .nav-sections .default-content-wrapper > ul > li > ul > li.nav-mega-item { - padding: var(--space-s) 0; - font-size: var(--nav-item-size); - font-weight: var(--nav-item-weight); - border-bottom: 0; -} - -header nav .nav-sections .default-content-wrapper > ul > li > ul > li:not(.nav-mega-heading, .nav-mega-item) { - padding: var(--space-xs) 0; - font-size: var(--nav-item-size); - font-weight: var(--nav-item-weight); -} - -/* mobile: mega menu accordion – structure with .nav-mega-inner */ -@media (width <= 899px) { - header nav .nav-sections .default-content-wrapper > ul > li > ul > li.nav-mega-promo, - header nav .nav-sections .default-content-wrapper > ul > li > ul > .nav-mega-inner > li.nav-mega-promo { - display: none; - } - - header nav .nav-sections .default-content-wrapper > ul > li > ul { - background-color: var(--nav-bg-dropdown-mobile); - padding: var(--nav-dropdown-padding); - margin: 0 var(--nav-gap) var(--nav-gap); - border-radius: var(--nav-radius); - border: 1px solid light-dark(var(--color-gray-200), var(--color-gray-600)); - } - - header nav .nav-sections .default-content-wrapper > ul > li > ul > .nav-mega-inner { - display: block; - padding: 0; - margin: 0; - } - - header nav .nav-sections .default-content-wrapper > ul > li > ul > .nav-mega-inner > li.nav-mega-heading { - padding: var(--space-m) 0 var(--space-s); - font-size: var(--nav-heading-size); - font-weight: var(--nav-heading-weight); - color: var(--dark-color); - } - - header nav .nav-sections .default-content-wrapper > ul > li > ul > .nav-mega-inner > li.nav-mega-heading:first-child { - padding-top: 0; - } - - header nav .nav-sections .default-content-wrapper > ul > li > ul > .nav-mega-inner > li.nav-mega-heading > p { - margin: 0 0 var(--space-s); - padding: 0; - } - - header nav .nav-sections .default-content-wrapper > ul > li > ul > .nav-mega-inner > li.nav-mega-heading > ul { - list-style: none; - padding: 0; - margin: 0; - } - - header nav .nav-sections .default-content-wrapper > ul > li > ul > .nav-mega-inner > li.nav-mega-heading > ul > li { - padding: 0; - margin: 0; - } - - header nav .nav-sections .default-content-wrapper > ul > li > ul > .nav-mega-inner > li.nav-mega-heading > ul a { - display: block; - padding: var(--space-s) 0; - font-size: var(--nav-item-size); - font-weight: var(--nav-item-weight); - text-transform: none; - color: inherit; - text-decoration: none; - } - - header nav .nav-sections .default-content-wrapper > ul > li > ul > .nav-mega-inner > li.nav-mega-item { - padding: var(--space-s) 0; - font-size: var(--nav-item-size); - font-weight: var(--nav-item-weight); - border-bottom: 1px solid light-dark(var(--color-gray-200), var(--color-gray-600)); - } - - header nav .nav-sections .default-content-wrapper > ul > li > ul > .nav-mega-inner > li.nav-mega-item:last-child { - border-bottom: 0; - } - - header nav .nav-sections .default-content-wrapper > ul > li > ul > .nav-mega-inner > li.nav-mega-item > a { - display: block; - padding: var(--space-s) 0; - color: inherit; - text-decoration: none; - } - - header nav .nav-sections .default-content-wrapper > ul > li > ul > .nav-mega-inner > li.nav-mega-heading + li.nav-mega-heading { - margin-top: var(--space-m); - padding-top: var(--space-m); - border-top: 1px solid light-dark(var(--color-gray-200), var(--color-gray-600)); - } } -header nav .nav-sections .default-content-wrapper > ul > li > ul a { +header nav .nav-sections .nav-drop > ul > li > a { display: block; - padding: var(--space-xs) 0; - font-size: inherit; - font-weight: inherit; + padding: 10px 12px; + font-size: 16px; + font-weight: 500; + color: #000; text-decoration: none; - color: inherit; + border-radius: 6px; } -header nav .nav-sections .default-content-wrapper > ul > li > ul a:hover, -header nav .nav-sections .default-content-wrapper > ul > li > ul a:focus-visible { - color: var(--nav-hover-color); +header nav .nav-sections .nav-drop > ul > li > a:hover { + color: var(--brand-red); + background: var(--light-color, #f5f5f5); } -header nav .nav-sections a.button:any-link, -header nav .nav-sections button.button { - border: 0; - border-radius: 0; - background: transparent; - color: var(--text-color); - padding: 0; - min-width: 0; - font-weight: var(--nav-primary-weight); - text-transform: uppercase; - letter-spacing: 1.5px; +header nav .nav-sections .nav-drop > ul > li > p { + margin: 0 0 4px; + padding: 0 12px 8px; + font-size: 13px; + font-weight: 400; + line-height: 1.4; + color: var(--brand-muted, #666); } -header nav .nav-sections a.button:any-link:hover, -header nav .nav-sections a.button:any-link:focus-visible, -header nav .nav-sections button.button:hover, -header nav .nav-sections button.button:focus-visible { - background: transparent; - border: 0; - transform: none; - box-shadow: none; -} - -/* CTA button in header tools */ -header nav .nav-tools a.button:any-link:not(.nav-tool) { - border: 1.5px solid var(--color-rust); - border-radius: 4px; - background: transparent; - color: var(--color-rust); - padding: 12px 24px; - font-size: 0.75rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 1.5px; - transition: all 0.3s; -} +/* ---------- Tools ---------- */ -header nav .nav-tools a.button.nav-auth-link:any-link { - padding: 10px 14px; - min-height: 36px; - line-height: 1; - white-space: nowrap; -} - -header nav .nav-auth-link .nav-auth-info { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - margin-left: 8px; - border: 1px solid currentcolor; - border-radius: 50%; - font-size: 10px; - font-weight: 700; - line-height: 1; - text-transform: none; -} - -header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p > a { - display: block; - color: inherit; - text-decoration: none; -} - -header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p > a:hover, -header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p > a:focus-visible { - color: var(--nav-hover-color); -} - -header nav .nav-tools a.button:any-link:not(.nav-tool):hover { - background: var(--color-rust); - color: var(--color-light); - transform: none; - box-shadow: none; -} - -header nav .nav-sections .default-content-wrapper > ul > li > ul p.button-container { - margin: 0; -} - -header nav .nav-sections .default-content-wrapper > ul > li > ul a.button:any-link { - display: block; - padding: var(--space-xs) 0; -} - -/* tools – mobile */ header nav .nav-tools { grid-area: tools; - justify-self: end; display: flex; align-items: center; - gap: var(--nav-gap); - justify-content: flex-end; + gap: 20px; + padding: 0 24px; } -header nav .nav-tools p { - margin: 0; -} +header nav .nav-tools p { margin: 0; } -header nav .nav-tools .nav-search-form { - position: relative; +header nav .nav-tools .nav-search-link { display: inline-flex; - align-items: center; - margin: 0; -} - -header nav .nav-tools .nav-search-input { - width: 120px; - border: 1px solid light-dark(rgb(0 0 0 / 14%), rgb(255 255 255 / 20%)); - border-radius: 999px; - padding: var(--nav-gap) 38px var(--nav-gap) var(--nav-padding); - background-color: transparent; - color: inherit; - font: inherit; - font-size: var(--body-font-size-s); - line-height: 1.2; - transition: border-color 0.18s ease, box-shadow 0.18s ease; -} - -header nav .nav-tools .nav-search-input::placeholder { - color: inherit; - opacity: 0.6; + width: 24px; + height: 24px; + color: #000; + background: currentcolor; + mask: url('data:image/svg+xml;utf8,') center / contain no-repeat; } -header nav .nav-tools .nav-search-input:focus-visible { - outline: none; - border-color: var(--nav-focus-color); - box-shadow: 0 0 0 2px light-dark(rgb(0 0 0 / 8%), rgb(255 255 255 / 12%)); +header nav .nav-tools .nav-search-link:hover { + color: var(--brand-red); } -header nav .nav-tools .nav-tool { +header nav .nav-tools .nav-cta { display: inline-flex; align-items: center; - justify-content: center; - cursor: pointer; - border: 0; - border-radius: 999px; - background: transparent; - padding: var(--nav-gap); - color: inherit; - transition: background-color 0.18s ease; -} - -header nav .nav-tools .nav-search-submit { - position: absolute; - top: 50%; - right: 6px; - transform: translateY(-50%); - flex: 0 0 auto; - padding: var(--space-xs); - border-radius: 999px; -} - -header nav .nav-tools .nav-tool:hover, -header nav .nav-tools .nav-tool:focus-visible, -header nav .nav-tools a.button.nav-tool:any-link:hover, -header nav .nav-tools a.button.nav-tool:any-link:focus-visible { - background-color: var(--nav-bg-hover); - opacity: 1; + padding: 12px 22px; + font-size: 15px; + font-weight: 600; + color: #fff; + background: var(--brand-red); + border-radius: 9999px; text-decoration: none; + white-space: nowrap; } -header nav .nav-tools a.button.nav-tool:any-link { - border: 0; - background-color: transparent; - color: inherit; - padding: var(--nav-gap); - min-width: 0; -} - -header nav .nav-tools .nav-tool:focus-visible { - outline: 2px solid currentcolor; - outline-offset: 2px; +header nav .nav-tools .nav-cta:hover { + background: var(--brand-red-dark); } -header nav .nav-tools .icon img { - width: 18px; - height: 18px; -} +/* ---------- Hamburger (hidden on desktop) ---------- */ -:where(body.dark-scheme, body.dark, body.theme-dark) header nav .nav-tools .icon img { - filter: invert(1) brightness(1.2); -} - -header nav .nav-tools .nav-language-wrap { - position: relative; - display: inline-flex; -} - -header nav .nav-tools .nav-language-menu { - list-style: none; - margin: 0; - padding: var(--nav-gap) 0; - position: absolute; - top: calc(100% + var(--nav-dropdown-offset)); - right: 0; - left: auto; - min-width: 160px; - max-width: min(200px, calc(100vw - 2 * var(--space-m))); - background-color: var(--nav-bg-dropdown); - border: 1px solid var(--header-drop-border); - border-radius: var(--nav-radius); - box-shadow: var(--header-drop-shadow); - z-index: 1001; -} - -header nav .nav-tools .nav-language-menu[hidden] { +header nav .nav-hamburger { display: none; + grid-area: brand; } -header nav .nav-tools .nav-language-menu li { - margin: 0; - padding: 0; -} - -header nav .nav-tools .nav-language-menu a { - display: block; - padding: var(--nav-dropdown-item-padding); - text-decoration: none; - transition: background-color 0.18s ease; -} - -header nav .nav-tools .nav-language-menu a:hover, -header nav .nav-tools .nav-language-menu a:focus-visible { - background-color: var(--nav-bg-hover-item); - text-decoration: none; -} - -/* tablet: 600px */ -@media (width >= 600px) { - header nav { - padding: 0 var(--space-l); - gap: 0 var(--space-l); - } - - header { - --nav-padding: var(--space-l); - } - - header nav .nav-tools .nav-search-input { - width: 160px; - } -} - -/* small phones: prevent header row clipping */ -@media (width <= 430px) { - header { - --nav-padding: var(--space-s); - --nav-gap: 6px; - } - - header nav { - gap: 0 var(--nav-gap); - } - - header nav .nav-brand :is(img, svg) { - width: min(138px, 37vw); - height: 36px; - } - - header nav .nav-brand .icon.icon-logo { - width: 100px; - height: 40px; - } - - header nav .nav-tools { - gap: 4px; - min-width: 0; - } - - header nav .nav-tools .nav-search-input { - width: min(98px, 26vw); - padding: var(--space-xs) 32px var(--space-xs) var(--space-s); - font-size: var(--body-font-size-xs); - } - - header nav .nav-tools .nav-tool, - header nav .nav-tools a.button.nav-tool:any-link { - padding: var(--space-xs); - } - - header nav .nav-tools .icon img { - width: 16px; - height: 16px; - } - - header nav .nav-tools a.button.nav-auth-link:any-link { - padding: 8px 10px; - font-size: var(--body-font-size-xs); - letter-spacing: 1px; - } - } - - /* very small phones */ - @media (width <= 350px) { - header { - --nav-padding: var(--space-s); - --nav-gap: var(--space-xs); - } - - header nav .nav-brand :is(img, svg) { - width: min(120px, 40vw); - height: 38px; - } - - header nav .nav-tools .nav-search-input { - width: 82px; - padding: var(--space-xs) 34px var(--space-xs) var(--space-s); - } - } - -/* desktop: 900px */ -@media (width >= 900px) { - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop > p > .nav-submenu-toggle { - display: none; - } +/* ---------- Mobile ---------- */ +@media (width < 900px) { header nav { - display: flex; - justify-content: space-between; - align-items: center; - gap: 0 var(--space-xl); - max-width: var(--nav-desktop-max-width); - margin: 0 auto; - padding: 0 var(--space-xl); - min-height: var(--nav-height); - } - - header { - --nav-padding: var(--space-xl); + grid-template-areas: + 'announcement announcement announcement' + 'hamburger brand tools' + 'sections sections sections'; + grid-template-columns: auto 1fr auto; + row-gap: 0; } - /* brand: left, no shrink */ - header .nav-brand { - flex: 0 0 auto; - flex-basis: 150px; - } - - /* sections: nav items flow from logo, horizontal nav with dropdowns */ - header nav .nav-sections { - flex: 1 1 auto; + header nav .nav-hamburger { display: flex; - justify-content: flex-start; + grid-area: hamburger; align-items: center; - visibility: visible; - padding: 0; - white-space: nowrap; + padding-left: 12px; } - header nav .nav-sections .default-content-wrapper { - width: 100%; - } - - header nav .nav-sections .default-content-wrapper > ul { - justify-content: flex-start; + header nav .nav-hamburger button { display: flex; - gap: var(--space-l); - margin: 0; - } - - /* tools: right, no shrink */ - header nav .nav-tools { - flex: 0 0 auto; - } - - header nav[aria-expanded='true'] { - min-height: 0; - overflow: visible; - } - - header nav .nav-hamburger { - display: none; - visibility: hidden; - } - - header nav[aria-expanded='true'] .nav-sections { - align-self: unset; - } - - header nav .nav-sections .default-content-wrapper > ul > li { - position: relative; - flex: 0 1 auto; - display: inline-flex; align-items: center; - border-bottom: 0; - } - - header nav .nav-sections .default-content-wrapper > ul > li > p { - margin: 0; - display: inline-flex; - align-items: center; - min-height: var(--nav-height); + justify-content: center; + width: 40px; + height: 40px; padding: 0; - font-size: var(--body-font-size-l); - font-weight: var(--nav-primary-weight); - letter-spacing: 0.01em; - } - - header nav .nav-sections .default-content-wrapper > ul > li > p > a { - padding: var(--space-xs) var(--space-s); - border-radius: var(--nav-radius); - line-height: 1.2; - transition: color 0.18s ease, background-color 0.18s ease; - } - - header nav .nav-sections .default-content-wrapper > ul > li > p > a:hover, - header nav .nav-sections .default-content-wrapper > ul > li > p > a:focus-visible { - color: var(--nav-hover-color); - background-color: var(--nav-bg-link-hover); - } - - header nav .nav-sections .default-content-wrapper > ul > li > p > a:focus-visible { - outline: 2px solid var(--nav-focus-color); - outline-offset: 2px; - } - - header nav .nav-sections .default-content-wrapper > ul > li:not(.nav-drop) a:hover, - header nav .nav-sections .default-content-wrapper > ul > li:not(.nav-drop) a:focus-visible { - color: var(--nav-hover-color); - text-decoration: underline; - text-underline-offset: 0.35em; - text-decoration-thickness: 2px; - } - - header nav .nav-sections .nav-drop { - padding-right: var(--nav-item-padding); + border: 0; + background: transparent; cursor: pointer; } - header nav .nav-sections .nav-drop:focus-within > p > a, - header nav .nav-sections .nav-drop:hover > p > a { - background-color: var(--nav-bg-link-hover); - color: var(--nav-hover-color); - } - - header nav .nav-sections .nav-drop::after { - content: ''; - display: inline-block; - position: absolute; - top: 50%; - right: var(--space-xs); - transform: translateY(-50%) rotate(135deg); - width: 5px; - height: 5px; - border: 2px solid currentcolor; - border-radius: 0 1px 0 0; - border-width: 2px 2px 0 0; - transition: transform 0.2s ease; - opacity: 0.7; - } - - header nav .nav-sections .nav-drop:hover::after, - header nav .nav-sections .nav-drop[aria-expanded='true']::after { - transform: translateY(-50%) rotate(-45deg); - opacity: 1; - } - - /* regular dropdown – desktop */ - header nav .nav-sections .default-content-wrapper > ul > li:not(.nav-drop-mega) > ul { - display: block; - position: absolute; - top: calc(100% + var(--nav-dropdown-offset)); - left: 50%; - transform: translate(-50%, -4px); - min-width: var(--nav-desktop-dropdown-min); - width: max-content; - max-width: var(--nav-desktop-dropdown-max); - padding: var(--nav-gap); - border-radius: var(--nav-radius); - border: 1px solid var(--header-drop-border); - background-color: var(--nav-bg-dropdown); - box-shadow: var(--header-drop-shadow); - white-space: initial; - visibility: hidden; - opacity: 0; - pointer-events: none; - z-index: 8; - transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s linear 0.2s; - } - - header nav .nav-sections .default-content-wrapper > ul > li:not(.nav-drop-mega)[aria-expanded='true'] > ul { - visibility: visible; - opacity: 1; - transform: translate(-50%, var(--space-xs)); - pointer-events: auto; - transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s; - } - - header nav .nav-sections .default-content-wrapper > ul > li:not(.nav-drop-mega) > ul::before { - content: ''; - position: absolute; - top: calc(-1 * var(--nav-desktop-arrow-size)); - left: 50%; - transform: translateX(-50%); - width: 0; - height: 0; - border-left: var(--nav-desktop-arrow-size) solid transparent; - border-right: var(--nav-desktop-arrow-size) solid transparent; - border-bottom: var(--nav-desktop-arrow-size) solid var(--nav-bg-dropdown); - } - - /* invisible hover bridge above regular dropdown to prevent close on gap traversal */ - header nav .nav-sections .default-content-wrapper > ul > li:not(.nav-drop-mega) > ul::after { + header nav .nav-hamburger-icon, + header nav .nav-hamburger-icon::before, + header nav .nav-hamburger-icon::after { content: ''; - position: absolute; - bottom: 100%; - left: 0; - right: 0; - height: calc(var(--nav-dropdown-offset) + var(--space-m)); - } - - header nav .nav-sections .default-content-wrapper > ul > li:not(.nav-drop-mega) > ul > li { - padding: 0; - border: 0; - } - - header nav .nav-sections .default-content-wrapper > ul > li:not(.nav-drop-mega) > ul > li + li { - border-top: 1px solid var(--header-drop-border); - } - - header nav .nav-sections .default-content-wrapper > ul > li:not(.nav-drop-mega) > ul a { display: block; - padding: var(--nav-dropdown-item-padding); - border-radius: var(--nav-radius); - text-decoration: none; - transition: background-color 0.18s ease, color 0.18s ease; - } - - header nav .nav-sections .default-content-wrapper > ul > li:not(.nav-drop-mega) > ul a:hover, - header nav .nav-sections .default-content-wrapper > ul > li:not(.nav-drop-mega) > ul a:focus-visible { - background-color: var(--nav-bg-hover-item); - color: var(--nav-hover-color); - text-decoration: none; - } - - header nav .nav-sections .default-content-wrapper > ul > li:not(.nav-drop-mega) > ul a:focus-visible { - outline: 2px solid var(--nav-focus-color); - outline-offset: -2px; - } - - /* mega menu – desktop: full-width panel, centered content */ - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul { - display: flex; - justify-content: center; - position: fixed; - left: 0; - right: 0; - top: var(--mega-top, var(--nav-height, 64px)); - padding: var(--space-xl) var(--space-xl); - border-bottom: var(--border-heritage); - background-color: light-dark(#faf8f5, var(--color-obsidian)); - color: var(--text-color); - box-shadow: var(--header-drop-shadow); - z-index: 999; - visibility: hidden; - opacity: 0; - pointer-events: none; - transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s linear 0.2s; - transform: translateY(-4px); - overflow: hidden; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega[aria-expanded='true'] > ul { - visibility: visible; - opacity: 1; - transform: translateY(0); - pointer-events: auto; - transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul::before { - content: ''; - position: absolute; - top: calc(-1 * var(--nav-desktop-arrow-size)); - left: var(--mega-pointer-x, 84px); - width: 0; - height: 0; - border-left: var(--nav-desktop-arrow-size) solid transparent; - border-right: var(--nav-desktop-arrow-size) solid transparent; - border-bottom: var(--nav-desktop-arrow-size) solid var(--nav-bg-mega); - } - - /* invisible hover bridge above mega dropdown to prevent close on gap traversal */ - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul::after { - content: ''; - position: absolute; - bottom: 100%; - left: 0; - right: 0; - height: var(--space-l); - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner { - display: grid; - grid-template-columns: repeat(var(--mega-columns, 2), minmax(var(--nav-desktop-mega-min-col), 1fr)); - align-items: start; - max-width: var(--nav-desktop-max-width); - width: 100%; - margin: 0 auto; - padding: 0; + width: 22px; + height: 2px; + background: #000; + position: relative; + transition: transform 0.3s ease, opacity 0.3s ease, background 0.3s ease; } - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li { - grid-column: var(--mega-group, 1); - grid-row: var(--mega-row, auto); - border: 0; - margin: 0; - padding: 0; - min-width: 0; - overflow-wrap: break-word; - } + header nav .nav-hamburger-icon::before { transform: translateY(-7px); } + header nav .nav-hamburger-icon::after { transform: translateY(5px); } - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-heading { - display: block; - margin-top: 0; - padding: 0 0 var(--nav-gap); - color: var(--dark-color); - font-size: var(--nav-heading-size); - font-weight: var(--nav-heading-weight); - line-height: 1.2; - } + header nav[aria-expanded='true'] .nav-hamburger-icon { background: transparent; } + header nav[aria-expanded='true'] .nav-hamburger-icon::before { transform: rotate(45deg); } + header nav[aria-expanded='true'] .nav-hamburger-icon::after { transform: rotate(-45deg) translateY(-4px) translateX(3px); } - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-heading > p { - margin: 0 0 var(--space-s); - padding: 0; + header nav .nav-brand { + justify-self: start; + padding: 0 12px; } - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-heading > ul { - list-style: none; - padding: 0; - margin: 0; + header nav .nav-brand img { + width: 84px; + height: 46px; } - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-heading > ul > li { - padding: 0; - margin: 0; + /* Tools (search + CTA) stay in the header bar, right-aligned. */ + header nav .nav-tools { + grid-area: tools; + gap: 12px; + padding: 0 12px; } - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul.nav-mega-has-groups > .nav-mega-inner > li.nav-mega-item { - padding-top: var(--space-xs); + header nav .nav-tools .nav-cta { + padding: 10px 16px; + font-size: 14px; } - /* mega items: direct links (e.g. Tools) get full padding */ - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-item > a { - display: block; - padding: var(--nav-dropdown-item-padding); - border-radius: var(--nav-radius); - color: var(--text-color); - font-size: var(--body-font-size-m); - line-height: 1.25; - font-weight: var(--nav-item-weight); - transition: background-color 0.18s ease, color 0.18s ease; - overflow-wrap: break-word; + /* Accordion drawer, hidden until open. */ + header nav .nav-sections { + grid-area: sections; + display: none; } - /* mega items under headings: align with heading, proper padding for hover */ - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-heading > ul a { + header nav[aria-expanded='true'] .nav-sections { display: block; - padding: var(--space-s) var(--space-s); - border-radius: var(--nav-radius); - color: var(--text-color); - font-size: var(--body-font-size-m); - font-weight: var(--nav-item-weight); - text-decoration: none; - transition: background-color 0.18s ease, color 0.18s ease; - overflow-wrap: break-word; - max-width: max-content; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-promo { - grid-column: var(--mega-group, 2); - grid-row: 1 / span 20; - align-self: start; - max-width: var(--nav-mega-promo-max-width); - min-width: 0; + border-top: 1px solid #ececec; } - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-promo a { - display: flex; + header nav .nav-sections > ul { flex-direction: column; - height: 100%; - min-height: 0; - max-height: var(--nav-mega-promo-max-height); - border-radius: var(--nav-radius); - background-color: var(--nav-bg-hover-item); - text-decoration: none; - overflow: hidden; - gap: var(--nav-gap); - padding: inherit; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-promo picture { - display: block; - border-radius: var(--nav-radius); - overflow: hidden; - flex: 1 1 auto; - min-height: 0; - min-width: 0; - max-height: calc(var(--nav-mega-promo-img-height) + var(--space-m)); - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-promo img { - display: block; + align-items: stretch; + gap: 0; width: 100%; - height: var(--nav-mega-promo-img-height); - max-width: 100%; - max-height: var(--nav-mega-promo-img-height); - object-fit: cover; - object-position: center; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-promo a:hover, - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-promo a:focus-visible { - background-color: var(--nav-bg-hover-item); - box-shadow: none; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-heading > ul a:hover, - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-heading > ul a:focus-visible { - color: var(--nav-hover-color); - background-color: var(--nav-bg-hover-item); - text-decoration: none; } - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-heading > ul a:focus-visible { - outline: 2px solid var(--nav-focus-color); - outline-offset: 2px; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-item > a:hover, - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-item > a:focus-visible { - background-color: var(--nav-bg-hover-item); - color: var(--nav-hover-color); - text-decoration: none; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-item > a:focus-visible { - outline: 2px solid var(--nav-focus-color); - outline-offset: 2px; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-drop-mega > ul > .nav-mega-inner > li.nav-mega-item + li.nav-mega-item { - border-top: 0; - } - - header nav .nav-tools .nav-search-input { - width: 190px; - } - - header nav .nav-tools a.button.nav-auth-link:any-link { - padding: 12px 18px; - } - - header nav .nav-tools .nav-search-input:hover { - border-color: light-dark(rgb(0 0 0 / 24%), rgb(255 255 255 / 30%)); + header nav .nav-sections > ul > li { + width: 100%; + border-bottom: 1px solid #ececec; } - header nav .nav-tools .nav-language-menu { - top: calc(100% + var(--nav-dropdown-offset)); + header nav .nav-sections > ul > li > a, + header nav .nav-sections > ul > li > .nav-drop-toggle { + width: 100%; + justify-content: space-between; + padding: 16px 20px; + font-size: 18px; } -} -@media (width <= 899px) { - header nav .nav-tools .nav-auth-desktop { + header nav .nav-sections .nav-drop > ul { + position: static; display: none; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item { - border-bottom: 0; - padding: var(--space-m) 0; - text-align: center; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p { - margin: 0; - padding: 0; - justify-content: center; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p > a.button.nav-auth-mobile:any-link { - display: inline-flex; - align-items: center; - justify-content: center; - border: 1.5px solid var(--color-rust); - border-radius: 4px; - background: transparent; - color: var(--color-rust); - padding: 12px 24px; - font-size: 0.75rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 1.5px; - line-height: 1; - min-width: 120px; - text-decoration: none; - } - - header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p > a.button.nav-auth-mobile:any-link:hover, - header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item > p > a.button.nav-auth-mobile:any-link:focus-visible { - background: var(--color-rust); - color: var(--color-light); - transform: none; + width: 100%; + min-width: 0; + border: 0; + border-radius: 0; box-shadow: none; - text-decoration: none; + padding: 0 0 8px 20px; } -} -@media (width >= 900px) { - header nav .nav-sections .default-content-wrapper > ul > li.nav-auth-mobile-item { - display: none; - } -} - -/* large screens: 1200px */ -@media (width >= 1200px) { - header nav { - max-width: 1264px; + header nav .nav-sections .nav-drop[aria-expanded='true'] > ul { + display: block; } } diff --git a/blocks/header/header.js b/blocks/header/header.js index bd51f82..5cb5478 100644 --- a/blocks/header/header.js +++ b/blocks/header/header.js @@ -1,534 +1,190 @@ /* - * Header block – simple, maintainable nav - * Nav fragment: plain.html via loadFragment (brand, sections, tools) - * Sections: ul > li; add #mega to link for full-width dropdown + * Header block — content-driven navigation. + * Nav fragment (content/nav.plain.html) provides four sections in order: + * 1. Announcement carousel —