From 17b63060682be0f6dc553dc3f5b2bce16fcf5c22 Mon Sep 17 00:00:00 2001 From: Sam Shao Date: Thu, 13 Aug 2026 11:10:23 -0700 Subject: [PATCH 01/36] Match progressrail.com homepage more closely Compared the local build against the live source page and fixed the gaps found: - header: fix hero overlap (header { height: var(--nav-height) } didn't account for the two-row desktop layout); add search box and EN/FR language switcher, replacing the header's misplaced "Contact Us" link (the source only has that in the jump-nav CTA) - scripts.js: decorateButtons now always emits the brand's single a.button.cta style instead of the unused primary/secondary/accent variants - content: bold-wrap the CTA links that should render as buttons (hero, jump-nav, teaser, columns) so decorateButtons picks them up - pr-jump-nav: real sticky-on-scroll (position: sticky doesn't hold once its own short section scrolls past, so this uses an IntersectionObserver sentinel + spacer instead) and a scroll-position-based active-link underline - pr-teaser: source renders this as a two-column split (dark text panel + image column), not a full-bleed background image like the hero; restructured accordingly - pr-cards: split the trailing "Learn More" link into its own footer with a divider + chevron, matching the source - styles.css: add the yellow underline rule under section headings Co-Authored-By: Claude Sonnet 5 --- blocks/header/header.css | 127 ++++++++++++++++++++++++----- blocks/header/header.js | 30 ++++++- blocks/pr-cards/pr-cards.css | 32 ++++++++ blocks/pr-cards/pr-cards.js | 16 ++++ blocks/pr-jump-nav/pr-jump-nav.css | 31 ++++++- blocks/pr-jump-nav/pr-jump-nav.js | 78 ++++++++++++++++++ blocks/pr-teaser/pr-teaser.css | 70 ++++++++++------ blocks/pr-teaser/pr-teaser.js | 18 +++- content/en.plain.html | 6 +- icons/globe.svg | 3 + scripts/scripts.js | 20 ++--- styles/styles.css | 21 +++++ tools/nav-footer-source/nav.html | 7 +- 13 files changed, 395 insertions(+), 64 deletions(-) create mode 100644 icons/globe.svg diff --git a/blocks/header/header.css b/blocks/header/header.css index fe5b2cc..f8e6824 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -11,7 +11,7 @@ header nav { box-sizing: border-box; display: grid; grid-template: - 'hamburger brand tools' var(--nav-height) + 'hamburger brand language' var(--nav-height) 'sections sections sections' 1fr / auto 1fr auto; align-items: center; gap: 0 24px; @@ -26,11 +26,17 @@ header nav[aria-expanded='true'] { grid-template: 'hamburger brand' var(--nav-height) 'sections sections' 1fr - 'tools tools' var(--nav-height) / auto 1fr; + 'language language' var(--nav-height) / auto 1fr; overflow-y: auto; min-height: 100dvh; } +/* search is desktop-only (no room for it in the mobile hamburger layout) */ +header nav .nav-search { + display: none; + visibility: hidden; +} + @media (width >= 900px) { header .nav-wrapper { position: relative; @@ -268,9 +274,34 @@ header nav .nav-sections ul > li > ul > li { } } -/* tools */ -header nav .nav-tools { - grid-area: tools; +/* language switcher */ +header nav .nav-language { + grid-area: language; +} + +/* stylelint-disable-next-line no-descending-specificity */ +header nav .nav-language ul { + list-style: none; + display: flex; + align-items: center; + gap: 8px; + margin: 0; + padding: 0; + font-size: 13px; +} + +header nav .nav-language li + li::before { + content: '|'; + margin-right: 8px; + color: currentcolor; + opacity: 0.5; +} + +header nav .nav-language .icon { + height: 14px; + width: 14px; + margin-right: 4px; + vertical-align: -2px; } /* ===== Progress Rail header branding — black bar, white uppercase nav ===== */ @@ -299,19 +330,59 @@ header nav .nav-sections a:any-link:hover { color: var(--brand-yellow, #ffcd11); } -/* the trailing "Contact Us" tools link → yellow CTA-ish, uppercase */ +/* language switcher links: white, current language marked yellow */ /* stylelint-disable-next-line no-descending-specificity */ -header nav .nav-tools a:any-link { +header nav .nav-language a:any-link { + color: #fff; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.3px; +} + +header nav .nav-language a[aria-current='true'] { color: var(--brand-yellow, #ffcd11); - font-family: var(--heading-font-family); - font-weight: 700; +} + +header nav .nav-language a:any-link:hover { + color: var(--brand-yellow, #ffcd11); +} + +/* search box */ +header nav .nav-search form { + display: flex; + align-items: stretch; + height: 40px; +} + +header nav .nav-search input[type='search'] { + box-sizing: border-box; + min-width: 0; + width: 100%; + border: 0; + padding: 0 12px; font-size: 14px; - text-transform: uppercase; - text-decoration: none; + color: var(--text-color); + background-color: #fff; + appearance: none; } -header nav .nav-tools a:any-link:hover { - color: #fff; +header nav .nav-search button { + flex: none; + width: 40px; + border: 0; + border-radius: 0; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--brand-yellow, #ffcd11); + color: var(--brand-dark, #131313); + cursor: pointer; +} + +header nav .nav-search .icon { + height: 18px; + width: 18px; } /* desktop dropdown panels: keep readable (dark text on white panel) */ @@ -333,13 +404,22 @@ header nav .nav-tools a:any-link:hover { /* ===== Progress Rail two-row header (logo row + nav row) ===== */ @media (width >= 900px) { - /* Two rows: logo on top, nav sections + Contact Us below — both on the - black bar. cols: sections take remaining space, tools hug the right. */ + /* The boilerplate's global `header { height: var(--nav-height) }` assumes a + single nav row. This header renders two rows on desktop, so let it size + to its actual content — otherwise the nav overflows the header's box and + overlaps whatever section comes right after it. */ + header { + height: auto; + } + + /* Two rows: logo + search + language on top, nav sections below — all on + the black bar. Row 1 cols: brand fixed, search flexible, language hugs + the right; row 2 (sections) spans the full width. */ header nav { display: grid; grid-template: - 'brand brand' auto - 'sections tools' auto / 1fr auto; + 'brand search language' auto + 'sections sections sections' auto / auto 1fr auto; align-items: center; justify-content: unset; gap: 12px 32px; @@ -351,13 +431,22 @@ header nav .nav-tools a:any-link:hover { grid-area: brand; } + header nav .nav-search { + grid-area: search; + display: block; + visibility: visible; + justify-self: center; + width: 100%; + max-width: 420px; + } + header nav .nav-sections { grid-area: sections; align-self: center; } - header nav .nav-tools { - grid-area: tools; + header nav .nav-language { + grid-area: language; justify-self: end; } diff --git a/blocks/header/header.js b/blocks/header/header.js index c7d6ea2..53839dc 100644 --- a/blocks/header/header.js +++ b/blocks/header/header.js @@ -1,4 +1,4 @@ -import { getMetadata } from '../../scripts/aem.js'; +import { getMetadata, decorateIcons } from '../../scripts/aem.js'; import { loadFragment } from '../fragment/fragment.js'; // media query match that indicates mobile/tablet width @@ -124,7 +124,7 @@ export default async function decorate(block) { nav.id = 'nav'; while (fragment.firstElementChild) nav.append(fragment.firstElementChild); - const classes = ['brand', 'sections', 'tools']; + const classes = ['brand', 'sections', 'language']; classes.forEach((c, i) => { const section = nav.children[i]; if (section) section.classList.add(`nav-${c}`); @@ -137,6 +137,32 @@ export default async function decorate(block) { brandLink.closest('.button-container').className = ''; } + // language switcher: mark the current language, add a globe icon + const navLanguage = nav.querySelector('.nav-language'); + if (navLanguage) { + const current = navLanguage.querySelector(`a[href="/${document.documentElement.lang || 'en'}/"]`) + || navLanguage.querySelector('a'); + if (current) current.setAttribute('aria-current', 'true'); + const firstLink = navLanguage.querySelector('a'); + if (firstLink) { + const globe = document.createElement('span'); + globe.className = 'icon icon-globe'; + firstLink.prepend(globe); + decorateIcons(navLanguage); + } + } + + // search box (UI only — this project has no search backend to submit to) + const navSearch = document.createElement('div'); + navSearch.className = 'nav-search'; + navSearch.innerHTML = `
+ + +
`; + navSearch.querySelector('form').addEventListener('submit', (e) => e.preventDefault()); + decorateIcons(navSearch); + navBrand.after(navSearch); + const navSections = nav.querySelector('.nav-sections'); if (navSections) { navSections.querySelectorAll(':scope .default-content-wrapper > ul > li').forEach((navSection) => { diff --git a/blocks/pr-cards/pr-cards.css b/blocks/pr-cards/pr-cards.css index d5b5301..49f38ab 100644 --- a/blocks/pr-cards/pr-cards.css +++ b/blocks/pr-cards/pr-cards.css @@ -16,6 +16,38 @@ margin: 16px; } +.pr-cards .pr-cards-card-footer { + margin: 0 16px 16px; + border-top: 1px solid #dadada; +} + +.pr-cards .pr-cards-card-footer p { + margin: 0; +} + +.pr-cards .pr-cards-card-footer a { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 12px 0; + font-family: var(--heading-font-family); + font-size: var(--body-font-size-xs); + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.3px; +} + +.pr-cards .pr-cards-card-footer a::after { + content: ''; + flex: none; + width: 8px; + height: 8px; + border: solid currentcolor; + border-width: 0 2px 2px 0; + transform: rotate(-45deg); +} + .pr-cards .pr-cards-card-image { line-height: 0; } diff --git a/blocks/pr-cards/pr-cards.js b/blocks/pr-cards/pr-cards.js index 05dd05a..d2229e0 100644 --- a/blocks/pr-cards/pr-cards.js +++ b/blocks/pr-cards/pr-cards.js @@ -12,6 +12,22 @@ export default function decorate(block) { if (div.children.length === 1 && div.querySelector('picture')) div.className = 'pr-cards-card-image'; else div.className = 'pr-cards-card-body'; }); + + // split a trailing link-only paragraph (e.g. "Learn More") into its own + // footer, so it gets a divider + chevron instead of running into the body copy + const body = li.querySelector('.pr-cards-card-body'); + const lastP = body && body.lastElementChild; + const lastLink = lastP && lastP.tagName === 'P' && lastP.querySelector('a'); + if (lastLink && lastP.textContent.trim() === lastLink.textContent.trim()) { + // a plain
, not
— the page's
landmark styling + // (blocks/footer/footer.css) targets the bare footer tag and would + // otherwise bleed into every card + const footer = document.createElement('div'); + footer.className = 'pr-cards-card-footer'; + footer.append(lastP); + li.append(footer); + } + ul.append(li); }); ul.querySelectorAll('picture > img').forEach((img) => { diff --git a/blocks/pr-jump-nav/pr-jump-nav.css b/blocks/pr-jump-nav/pr-jump-nav.css index 3001f23..c750795 100644 --- a/blocks/pr-jump-nav/pr-jump-nav.css +++ b/blocks/pr-jump-nav/pr-jump-nav.css @@ -1,5 +1,20 @@ .pr-jump-nav { background-color: var(--background-color); + border-bottom: 1px solid var(--light-color); +} + +/* `position: sticky` only holds an element within the height of its own + containing block — here that's just this one small section, so a plain + sticky rule would let go the moment that section scrolls past. The block + JS toggles `.is-stuck` (backed by an IntersectionObserver sentinel) to + fix it to the viewport for the rest of the page instead, using a spacer + to hold its place in the flow so nothing jumps. */ +.pr-jump-nav.is-stuck { + position: fixed; + top: var(--nav-height); + left: 0; + right: 0; + z-index: 1; } .pr-jump-nav nav { @@ -26,15 +41,23 @@ } .pr-jump-nav nav ul li a { + display: inline-block; text-transform: uppercase; text-decoration: none; font-weight: 700; letter-spacing: 0.02em; color: var(--text-color); + padding-bottom: 6px; + border-bottom: 3px solid transparent; } .pr-jump-nav nav ul li a:hover { - text-decoration: underline; + color: var(--link-hover-color); + text-decoration: none; +} + +.pr-jump-nav nav ul li a.active { + border-bottom-color: var(--brand-yellow); } .pr-jump-nav nav .button-wrapper { @@ -42,6 +65,12 @@ } @media (width >= 900px) { + .pr-jump-nav.is-stuck { + /* header is in normal flow (not fixed) at this width, so the strip can + stick right at the viewport top once it scrolls under the header */ + top: 0; + } + .pr-jump-nav nav { padding: 12px 32px; flex-wrap: nowrap; diff --git a/blocks/pr-jump-nav/pr-jump-nav.js b/blocks/pr-jump-nav/pr-jump-nav.js index 40a3fa4..25baa6a 100644 --- a/blocks/pr-jump-nav/pr-jump-nav.js +++ b/blocks/pr-jump-nav/pr-jump-nav.js @@ -6,7 +6,82 @@ * (the jump/anchor links) followed optionally by a final link that acts as * the call-to-action (e.g. "Contact Us"). The CTA is detected as a link * already styled as a button (`a.button`) or, failing that, the last link. + * + * Once built, the strip sticks to the top of the viewport on scroll and + * underlines whichever jump link's target section is currently in view, + * matching the source site's behavior. + */ + +/** + * Fixes the block to the top of the viewport once its natural position + * scrolls past it, using an IntersectionObserver sentinel left behind at + * its original spot (`position: sticky` can't do this here — see the CSS + * comment). A spacer holds the block's height in the flow while it's + * fixed, so removing it doesn't jump the rest of the page up. + * @param {Element} block The pr-jump-nav block element + */ +function setupSticky(block) { + const spacer = document.createElement('div'); + const sentinel = document.createElement('div'); + sentinel.setAttribute('aria-hidden', 'true'); + block.before(sentinel, spacer); + + const observer = new IntersectionObserver(([entry]) => { + const stuck = entry.boundingClientRect.top < 0; + block.classList.toggle('is-stuck', stuck); + spacer.style.height = stuck ? `${block.getBoundingClientRect().height}px` : '0'; + }, { threshold: 0 }); + observer.observe(sentinel); +} + +/** + * Highlights the nav link whose target section is currently in view. + * Tracked by scroll position rather than a narrow IntersectionObserver band, + * since a fast scroll (flick, Page Down) can jump straight past a narrow + * band between two animation frames and never register the crossing. + * @param {Element} nav The
From 2dc25d5730baf097266a33aa96e03e0661258fde Mon Sep 17 00:00:00 2001 From: Sam Shao Date: Thu, 13 Aug 2026 12:59:19 -0700 Subject: [PATCH 07/36] header: fix hover underline and add missing background on nav items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The global a:hover { text-decoration: underline } default was bleeding through on nav-sections and language links since neither hover rule overrode it. Source has no underline on hover anywhere in the header. Also matched the source's #555 gray background on nav-item hover — it wasn't just a text color change, it highlights the whole padded item. Co-Authored-By: Claude Sonnet 5 --- blocks/header/header.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/blocks/header/header.css b/blocks/header/header.css index b5d11dd..24d39d7 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -286,6 +286,10 @@ header nav .nav-sections ul > li > ul > li { header nav .nav-sections .default-content-wrapper > ul > li > ul > li { padding: 8px 0; } + + header nav .nav-sections .default-content-wrapper > ul > li:hover { + background-color: #555; + } } /* language switcher */ @@ -334,6 +338,7 @@ header nav .nav-sections .default-content-wrapper > ul > li { header nav .nav-sections a:any-link:hover { color: var(--brand-yellow, #ffcd11); + text-decoration: none; } /* language switcher links: plain white for both languages, same body @@ -348,6 +353,10 @@ header nav .nav-language a:any-link { text-transform: uppercase; } +header nav .nav-language a:any-link:hover { + text-decoration: none; +} + /* search box */ header nav .nav-search form { display: flex; From 8274c72477d7893c8c16037513816df70bb92ea3 Mon Sep 17 00:00:00 2001 From: Sam Shao Date: Thu, 13 Aug 2026 13:04:41 -0700 Subject: [PATCH 08/36] header: use the source's actual chevron path (mark was reading too small) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The icon box was already 14x14 to match, but my stroke-based caret (M4 6l4 4 4-4, stroke-width 2) only filled about half of that box — the source's chevron is a filled path that spans nearly the full viewBox. Copied its exact path data so the visual weight matches, not just the container size. Co-Authored-By: Claude Sonnet 5 --- blocks/header/header.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/blocks/header/header.js b/blocks/header/header.js index 13ce278..f1c57c9 100644 --- a/blocks/header/header.js +++ b/blocks/header/header.js @@ -171,13 +171,15 @@ export default async function decorate(block) { // chevron as a real icon next to the label (matching the source), // not a CSS-drawn triangle crammed against the text. Inlined // directly (rather than through decorateIcons' ) so its - // `currentColor` stroke tracks the label's hover color, the way - // the source's own inline chevron does. + // `currentColor` fill tracks the label's hover color, the way + // the source's own inline chevron does. Path data copied from the + // source's own chevron icon — a thin stroke-based caret reads + // noticeably smaller/thinner than this within the same box. const label = navSection.querySelector('a') || navSection; const chevron = document.createElement('span'); chevron.className = 'icon icon-chevron'; chevron.innerHTML = ''; + + ''; label.append(chevron); } navSection.addEventListener('click', () => { From 70cdfa4602ff652bd559e81040223137dee31493 Mon Sep 17 00:00:00 2001 From: Sam Shao Date: Thu, 13 Aug 2026 16:49:55 -0700 Subject: [PATCH 09/36] hero: fix heading size/weight/margin and CTA button styling to match source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured the source's h1 and .button.cta precisely and found several mismatches: - h1 (hero heading): 45px/600 -> 54px/700, and it had a 36px top margin from the shared h1-h6 rule stacking on top of the hero's own padding — zeroed that out (only one h1 exists sitewide, the hero's). - .button.cta: was inheriting font-size from the surrounding copy (14-22px depending on context) instead of a fixed 14px, was bold instead of regular weight, 2px square corners instead of 1px/4px rounded, and forced text-transform: uppercase instead of capitalize — the latter was visibly wrong for authored text like "Read More", which should keep its casing rather than becoming "READ MORE". Verified against all 7 cta buttons on the source page, not just the hero one — the values are consistent site-wide. Co-Authored-By: Claude Sonnet 5 --- styles/styles.css | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/styles/styles.css b/styles/styles.css index 484633a..39aaf50 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -66,7 +66,7 @@ --body-font-size-xs: 14px; /* heading sizes */ - --heading-font-size-xxl: 45px; + --heading-font-size-xxl: 54px; --heading-font-size-xl: 36px; --heading-font-size-l: 28px; --heading-font-size-m: 22px; @@ -117,7 +117,17 @@ h6 { scroll-margin: 40px; } -h1 { font-size: var(--heading-font-size-xxl); } +h1 { + font-size: var(--heading-font-size-xxl); + + /* the only h1 on the site is the hero heading — source uses a bolder + weight than the shared h1-h6 rule and no top margin (the hero's own + padding already provides the offset) */ + font-weight: 700; + margin-top: 0; + margin-bottom: 0.5em; +} + h2 { font-size: var(--heading-font-size-xl); } h3 { font-size: var(--heading-font-size-l); } h4 { font-size: var(--heading-font-size-m); } @@ -350,19 +360,23 @@ main .section.highlight { padding: 40px 0; } -/* Progress Rail CTA button — Caterpillar yellow, square, condensed uppercase. - The source renders in-content CTAs as a.button.cta. */ +/* Progress Rail CTA button — Caterpillar yellow. The source renders + in-content CTAs as a.button.cta: a slightly-rounded pill, 14px regular + weight (its own size, not inherited from the surrounding copy), and + `capitalize` rather than a forced uppercase — authored text like + "Read More" keeps its own casing instead of becoming "READ MORE". */ a.button.cta:any-link, button.button.cta { - border: 2px solid var(--brand-yellow); - border-radius: 0; + border: 1px solid var(--brand-yellow); + border-radius: 4px; background-color: var(--brand-yellow); color: var(--brand-dark); font-family: var(--heading-font-family); - font-weight: 700; - text-transform: uppercase; + font-size: 14px; + font-weight: 400; + text-transform: capitalize; letter-spacing: 0.3px; - padding: 0.6em 1.4em; + padding: 0.5em 1.2em; } a.button.cta:hover, From 05a1c849ab8ec821bfacfff4c7bc704c24a8a1e3 Mon Sep 17 00:00:00 2001 From: Sam Shao Date: Thu, 13 Aug 2026 16:56:28 -0700 Subject: [PATCH 10/36] pr-hero: vertically center content instead of top-padding it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The source vertically centers the hero's content within the media's full height (a ~24px-padded box centered via flex), not top-anchored with a fixed padding. Ours used a flat 40px top padding, which put the heading noticeably higher than the source at any height other than exactly (media height - content height - 40px*2). Marked the two rows (media/body) with distinct classes so the media can be a full-bleed absolute layer while .pr-hero itself is a flex container centering the body within it — mirrors the pr-teaser restructure from earlier in this branch. (CTA button padding was already fixed to match the source in the previous commit — verified again here, still 7px/16.8px.) Co-Authored-By: Claude Sonnet 5 --- blocks/pr-hero/pr-hero.css | 58 +++++++++++++++++++++++--------------- blocks/pr-hero/pr-hero.js | 17 +++++++---- 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/blocks/pr-hero/pr-hero.css b/blocks/pr-hero/pr-hero.css index c1b47d2..d8e5ce4 100644 --- a/blocks/pr-hero/pr-hero.css +++ b/blocks/pr-hero/pr-hero.css @@ -5,44 +5,58 @@ .pr-hero { position: relative; - padding: 40px 24px; + display: flex; + align-items: center; min-height: 300px; } -.pr-hero h1, -.pr-hero h2, -.pr-hero h3, -.pr-hero h4, -.pr-hero h5, -.pr-hero h6, -.pr-hero p { - max-width: 1200px; - margin-left: auto; - margin-right: auto; - color: var(--background-color); -} - -.pr-hero.no-image * { - color: var(--text-color); +.pr-hero-media { + position: absolute; + inset: 0; + z-index: -1; } -.pr-hero picture { +.pr-hero-media picture { position: absolute; - z-index: -1; inset: 0; object-fit: cover; box-sizing: border-box; } -.pr-hero img { +.pr-hero-media img { object-fit: cover; width: 100%; height: 100%; } +/* content is vertically centered in the media's height (not top-padded + by a fixed offset) — a ~24px box, not the whole hero's padding */ +.pr-hero-body { + box-sizing: border-box; + width: 100%; + padding: 24px; +} + +.pr-hero-body h1, +.pr-hero-body h2, +.pr-hero-body h3, +.pr-hero-body h4, +.pr-hero-body h5, +.pr-hero-body h6, +.pr-hero-body p { + max-width: 1200px; + margin-left: auto; + margin-right: auto; + color: var(--background-color); +} + +.pr-hero.no-image .pr-hero-body * { + color: var(--text-color); +} + @media (width >= 900px) { - .pr-hero { - padding: 40px 32px; + .pr-hero-body { + padding: 24px 32px; } } @@ -61,6 +75,6 @@ /* When a video is present, keep the poster picture as the underlying fallback layer (one step further back). */ -.pr-hero.has-video picture { +.pr-hero.has-video .pr-hero-media picture { z-index: -2; } diff --git a/blocks/pr-hero/pr-hero.js b/blocks/pr-hero/pr-hero.js index eeebf6e..6317c1a 100644 --- a/blocks/pr-hero/pr-hero.js +++ b/blocks/pr-hero/pr-hero.js @@ -7,24 +7,31 @@ * as a plain so the URL survives round-tripping. * Row 2 cell: content — heading + CTA, overlaid on the media. * + * The two rows are marked as distinct layers (media full-bleed behind, + * body centered on top) rather than left as plain divs, so the content + * can be vertically centered in the media's height instead of just + * top-padded — matching the source's layout. + * * decorate() upgrades a background-video link into an autoplaying, muted, * looping, inline
-

- Legal - Privacy - Caterpillar © 2026. All Rights Reserved. - Do Not Sell Or Share My Personal Information -

+ + +
+ + +
+
+ +
From a26c4220e1b4d193fe611c45532337c766da8f49 Mon Sep 17 00:00:00 2001 From: Sam Shao Date: Thu, 13 Aug 2026 21:49:45 -0700 Subject: [PATCH 32/36] footer: fix content-column width (1140px, not the boilerplate's 1200px) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified via getBoundingClientRect: the source's footer-nav content column is exactly 1140px wide, starting at the same left edge (x=230 at 1600px viewport) as every other section on the page. The footer's own container was still the boilerplate's unmodified 1200px default, so its left/right edges didn't line up with the header/hero/cards/etc. above it — the same class of bug as the earlier main-content 1200→1140 fix, just never applied to the footer's own (separate) container rule. --- blocks/footer/footer.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css index 52ef412..a4135e3 100644 --- a/blocks/footer/footer.css +++ b/blocks/footer/footer.css @@ -11,8 +11,13 @@ footer .footer { } footer .footer > div { + /* matches the rest of the page's content column (verified via + getBoundingClientRect: the source's footer-nav is 1140px wide, + starting at the same left edge as every other section) — this was + still the boilerplate's own 1200px default, misaligning the whole + footer against the page above it. */ margin: auto; - max-width: 1200px; + max-width: 1140px; padding: 32px 24px; } From 5a3c009377af079afd02ed3c848c9e11cb8fae85 Mon Sep 17 00:00:00 2001 From: Sam Shao Date: Thu, 13 Aug 2026 21:54:05 -0700 Subject: [PATCH 33/36] footer: top-align nav columns, right-align social icon row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two alignment bugs, both verified against the source via getComputedStyle: - The generic .columns block centers grid items vertically at >=900px (blocks/columns/columns.css's own desktop rule), so a shorter category (e.g. "Supply Chain", 3 links) had its heading pulled down to align with the vertical center of a taller neighbor (e.g. "Company", 5 links) instead of starting at the same top row. Source uses default top alignment. Added align-items: start to override. - The social icon row's