diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css
index da89ffb..c1df50a 100644
--- a/blocks/footer/footer.css
+++ b/blocks/footer/footer.css
@@ -26,14 +26,127 @@ 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.
+
+ 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: 32px 0 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. */
+
+/* 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 67px;
+}
+
+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;
}
@@ -61,15 +174,35 @@ 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;
}
-/* columns block: responsive grid of link columns */
+/* 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
+ 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;
}
@@ -90,6 +223,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..abc68c4 100644
--- a/blocks/footer/footer.js
+++ b/blocks/footer/footer.js
@@ -1,6 +1,122 @@
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' },
+};
+
+// 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() : ''))
+ .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.
+ * 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) {
+ let socialLinks = [...column.querySelectorAll('a')]
+ .filter((a) => a.textContent.trim().toLowerCase() in SOCIAL_ICONS);
+
+ 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';
+
+ 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 = `
+
+
Subscription successful!
+
+ `;
+
+ 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 +132,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