Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 143 additions & 5 deletions blocks/footer/footer.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ul>, 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;
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
Expand Down
124 changes: 124 additions & 0 deletions blocks/footer/footer.js
Original file line number Diff line number Diff line change
@@ -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 = `
<div class="footer-subscribe-control">
<label for="footer-subscribe-email">Enter Email Address</label>
<input type="email" id="footer-subscribe-email" name="emailAddress" placeholder="Enter Email Address" required>
<button type="submit" aria-label="Submit"></button>
</div>
<p class="footer-subscribe-status" role="status" hidden>Subscription successful!</p>
<div class="footer-subscribe-consent">
<label for="footer-subscribe-consent">
<input type="checkbox" id="footer-subscribe-consent" name="consent" required aria-label="Consent agreement">
</label>
<p>I want to receive news and product emails. Read our <a href="https://www.fortinet.com/corporate/about-us/privacy">privacy policy</a>.</p>
</div>
`;

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
Expand All @@ -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);
}
1 change: 1 addition & 0 deletions icons/footer-social-facebook.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions icons/footer-social-instagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions icons/footer-social-linkedin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions icons/footer-social-rss.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions icons/footer-social-twitter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions icons/footer-social-youtube.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions icons/footer-submit-arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.