Skip to content

Match progressrail.com homepage more closely - #1

Merged
samshao merged 36 commits into
mainfrom
match-source-page-parity
Aug 14, 2026
Merged

Match progressrail.com homepage more closely#1
samshao merged 36 commits into
mainfrom
match-source-page-parity

Conversation

@samshao

@samshao samshao commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Pixel-parity pass against the live source (https://www.progressrail.com/en/), driven by iterative side-by-side comparison (getComputedStyle/getBoundingClientRect measurements against the source, not visual guessing) across many rounds of feedback.

Header / Hero

  • Fixed two-row desktop header collapsing to 3 stacked rows (mobile-only rule was leaking into desktop at equal specificity).
  • Added the header's search box and EN/FR language switcher (source has these, we didn't).
  • Fixed logo size, nav-link font/spacing/hover (background instead of underline), chevron icon (real path data, correct size), Careers dropdown.
  • Fixed header/hero exact pixel heights (128px / 350px) and the hero/jump-nav margin-collapse gap.
  • Added the dark gradient overlay both pr-hero and pr-teaser were missing entirely (verified via the source's own CSSOM).

Jump nav

  • Full rewrite: sticky-on-scroll (IntersectionObserver, since native position: sticky can't hold across sibling sections), scroll-position-based active-link underline, working anchor navigation (fixed a trailing-slash href bug), full-bleed bar with correct divider color.
  • Fixed two scrollspy bugs found via user-provided screenshots: an off-by-one (clicking a link activated the previous one), and — found via a screenshot showing "News & Events" content on screen while "Supply Chain" stayed underlined — a threshold that exceeded the gap between two adjacent section headings on tall viewports.

Content blocks

  • pr-cards: fonts, card height, "Learn More" chevron position (was flush against the card edge instead of hugging the label).
  • pr-teaser: was a hard-edged two-panel split; source is actually a full-bleed image with text overlaid on a gradient. Also fixed a flex-axis mixup that centered the panel horizontally instead of vertically.
  • pr-columns: fixed image-column width (flex:1 let text's min-content steal space from the image), height (image aspect ratio was inflating row height instead of a fixed 314px), and missing 64px text-panel padding.
  • pr-video: width was the boilerplate's 900px default; source uses the same 1070px --content-width as body copy.
  • Site-wide: content column was 1200px, source measures 1140px everywhere; section spacing was 40px, source uses 64px; body copy under a heading needs a 35px indent; block-to-block spacing within a multi-block section needed its own margin rule.

Footer

Full rebuild — the previous footer was built on an unverified assumption (dark/white theme, generic heading+list nav) that turned out to be wrong in almost every respect once actually measured:

  • Light gray background (#ccc, full viewport width) with dark nav text, not dark/white.
  • Each nav category is a native <details>/<summary> accordion (collapsible on mobile, static on desktop) — authored as plain <h3>/<ul> since DA's content pipeline strips <details>/<button> from block content, then built into real elements client-side.
  • A second, icon-only social row (source shows both a text column and an icon row).
  • The legal/copyright row is a centered <ul> with a genuine "Cookie Settings" control and a CSS-generated | separator, sitting on its own distinct white background (a full-bleed pseudo-element), not a paragraph of loose links on the same gray.
  • Grid layout with the source's own row gaps and column alignment.

Preview

https://match-source-page-parity--progressrail-eds--samshao.aem.page/en

Checks

  • npm run lint: clean.
  • Lighthouse (desktop): Performance 99, Accessibility 100, Best Practices 96, SEO 61.
    • The SEO score is dragged down by two things unrelated to this PR's changes: (1) noindex/robots.txt disallow, which is a platform-wide default for every *.aem.page/*.aem.live preview URL (confirmed present on main's own preview too) until a production custom domain is configured, and (2) generic "Learn More"/"Read More" link text, copied verbatim from the source site's own copy as part of this PR's content-fidelity goal.
    • The CI aem-psi-check's mobile run timed out against Google's PSI API; a local Lighthouse mobile run completed normally (Accessibility 95, mainly a footer-link touch-target-size nitpick inherited from matching the source's own tight link spacing).

🤖 Generated with Claude Code

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 <noreply@anthropic.com>
@aem-code-sync

aem-code-sync Bot commented Aug 13, 2026

Copy link
Copy Markdown

Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch and validate page speed.
In case there are problems, just click a checkbox below to rerun the respective action.

  • Re-run all PSI checks
  • Re-run failed PSI checks
  • Re-sync branch
Commits

@aem-code-sync

aem-code-sync Bot commented Aug 13, 2026

Copy link
Copy Markdown
Page Scores Audits Google
📱 /en Timeout Exceeded PSI
🖥️ /en PERFORMANCE A11Y SEO BEST PRACTICES SI FCP LCP TBT CLS PSI

samshao and others added 2 commits August 13, 2026 11:37
…h width

The mobile "hamburger open" full-screen grid-template
(header nav[aria-expanded='true']) wasn't scoped to mobile, and its
attribute-selector specificity beat the desktop two-row grid-template
regardless of viewport — aria-expanded is legitimately 'true' at desktop
widths too (toggleMenu() overloads it to also mean "no hamburger
needed"). That silently broke the desktop layout into brand/sections/
language stacked as separate rows instead of the intended two-row
logo+search+language / nav-links layout.

Also matches the source more closely: nav links use their authored
title-case text (not forced uppercase+bold), and the search input
stretches to fill the row instead of being capped at 420px.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Measured the source header precisely: row 1 is logo + search only: the
language switcher actually sits on row 2 with the nav links (right-
aligned), not next to search. Also tightened nav-link gap (24px -> 8px)
and font-size (15px -> 14px) to match the source's much denser spacing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ding

- globe.svg: hardcode fill to white instead of currentcolor. It's loaded
  via <img>, and currentColor doesn't resolve through an externally-
  referenced SVG image the way it does for an inlined one — it was
  defaulting to black, rendering invisibly against the black header.
- language switcher links: the source shows both languages in plain
  white, same weight, body font (not the heading/condensed font, and
  no yellow highlight for the current one) — matches that now, and
  drops the fake "|" separator (there isn't one; it's just a 16px gap).
- top-level nav items (Company, Business Segments, ...): added ~8px
  padding to each, matching the source's button-like hit targets —
  the gap value alone read much tighter than the source.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- nav-sections links were set to the condensed heading font at 500
  weight; the source uses the plain body font at regular (400) weight.
  Same fix for the stale comment claiming that was intentional.
- the dropdown chevron was a CSS-drawn border triangle absolutely
  positioned against the text (6x6px, cramped) — reads as a checkmark
  at that size. Replaced with an actual chevron icon inserted as a flex
  sibling of the label with a real gap, matching how the source lays
  it out (a 14-16px icon, not a tiny corner clipped out of a box).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ubmenu

- logo: the two-row breakpoint bumped it to 48px "a touch larger" — the
  source is 40px at every width. Dropped that override.
- chevron: 10px -> 14px to match the source, and rebuilt as an inlined
  SVG (stroke="currentColor") instead of an <img> — an externally-
  referenced image can't track the label's color on hover the way an
  inline one can, so the arrow wasn't changing color together with the
  text like it does on the source.
- language switcher: removed the yellow hover color I'd added — the
  source has no hover effect on these links at all.
- nav.html: "Careers" had no submenu, so it never got a dropdown or
  chevron. Added the source's actual regional careers links (Brazil,
  India, Italy, UK, US) so it matches.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
samshao and others added 2 commits August 13, 2026 12:59
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 <noreply@anthropic.com>
…all)

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 <noreply@anthropic.com>
…source

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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
The CSS padding value (16.8px) was already correct, but the visible gap
either side of the text on the source is 62px, not 16.8px — because
every in-content CTA (hero, teaser, columns) has min-width: 200px, and
the shared .button rule's justify-content: center centers the (shorter)
label within that fixed width. Confirmed across all 6 in-content CTAs
on the source page — all exactly 200px regardless of label length.

The jump-nav's own CTA ("Contact Us") is the one exception: it sizes to
its label with no min-width, confirmed separately. Reset min-width: 0
there so it doesn't inherit the 200px floor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
header (was 114px, source is 128px):
- header nav p { line-height: 1 } was squashing the nav-sections links
  (they're wrapped in a <p> where standalone) — the source keeps the
  body's normal line-height (1.6) on these, which alone accounts for
  most of the missing height (each row ends up ~9px shorter than the
  source's without it).
- row-gap 12px -> 16px, matching the source's logo row having 16px
  padding on both top and bottom rather than a smaller gap plus the
  outer nav's own padding.

hero (was min-height: 300px, source is 350px, via --hero-height): fixed
value updated directly.

Header now measures 126.4px vs the source's 128px (a ~1.6px rounding
difference, not worth chasing further); hero is an exact 350px match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…isely

Measured every property on the source's jump-nav links and found it was
wrong on nearly every axis:

- font-family: roboto (body) -> roboto-condensed (heading) — the source
  uses the condensed font here, unlike the main header nav which uses body
- font-weight: 700 -> 400
- padding: only a 6px bottom offset for the underline -> full button-like
  0.5em/1.2em (7px/16.8px), matching the header nav-link/CTA pattern
- link gap: 24px -> 0 — the source's links touch directly, padding alone
  provides the spacing
- alignment: left-aligned -> centered (`justify-content: safe center` on
  the link list, which grows via flex:1 to fill the row and push the CTA
  to the right)
- active-state indicator: was a 3px border-bottom sized to the text only;
  the source uses a 5px-tall ::after spanning the full padded link width,
  toggled by opacity rather than added/removed

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
samshao and others added 2 commits August 13, 2026 18:15
…tion

- height/gap: the strip was 40px below the hero (the boilerplate's
  generic `main > .section { margin: 40px 0 }` exempts only the first
  section — zeroed the hero's margin-bottom too, since sibling margins
  collapse to their max and zeroing just one side wasn't enough) and
  61px tall instead of the source's 68px (added min-height: 68px,
  removed the redundant vertical padding that was stacking on top of it).
  This also fixes the active-state underline's position, since it's
  anchored to the link's own (now correctly centered) box.

- click navigation was actually broken: the authored hrefs carry the
  source's own path convention ("/en/#featured", trailing slash), but
  this page is served at "/en" (no trailing slash) — clicking navigated
  to a different, 404ing path instead of jumping to the anchor on the
  current page. Normalized same-page anchor hrefs to the page's real
  path in decorate(), so this doesn't depend on content being
  re-authored/republished to take effect.

Also fixed the same trailing-slash issue in content/en.plain.html
directly (the authored source of truth) — not deployed yet, needs a DA
upload same as previous content fixes this session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…" anchor

- the bar's background/divider was constrained to the standard 1200px
  content column instead of spanning the full viewport width like the
  source — added the max-width:unset wrapper override (same pattern as
  pr-hero/pr-teaser), with the nav's own content now centered at 1140px
  inside it, matching the source's structure exactly.
- divider color: var(--light-color) (#f2f2f2, near-white) -> #ccc,
  matching the source's actual rgb(204,204,204).
- "FEATURED" link was dead: it points to #featured, but nothing on the
  page had that id — the source sets it directly on the cards section
  itself (no visible heading there to hang an id off of). Added the
  same in pr-cards.js.
- bumped scroll-margin-top (headings: 40px -> 100px; sections generally,
  for id targets with no heading like #featured) so an anchor-jump
  target isn't left hidden underneath the sticky jump-nav strip.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
samshao and others added 2 commits August 13, 2026 18:40
… previous one

The "has this section been passed?" threshold was derived from the
sticky bar's own bottom edge (~69-93px), but clicking a link scrolls its
target to sit at scroll-margin-top (100px) via native anchor scrolling —
a taller offset. So the just-clicked target never satisfied its own
"passed" check immediately after landing, and the previous link's
section (the last one that legitimately had passed) stayed active
instead. Reproduced exactly as reported: clicking "News & Events"
activated "Featured", clicking "Supply Chain" activated "News & Events".

Fixed by tying the JS threshold to the same 100px used for
scroll-margin-top, plus a couple of px of tolerance — the source page
renders at sub-pixel positions throughout (e.g. 494.796875), so a
just-landed target can sit at 100.2px, a hair over an exact-100
threshold, and needed a small buffer to register as "passed" (this is
what was still failing for "Gifts & Apparel" specifically after the
first part of this fix).

Also drops the now-unused `nav` param from setupScrollSpy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The "passed" line was pinned to ~100px (matching where a click-triggered
anchor jump lands), which only validates "did my click land correctly".
For ordinary scrolling it left a large dead zone: a section could fill
most of the viewport — its heading clearly the dominant, current
content — without having crossed that narrow a line yet, leaving the
*previous* section (long since scrolled entirely out of view) marked
active. Reproduced exactly as reported via screenshot: scrolled to
Gifts & Apparel (heading ~200px down, clearly the current section),
Supply Chain still underlined.

Now uses whichever is larger: that same ~100px floor (so the
click-jump case is unaffected — it already satisfies a bigger line
trivially) or 35% of the viewport height, which tracks ordinary
scrolling the way scrollspy usually works. Verified it doesn't
overcorrect either: a section still mostly below the fold (target top
~700px in a 900px viewport) correctly leaves the previous link active.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…d edge

display:flex + justify-content:space-between stretched the link to the
footer's full width, pushing the chevron out to the card's right border.
The source's link is plain inline content sized to itself (text + small
gap + chevron) sitting flush-left, with the rest of the footer's width
simply unused. inline-flex (no space-between) matches that.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
samshao and others added 2 commits August 13, 2026 19:54
Measured the "Advanced Rail Technology" teaser directly against the
source and found the block itself was structurally wrong, not just its
type sizes:

- width: unlike the hero (genuinely full-bleed), the source keeps this
  teaser within the page's normal 1140px content column — it does NOT
  stretch edge-to-edge. Ours had `max-width: unset` copied over from
  the hero, making it full-bleed when it shouldn't be. Confirmed by
  walking the source's actual DOM ancestor chain (the outer section is
  full width, but its wrapper is capped at 1140px, and the teaser block
  itself measures exactly that).
- height: 325px specifically for this "teaser" variant (the source
  defines a separate --hero-height for it, distinct from the plain
  hero's 350px) — ours was 360px.
- panel: max-width 488px, padding 24px 64px (notably less vertical and
  more horizontal than what we had, 40px 32px).
- heading: fixed 20px/700/1.25 line-height, no top margin — ours
  inherited the shared, much larger heading scale.
- body copy: 14px/400/1.6 line-height — margin already matched via the
  global paragraph rule once the font-size was corrected (0.8em/0.25em
  at 14px lines up with the measured 11.2px/3.5px exactly).
- panel background: solid black (#000), not var(--brand-dark) (#131313).

(The source's actual mechanism is a single full-bleed image with a
bottom gradient scrim, relying on the photo's own left-side darkness
for contrast — kept our two-column structural approach instead, since
it's more robust for a CMS where the authored image could vary, but
matched every measurable style value.)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Measured the "PROGRESS RAIL" default-content heading against the source
and found the underlying issue is bigger than just this section:

- content column width: main > .section > div was 1200px; the source
  measures exactly 1140px across every section checked (default
  content, cards, teaser, jump-nav) — this was our own assumed default,
  not verified against the source until now. Fixed at the source
  (styles.css), which also let two now-redundant per-block 1140px
  overrides (pr-teaser, and pr-jump-nav's inner nav already had its own
  reason to keep) get cleaned up.
- h2/h3 in body content inherit the shared h1-h6 scale sized for the
  hero (36-44px) — the source uses a fixed, much smaller scale for
  content headings: 26px for h2 (default content, cards — already
  fixed for cards), 20px for h3 (columns, teaser — teaser already
  fixed). Added the same for default-content h2 and pr-columns h3.
- body copy (default-content and pr-columns paragraphs) inherits the
  page's body font-size (18-22px) — source is a fixed 14px throughout,
  matching the cards/teaser copy already fixed earlier.
- underline bar: 4px -> 5px tall, and the bottom margin was doubling up
  with the next element's own top margin (16px 0 -> 16px 0 0).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Verified against source via getComputedStyle: all four paragraphs under
the "PROGRESS RAIL" heading carry a symmetric 35px left/right margin
(the heading itself does not), producing both the visible left indent
and the narrower 1070px effective width already implied by 1140-35*2.
…out model

Verified against source via getBoundingClientRect + CSSOM rule inspection:

- pr-teaser was built as a two-panel flex split (solid black text panel
  beside the image) under an earlier, unverified assumption. The source
  actually uses the same full-bleed-media-with-overlaid-text model as
  pr-hero: .media-wrapper is absolutely positioned across the block's
  entire width, with the text panel on top of it (z-index layered) and
  a dark gradient — not a solid panel — providing contrast underneath
  the copy. Restructured to match at >=900px; mobile's simpler stacked
  layout is unchanged.

- pr-hero had the same absolute-media/overlaid-body structure already,
  but was missing the gradient entirely, so light hero images visibly
  lacked the contrast backdrop under the heading/CTA that the source
  gives them.

Gradient values (linear-gradient(to right, ...)) read directly off the
source's stylesheet via document.styleSheets: 85% opaque black fading
out by 60% width for pr-hero, fully opaque fading out by 93% for the
stronger pr-teaser variant.
…ify-content)

The desktop overlay rule set align-items: center on .pr-teaser, but since
flex-direction is column (inherited from the base rule), align-items
controls the cross (horizontal) axis, not the vertical one — this
centered the text panel horizontally in the middle of the full-bleed
image instead of vertically centering it while keeping it pinned left,
per the source (verified via getBoundingClientRect: body-wrapper's left
edge matches the teaser's own left edge, vertically centered in its
height). Swapped to justify-content: center (vertical) + align-items:
flex-start (horizontal, left).
Verified against the source via getBoundingClientRect on every section
boundary (cards-container, the teaser/columns/video container, and the
gap before the footer): sections are separated by 64px, not the
boilerplate's 40px default we'd never corrected. Also explains the
recently-reported "section needs bottom margin" — the gap was present,
just visibly smaller than the source's.
Verified against the source's own stylesheet (via document.styleSheets):
it separates block wrappers stacked in the same section with their own
margin-top, not section padding — a generic rule for any wrapper
following another wrapper (32px), and a smaller special case (16.8px,
i.e. 1.2em at the body copy's 14px font-size) specifically right after
default content. Applied the same generic pattern here so it covers
pr-teaser-wrapper, pr-columns-wrapper, and pr-video-wrapper (all three
were flush against each other with 0 margin).
Verified against the source via getBoundingClientRect on both a
picture-first row (News & Events) and a text-first one (Supply Chain):
each row is a 2-column grid with zero gap, and the text panel supplies
its own uniform 64px padding on every side — that's what actually
separates it from the image, not a gap between columns. Our text column
had no padding at all (just a 24px flex-gap), so headings/copy sat
almost flush against the adjacent image. Replaced the flex-gap with the
matching padding on the non-image column.
Verified against the source via getBoundingClientRect: image and text
columns are always exactly equal halves (570px each of the 1140px row).
Our flex:1/flex:1 layout looked equivalent in principle, but the text
column's own min-content width (driven by its longest unbreakable
word/heading, especially after the last commit added 64px padding)
forced the flex algorithm to over-allocate it space at the image
column's expense — e.g. 506px/634px instead of an even 570/570 split.
Switched the row to a true CSS grid (1fr 1fr), matching how the source
actually implements it, with an explicit min-width: 0 on both columns
so long text still can't push the split off balance.
…ect ratio)

Verified against the source: rows measured 380px (varying slightly per
row) instead of the source's uniform, fixed 314px. Root cause was the
picture element being left in normal flow — since its child img's
height:100% can't resolve during the grid's own row-sizing pass (the
row's height is literally what's being computed), the browser fell back
to the image's natural aspect ratio to size it, so each row ended up as
tall as its own image happened to render (object-fit was also missing
entirely, defaulting to `fill` and distorting images in the process).

Matches the source's actual technique (found directly in its
stylesheet): position the picture absolutely (inset: 0) inside the
column so it can't contribute to the column's intrinsic height at all —
the column's height then comes purely from its min-height (184px
mobile, 314px desktop) or the row's own natural height from the text
column, whichever is taller, with the image cropped via object-fit:
cover to fill whatever height it ends up with.
Verified via getComputedStyle: the source's .video block uses the same
1070px --content-width constraint as the default-content paragraphs
(35px margin each side within the 1140px section column), not the
boilerplate's own 900px default we'd inherited unchanged. Also dropped
the block's own 24px vertical margin — spacing above it is already
handled by the generic wrapper-to-wrapper margin rule added earlier.
…n row)

Verified against the source via getComputedStyle/CSSOM — the footer had
never actually been checked precisely, and turned out to be a completely
different design than assumed:

- Background is a flat #ccc, not dark/black; nav links and headings use
  the page's own dark text color, not white — a from-scratch color
  correction.
- Each nav category is a native <details>/<summary> accordion (collapsible
  on mobile via free native behavior, forced open and non-interactive at
  desktop via CSS), not a plain heading + always-visible list. Since DA's
  content pipeline strips <details>/<summary>/<button> from block-cell
  content on upload, these are authored as plain <h3>/<ul>/<a> and built
  into their real elements client-side in footer.js instead.
- A second, icon-only row of the same 5 social links sits below the nav
  (source renders both a text column AND an icon row) — added as its own
  section (footer-social, via section-metadata) with brand SVGs inlined
  for currentColor, matching the project's existing icon convention.
- The legal/copyright line is a <ul> with a genuine "Cookie Settings"
  control (a CSS-generated "|" separator between items, not typed
  pipes), not a <p> of loose links — restructured to match, with its own
  distinct blue link color (verified: rgb(38, 121, 184), separate from
  the rest of the footer's dark text).

blocks/footer/footer.js, blocks/footer/footer.css, and
tools/nav-footer-source/footer.html (uploaded to DA + previewed) all
rewritten together.
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.
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 <ul> had no justify-content, so icons clustered
  left under "COMPANY" instead of right-aligning under "SOCIAL MEDIA" —
  source sets justify-content: flex-end here.
Two more things verified against the source that our footer had wrong:

- The copyright row sits on a genuinely different (white) background
  from the rest of the footer's gray — found via
  getComputedStyle(el, '::after'): the source gives .footer-copyright a
  full-viewport-width white rectangle behind it (position: absolute,
  left: calc(50% - 50vw), width: 100vw — a standard full-bleed technique
  to escape the footer's own centered/padded column), extending from its
  own top edge down through its 32px padding-top plus another 32px
  beyond, landing exactly on the footer block's own bottom edge.
  isolation: isolate keeps its z-index: -1 contained to this element
  rather than escaping behind the whole page.

- The footer.block itself is a CSS grid (grid-template-areas: "nav"
  "social" "copyright"), with a verified 24px row gap between each
  section — our three sections had no gap between them at all (nav sat
  flush against the icon row, which sat flush against the copyright
  row), which is what read as "social needs bigger top/bottom margins."

Restructured footer.js to append the fragment's three sections directly
as the block's children (matching the source's own DOM shape) instead
of an extra unstyled wrapper div, so footer.css's grid can target them
by name.
Verified against the source via getBoundingClientRect/getComputedStyle:

- The gray background belongs on the outer <footer> element (spans the
  full viewport width), not on .footer.block itself — the block is only
  the centered/padded 1140px content column sitting on top of it
  (background: transparent). Moved background-color: #ccc off the block
  onto the bare `footer` selector.

- .footer-copyright ul has justify-content: center on the source (missed
  in the earlier rebuild) — the legal/cookie links center within the row
  rather than starting flush left.
Reproduced from a user screenshot: clicking "NEWS & EVENTS" landed there
correctly, but "SUPPLY CHAIN" stayed underlined instead.

Root cause: the "passed" line was Math.max(102, innerHeight * 0.35), uncapped.
On a tall enough viewport (reproduced at 1250px), 35% of it (437.5px) exceeds
the ~314px vertical gap between two adjacent jump-nav targets — the
"News & Events"/"Supply Chain" pr-columns rows (a fixed height established
by an earlier fix in this same area). Once the line exceeds that gap, BOTH
targets simultaneously satisfy "top <= line", and .filter(passed).pop()
picks the later (wrong) one.

Capped the line at 260px — comfortably under every real inter-target gap
on this page, while still clearing the original dead-zone floor the 0.35
multiplier was there to solve. Verified: reproduced the exact bug at
2000x1250 before the fix, confirmed fixed after; re-verified the original
dead-zone scenario and ordinary scroll-through of all four sections still
track correctly at 1600x900.
@samshao
samshao merged commit 76a9714 into main Aug 14, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant