From f97f847dd9f13edd7860c3e09ca49bbe27a09f3c Mon Sep 17 00:00:00 2001 From: Sam Shao Date: Sun, 9 Aug 2026 21:18:07 -0700 Subject: [PATCH 1/4] Add email signup form and social icon row to footer's "Connect With Us" column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fortinet.com's real footer pairs its "Connect With Us" column with an email signup form above it and a row of social icons below its own link list — neither existed here at all. - Email signup form (buildEmailForm in footer.js): matches fortinet.com's own .ftnt-subscribe-form layout (white input, red submit-arrow button, consent checkbox + copy). No real subscription backend exists for this site: fortinet.com posts to its own Eloqua marketing-automation account (site/campaign/form IDs baked into hidden fields), which isn't something we have or should submit to — this is presentational only for now (preventDefault + a client-side "Subscription successful!" message), pending a decision on where this should actually go. - Social icon row (buildSocialRow in footer.js): finds links in the "Connect With Us" column by their visible text (LinkedIn/X/YouTube/ Instagram/Facebook/RSS), moves them into their own list, and swaps in the matching icon — self-hosted from fortinet.com's own DAM (icons/social-media/*_icon_footer.svg), already white in the source so no recolor needed. No-ops if those links haven't been authored yet (content still needs to be added — see the content draft shared earlier in this conversation). Both verified at 390px and 1464px: the email form renders correctly with just the existing content, and the social-icon extraction was verified by injecting the 6 platform links the same way an author would and confirming all 6 matched and converted correctly. Co-Authored-By: Claude Sonnet 5 --- blocks/footer/footer.css | 113 +++++++++++++++++++++++++++++- blocks/footer/footer.js | 102 +++++++++++++++++++++++++++ icons/footer-social-facebook.svg | 1 + icons/footer-social-instagram.svg | 1 + icons/footer-social-linkedin.svg | 1 + icons/footer-social-rss.svg | 1 + icons/footer-social-twitter.svg | 10 +++ icons/footer-social-youtube.svg | 1 + icons/footer-submit-arrow.svg | 1 + 9 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 icons/footer-social-facebook.svg create mode 100644 icons/footer-social-instagram.svg create mode 100644 icons/footer-social-linkedin.svg create mode 100644 icons/footer-social-rss.svg create mode 100644 icons/footer-social-twitter.svg create mode 100644 icons/footer-social-youtube.svg create mode 100644 icons/footer-submit-arrow.svg diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css index da89ffb..e4656ae 100644 --- a/blocks/footer/footer.css +++ b/blocks/footer/footer.css @@ -26,14 +26,116 @@ footer .footer p { margin: 0; } -/* links */ +/* links — established here, before every more-specific a:any-link override + below (the subscribe form's consent link, the legal-links row, etc.), so + each of those layers on top of this base instead of coming before it. + :hover is further down (see near the end of this file), after every + selector its own specificity needs to exceed. */ footer a:any-link { color: #fff; text-decoration: none; } -footer a:any-link:hover { - color: #fff; +/* Social icon row — appended after "Connect With Us"'s own link list (see + buildSocialRow in footer.js). Icons are already white in the source SVGs + (fill baked in), so no filter/recolor needed here. */ +footer .footer-social { + display: flex; + flex-wrap: wrap; + gap: 12px 17px; + margin: 20px 0 0; +} + +footer .footer-social li { + margin: 0; +} + +footer .footer-social a { + display: block; +} + +footer .footer-social svg { + display: block; + width: 24px; + height: 24px; +} + +/* Email signup form (see buildEmailForm in footer.js) — measured directly + from fortinet.com's own .ftnt-subscribe-form: white input, no border, + red submit button beside it, consent checkbox + copy below. */ +footer .footer-subscribe-form { + margin: 0 0 24px; +} + +footer .footer-subscribe-control { + display: flex; +} + +footer .footer-subscribe-control label { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; +} + +footer .footer-subscribe-control input { + flex: 1; + min-width: 0; + border: 0; + padding: 9px; + background-color: #fff; + color: #000; + font-size: var(--body-font-size-xs, 13px); +} + +footer .footer-subscribe-control button { + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 33px; + border: 0; + padding: 0; + background-color: var(--brand-red, #da291c); + cursor: pointer; +} + +footer .footer-subscribe-control button img, +footer .footer-subscribe-control button svg { + width: 16px; + height: 16px; +} + +footer .footer-subscribe-status { + margin: 12px 0 0; + color: var(--brand-red, #da291c); + font-size: var(--body-font-size-xs, 13px); +} + +footer .footer-subscribe-consent { + display: flex; + align-items: flex-start; + gap: 8px; + margin-top: 12px; +} + +footer .footer-subscribe-consent input { + flex: 0 0 auto; + margin: 3px 0 0; + accent-color: var(--brand-red, #da291c); +} + +footer .footer-subscribe-consent p { + margin: 0; + font-size: var(--body-font-size-xs, 13px); + line-height: 1.4; + color: rgb(255 255 255 / 80%); +} + +footer .footer-subscribe-consent a:any-link { + color: rgb(255 255 255 / 80%); text-decoration: underline; } @@ -90,6 +192,11 @@ footer .default-content-wrapper > p:first-child { border-top: 1px solid rgb(255 255 255 / 20%); } +footer a:any-link:hover { + color: #fff; + text-decoration: underline; +} + /* legal links row — inline list separated by vertical bars */ footer .default-content-wrapper > p:last-child { display: flex; diff --git a/blocks/footer/footer.js b/blocks/footer/footer.js index ff5708a..72e0745 100644 --- a/blocks/footer/footer.js +++ b/blocks/footer/footer.js @@ -1,6 +1,100 @@ import { getMetadata } from '../../scripts/aem.js'; import { loadFragment } from '../fragment/fragment.js'; +// fortinet.com's own icon per platform name (self-hosted, fetched from its DAM +// the same way carousel-story-dots.svg and the footer-legal badges were — +// matched by the link's own visible text, not its href, since "X"/"Twitter" +// share one icon and this is simpler than pattern-matching every platform's +// possible URL shape). +const SOCIAL_ICONS = { + linkedin: { file: 'footer-social-linkedin.svg', label: 'LinkedIn' }, + x: { file: 'footer-social-twitter.svg', label: 'X' }, + twitter: { file: 'footer-social-twitter.svg', label: 'X' }, + youtube: { file: 'footer-social-youtube.svg', label: 'YouTube' }, + instagram: { file: 'footer-social-instagram.svg', label: 'Instagram' }, + facebook: { file: 'footer-social-facebook.svg', label: 'Facebook' }, + rss: { file: 'footer-social-rss.svg', label: 'RSS' }, +}; + +function fetchIcon(el, file) { + fetch(`${window.hlx.codeBasePath}/icons/${file}`) + .then((resp) => (resp.ok ? resp.text() : '')) + .then((svg) => { el.innerHTML = svg; }) + .catch(() => {}); // decorative only; leave empty on failure +} + +/** + * fortinet.com's "Connect With Us" column ends with a row of icon-only + * social links (LinkedIn, X, YouTube, Instagram, Facebook, RSS) in their own + * list, separate from the column's own text links above them. Authored here + * as plain links naming the platform (e.g. "LinkedIn") anywhere in the given + * column, since that's the only signal available — this finds them by that + * visible text, moves them into their own list, and swaps in the real icon. + * @param {Element} column the column to search for and relocate social links within + */ +function buildSocialRow(column) { + const socialLinks = [...column.querySelectorAll('a')] + .filter((a) => a.textContent.trim().toLowerCase() in SOCIAL_ICONS); + if (!socialLinks.length) return; + + const ul = document.createElement('ul'); + ul.className = 'footer-social'; + + socialLinks.forEach((a) => { + const { file, label } = SOCIAL_ICONS[a.textContent.trim().toLowerCase()]; + const oldLi = a.closest('li'); + a.textContent = ''; + a.setAttribute('aria-label', label); + a.setAttribute('target', '_blank'); + a.setAttribute('rel', 'noopener'); + const li = document.createElement('li'); + li.append(a); // moves `a` out of oldLi + ul.append(li); + if (oldLi && !oldLi.children.length) oldLi.remove(); + fetchIcon(a, file); + }); + + column.append(ul); +} + +/** + * fortinet.com pairs an email signup form with the "Connect With Us" column + * — not expressible as authored link content (it's a real form, not a + * link), so built here instead. No real subscription backend exists for + * this site yet: fortinet.com posts this to its own Eloqua marketing- + * automation account (site/campaign/form IDs baked into hidden fields), + * which isn't something this site has or should submit to. Placeholder + * behavior only, pending a decision on where this should actually go. + * @param {Element} column the column to prepend the form to + */ +function buildEmailForm(column) { + const form = document.createElement('form'); + form.className = 'footer-subscribe-form'; + form.innerHTML = ` + + + + `; + + form.addEventListener('submit', (e) => { + e.preventDefault(); + form.querySelector('.footer-subscribe-status').hidden = false; + form.reset(); + }); + + column.prepend(form); + fetchIcon(form.querySelector('button[type="submit"]'), 'footer-submit-arrow.svg'); +} + /** * loads and decorates the footer * @param {Element} block The footer block element @@ -16,5 +110,13 @@ export default async function decorate(block) { const footer = document.createElement('div'); while (fragment.firstElementChild) footer.append(fragment.firstElementChild); + const connectHeading = [...footer.querySelectorAll('h3')] + .find((h) => h.textContent.trim().toLowerCase() === 'connect with us'); + if (connectHeading) { + const connectColumn = connectHeading.parentElement; + buildSocialRow(connectColumn); + buildEmailForm(connectColumn); + } + block.append(footer); } diff --git a/icons/footer-social-facebook.svg b/icons/footer-social-facebook.svg new file mode 100644 index 0000000..ac77913 --- /dev/null +++ b/icons/footer-social-facebook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/footer-social-instagram.svg b/icons/footer-social-instagram.svg new file mode 100644 index 0000000..8bc8ba0 --- /dev/null +++ b/icons/footer-social-instagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/footer-social-linkedin.svg b/icons/footer-social-linkedin.svg new file mode 100644 index 0000000..c5e27ac --- /dev/null +++ b/icons/footer-social-linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/footer-social-rss.svg b/icons/footer-social-rss.svg new file mode 100644 index 0000000..a2c25f1 --- /dev/null +++ b/icons/footer-social-rss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/footer-social-twitter.svg b/icons/footer-social-twitter.svg new file mode 100644 index 0000000..5474087 --- /dev/null +++ b/icons/footer-social-twitter.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/icons/footer-social-youtube.svg b/icons/footer-social-youtube.svg new file mode 100644 index 0000000..89db210 --- /dev/null +++ b/icons/footer-social-youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/footer-submit-arrow.svg b/icons/footer-submit-arrow.svg new file mode 100644 index 0000000..71d3b9e --- /dev/null +++ b/icons/footer-submit-arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file From 3a9457e30920fe2bd4924a0a799321c08f44aac2 Mon Sep 17 00:00:00 2001 From: Sam Shao Date: Sun, 9 Aug 2026 21:26:01 -0700 Subject: [PATCH 2/4] Fix column top-alignment and default the social icon row to fortinet.com's real links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Columns were vertically centered within their shared row height (the generic columns block's own align-items:center, meant for its usual image+text pairing elsewhere on the site), not top-aligned — so a shorter column like "Connect With Us" (now with the email form prepended to it) started ~64px below its taller siblings instead of flush with their tops. Overridden to align-items:start for the footer's own use of this block. - buildSocialRow now falls back to fortinet.com's own real 6 links (DEFAULT_SOCIAL_LINKS) when none are authored in the footer doc yet, rather than silently omitting the row until that content edit happens. Content-authored links (matched by visible platform name, as before) still take priority if present. Verified: "More" and "Connect With Us" columns now start at the exact same y-position, and all 6 social icons render immediately without requiring any content change. Co-Authored-By: Claude Sonnet 5 --- blocks/footer/footer.css | 9 ++++++++- blocks/footer/footer.js | 26 ++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css index e4656ae..161efdb 100644 --- a/blocks/footer/footer.css +++ b/blocks/footer/footer.css @@ -168,10 +168,17 @@ footer .footer li { line-height: 1.4; } -/* columns block: responsive grid of link columns */ +/* columns block: responsive grid of link columns. align-items:start + overrides the generic columns block's own align-items:center (right for + its usual image+text pairing elsewhere on the site, wrong here) — center + was vertically centering each column within the shared row height (set + by whichever column has the most links), so a short column like "Connect + With Us" started well below its taller siblings instead of flush with + their tops like fortinet.com's own footer columns. */ footer .columns > div { display: grid; grid-template-columns: 1fr; + align-items: start; gap: 32px 24px; } diff --git a/blocks/footer/footer.js b/blocks/footer/footer.js index 72e0745..abc68c4 100644 --- a/blocks/footer/footer.js +++ b/blocks/footer/footer.js @@ -16,6 +16,18 @@ const SOCIAL_ICONS = { rss: { file: 'footer-social-rss.svg', label: 'RSS' }, }; +// fortinet.com's own 6 social links, in the order they appear there — +// used whenever the footer doc hasn't authored these yet (see buildSocialRow +// below), so this row shows up correctly without waiting on a content edit. +const DEFAULT_SOCIAL_LINKS = [ + ['linkedin', 'https://www.linkedin.com/company/fortinet'], + ['x', 'https://www.x.com/Fortinet'], + ['youtube', 'https://www.youtube.com/channel/UCJHo4AuVomwMRzgkA5DQEOA?sub_confirmation=1'], + ['instagram', 'https://www.instagram.com/fortinet/'], + ['facebook', 'https://www.facebook.com/fortinet'], + ['rss', 'https://www.fortinet.com/rss-feeds'], +]; + function fetchIcon(el, file) { fetch(`${window.hlx.codeBasePath}/icons/${file}`) .then((resp) => (resp.ok ? resp.text() : '')) @@ -30,12 +42,22 @@ function fetchIcon(el, file) { * as plain links naming the platform (e.g. "LinkedIn") anywhere in the given * column, since that's the only signal available — this finds them by that * visible text, moves them into their own list, and swaps in the real icon. + * Falls back to fortinet.com's own real links (DEFAULT_SOCIAL_LINKS above) + * if none are authored yet, rather than silently omitting the row. * @param {Element} column the column to search for and relocate social links within */ function buildSocialRow(column) { - const socialLinks = [...column.querySelectorAll('a')] + let socialLinks = [...column.querySelectorAll('a')] .filter((a) => a.textContent.trim().toLowerCase() in SOCIAL_ICONS); - if (!socialLinks.length) return; + + if (!socialLinks.length) { + socialLinks = DEFAULT_SOCIAL_LINKS.map(([key, href]) => { + const a = document.createElement('a'); + a.href = href; + a.textContent = SOCIAL_ICONS[key].label; + return a; + }); + } const ul = document.createElement('ul'); ul.className = 'footer-social'; From 7e007029ff0b09dbd544fccdffad57ada59a0d43 Mon Sep 17 00:00:00 2001 From: Sam Shao Date: Sun, 9 Aug 2026 21:39:22 -0700 Subject: [PATCH 3/4] Increase footer link spacing and social-icon top margin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Link gap within a column: 8px -> 20px, matching fortinet.com's own measured li margin-bottom exactly. - Social icon row's top margin: bumped to 32px, deliberately more than fortinet.com's own 20px gap there, per explicit request. Both new margin declarations needed a specificity fix: .footer-social and .footer-social li are each canceled out by a same-or-higher-specificity generic rule elsewhere in this file ("footer .footer ul" zeroing every list's margin, "footer .footer li" setting the 20px between-link spacing) that either tied and came later in the file, or was one tier more specific outright. Fixed by matching that structure ("footer .footer ul.footer-social") or repeating the class to add a tier ("footer .footer-social.footer-social li") rather than just naming the class alone. Verified: link-to-link gap now 20px (was already the effective value before this change, since a prior 8px li margin was already losing a margin-collapse against the social row's own then-uncontested 20px top margin — but is now an intentional, correct value rather than an accidental one). Social row's own top gap now 32px (was silently 0 due to the specificity bug above, despite the CSS already saying 20px). Co-Authored-By: Claude Sonnet 5 --- blocks/footer/footer.css | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css index 161efdb..66c9731 100644 --- a/blocks/footer/footer.css +++ b/blocks/footer/footer.css @@ -38,16 +38,23 @@ footer a:any-link { /* Social icon row — appended after "Connect With Us"'s own link list (see buildSocialRow in footer.js). Icons are already white in the source SVGs - (fill baked in), so no filter/recolor needed here. */ -footer .footer-social { + (fill baked in), so no filter/recolor needed here. + + Selector repeats "footer .footer ul" (rather than just ".footer-social" + alone) so this wins on specificity — this element is itself a
    , so + without the extra structure here that later, more general rule's own + margin:0 (meant to zero out every link list's own default margin) beat + this one and canceled the margin-top below entirely. + + margin-top:32px here is deliberately more than fortinet.com's own 20px + gap above this row — this collapses with the preceding link's own 20px + margin-bottom (the larger of the two wins, not their sum), so this is + the row's actual top gap, not 20+32. */ +footer .footer ul.footer-social { display: flex; flex-wrap: wrap; gap: 12px 17px; - margin: 20px 0 0; -} - -footer .footer-social li { - margin: 0; + margin: 32px 0 0; } footer .footer-social a { @@ -163,11 +170,24 @@ footer .footer ul { padding: 0; } +/* Gap between links within a column — measured directly on fortinet.com + (the "More" column's own li margin-bottom): 20px, not 8px. */ footer .footer li { - margin: 0 0 8px; + margin: 0 0 20px; line-height: 1.4; } +/* Overrides the above for the social-icon row specifically, so these sit + flush in their own row instead of getting the same 20px margin-bottom + real links get. Ties this rule's specificity, so needs to come after it + here and repeats .footer-social (a real, if unusual-looking, selector — + it just matches the same element twice) to add a class to this + selector's specificity without needing a class in the markup that isn't + otherwise needed. */ +footer .footer-social.footer-social li { + margin: 0; +} + /* columns block: responsive grid of link columns. align-items:start overrides the generic columns block's own align-items:center (right for its usual image+text pairing elsewhere on the site, wrong here) — center From cd49d79c2ce935ab1f88f04426ad3bdf4661aa28 Mon Sep 17 00:00:00 2001 From: Sam Shao Date: Sun, 9 Aug 2026 21:44:43 -0700 Subject: [PATCH 4/4] Align "Connect With Us" heading to "Ransomware Hub" in the More column Bumped the email form's own margin-bottom from 24px to 67px (measured the exact 43px gap needed directly) so the heading below it lines up with that specific reference point in the adjacent column, per explicit request. Verified: the two are now within a fraction of a pixel of each other (diff: -0.09px). Co-Authored-By: Claude Sonnet 5 --- blocks/footer/footer.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css index 66c9731..c1df50a 100644 --- a/blocks/footer/footer.css +++ b/blocks/footer/footer.css @@ -70,8 +70,12 @@ footer .footer-social svg { /* Email signup form (see buildEmailForm in footer.js) — measured directly from fortinet.com's own .ftnt-subscribe-form: white input, no border, red submit button beside it, consent checkbox + copy below. */ + +/* margin-bottom pushes the "Connect With Us" heading below this form down + to align with "Ransomware Hub" in the "More" column beside it (measured + directly: 43px lower than the previous 24px produced). */ footer .footer-subscribe-form { - margin: 0 0 24px; + margin: 0 0 67px; } footer .footer-subscribe-control {