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 —