Last active
March 19, 2025 22:33
-
-
Save edjw/2a0be893bb67e601681f512916a88602 to your computer and use it in GitHub Desktop.
page-builder-prototype
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Todo | |
// - Work out how to change font, colours, weights | |
// - Work out how to change the logo if it's not available as a URL | |
// - Add changing page and form background colours | |
// - Make a UI to build the config including maybe base64 encoding the images and font files | |
// A config that could be created by a user or generated by a tool | |
const PAGE_ELEMENTS_CHANGES = { | |
LOGO_SRC: "https://cdn.friendsoftheearth.uk/themes/custom/foed8/logo.svg", // Just a demo! | |
TOP_BAR_COLOUR: "#232f4a", | |
BANNER_IMAGE_SRC: | |
"https://cdn.friendsoftheearth.uk/sites/default/files/styles/hero_image_1920x1080/public/media/images/Planet.webp", // Just a demo! | |
PAGE_TITLE: "My New Page Title", | |
HEADLINE: "My new headline", | |
INTRO: "My new intro text", | |
FORM_INTRO_HTML: "<p>My new form intro text</p>", | |
PROGRESS_METER_PROGRESS: "70%", | |
PROGRESS_METER_BACKGROUND_COLOUR: "#232f4a", | |
PROGRESS_METER_FIRST_TEXT_HTML: | |
"<p>My new progress meter first text with <strong>20,242</strong> signatures</p>", | |
PROGRESS_METER_SECOND_TEXT_HTML: "<p>My new progress meter second text</p>", | |
OPTIN_INTRO: `My new optin intro text`, | |
OPTIN_QUESTION_LABEL: "My new optin question label", | |
OPTIN_YES_LABEL: "My new optin yes label", | |
OPTIN_NO_LABEL: "My new optin no label", | |
SUBMIT_BUTTON_LABEL: "New submit button label", | |
SUBMIT_BUTTON_COLOUR: "#FF0100", | |
PAGE_CONTENT_HTML: "<p>Lots of content</p><p><em>More</em> content</p>", | |
IN_CONTENT_IMAGE_SRC: | |
"https://cdn.friendsoftheearth.uk/sites/default/files/styles/hero_image/public/media/images/flooding%20sunderland%20thinkstock%204x3.webp", // Just a demo! | |
}; | |
// | |
// | |
// Users shouldn't need to edit anything after this point | |
// | |
// | |
const PAGE_ELEMENTS_SELECTORS = { | |
PAGE: "#page", | |
PAGE_TITLE: "#page-title", | |
INTRO: "#top p", | |
HEADLINE: "#headline p", | |
BANNER_PICTURE: "#banner picture", | |
PAGE_CONTENT: "#content", | |
IN_CONTENT_IMAGE: "#content .media-stretch", | |
LOGO: ".logo>img", | |
TOP_BAR: ".top-bar", | |
FORM_OUTER: "#form-outer", | |
FORM_INTRO: "#sidebar div.intro", | |
PROGRESS_METER: ".progress-meter", | |
PROGRESS_METER_FIRST_TEXT_HTML: ".progress-wrapper p:nth-of-type(1)", | |
PROGRESS_METER_SECOND_TEXT_HTML: ".progress-wrapper p:nth-of-type(2)", | |
OPTIN_INTRO: `.form-group:has(input[name="opt-in-email"]) .intro-text`, | |
OPTIN_QUESTION_LABEL: `.form-group:has(input[name="opt-in-email"]) .group-item.form-item > label:first-of-type`, | |
OPTIN_YES_LABEL: `input[name="opt-in-email"][value="1"] + label span`, | |
OPTIN_NO_LABEL: `input[name="opt-in-email"][value="0"] + label span`, | |
SUBMIT_BUTTON: `input[type="submit"]`, | |
}; | |
// Create and add loading overlay | |
const createLoadingOverlay = () => { | |
const overlay = document.createElement("div"); | |
overlay.id = "loading-overlay"; | |
// Add CSS for the overlay | |
overlay.style.cssText = ` | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(255, 255, 255, 1); | |
z-index: 9999; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
transition: opacity 0.5s ease-out; | |
`; | |
// Create spinner | |
const spinner = document.createElement("div"); | |
spinner.className = "spinner"; | |
spinner.style.cssText = ` | |
width: 60px; | |
height: 60px; | |
border: 8px solid #f3f3f3; | |
border-top: 8px solid #232f4a; | |
border-radius: 50%; | |
animation: spin 1s linear infinite; | |
`; | |
// Add animation | |
const styleSheet = document.createElement("style"); | |
styleSheet.textContent = ` | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
`; | |
document.head.appendChild(styleSheet); | |
overlay.appendChild(spinner); | |
document.body.appendChild(overlay); | |
return overlay; | |
}; | |
// Show loading overlay immediately | |
const loadingOverlay = createLoadingOverlay(); | |
// sleep for 2 seconds to allow the page to load | |
setTimeout(() => { | |
// Helper function to apply changes to elements if they exist | |
const updateElement = (property, selector, updateFn) => { | |
if (PAGE_ELEMENTS_CHANGES[property]) { | |
const element = document.querySelector(selector); | |
if (element) { | |
updateFn(element, PAGE_ELEMENTS_CHANGES[property]); | |
} | |
} | |
}; | |
// Define all simple updates in a declarative way | |
const simpleUpdates = [ | |
{ | |
property: "LOGO_SRC", | |
selector: PAGE_ELEMENTS_SELECTORS.LOGO, | |
update: (el, val) => (el.src = val), | |
}, | |
{ | |
property: "TOP_BAR_COLOUR", | |
selector: PAGE_ELEMENTS_SELECTORS.TOP_BAR, | |
update: (el, val) => | |
el.style.setProperty("background-color", val, "important"), | |
}, | |
{ | |
property: "PAGE_TITLE", | |
selector: PAGE_ELEMENTS_SELECTORS.PAGE_TITLE, | |
update: (el, val) => (el.innerText = val), | |
}, | |
{ | |
property: "HEADLINE", | |
selector: PAGE_ELEMENTS_SELECTORS.HEADLINE, | |
update: (el, val) => (el.innerText = val), | |
}, | |
{ | |
property: "INTRO", | |
selector: PAGE_ELEMENTS_SELECTORS.INTRO, | |
update: (el, val) => (el.innerText = val), | |
}, | |
{ | |
property: "FORM_INTRO_HTML", | |
selector: PAGE_ELEMENTS_SELECTORS.FORM_INTRO, | |
update: (el, val) => (el.innerHTML = val), | |
}, | |
{ | |
property: "PROGRESS_METER_FIRST_TEXT_HTML", | |
selector: PAGE_ELEMENTS_SELECTORS.PROGRESS_METER_FIRST_TEXT_HTML, | |
update: (el, val) => (el.innerHTML = val), | |
}, | |
{ | |
property: "PROGRESS_METER_SECOND_TEXT_HTML", | |
selector: PAGE_ELEMENTS_SELECTORS.PROGRESS_METER_SECOND_TEXT_HTML, | |
update: (el, val) => (el.innerHTML = val), | |
}, | |
{ | |
property: "OPTIN_INTRO", | |
selector: PAGE_ELEMENTS_SELECTORS.OPTIN_INTRO, | |
update: (el, val) => (el.innerText = val), | |
}, | |
{ | |
property: "OPTIN_QUESTION_LABEL", | |
selector: PAGE_ELEMENTS_SELECTORS.OPTIN_QUESTION_LABEL, | |
update: (el, val) => (el.innerText = val), | |
}, | |
{ | |
property: "OPTIN_YES_LABEL", | |
selector: PAGE_ELEMENTS_SELECTORS.OPTIN_YES_LABEL, | |
update: (el, val) => (el.innerText = val), | |
}, | |
{ | |
property: "OPTIN_NO_LABEL", | |
selector: PAGE_ELEMENTS_SELECTORS.OPTIN_NO_LABEL, | |
update: (el, val) => (el.innerText = val), | |
}, | |
{ | |
property: "SUBMIT_BUTTON_COLOUR", | |
selector: PAGE_ELEMENTS_SELECTORS.SUBMIT_BUTTON, | |
update: (el, val) => (el.style.backgroundColor = val), | |
}, | |
]; | |
// Apply all simple updates | |
simpleUpdates.forEach(({ property, selector, update }) => { | |
updateElement(property, selector, update); | |
}); | |
// Special case: Banner image | |
updateElement( | |
"BANNER_IMAGE_SRC", | |
PAGE_ELEMENTS_SELECTORS.BANNER_PICTURE, | |
(element, value) => { | |
const bannerImageSource = element.querySelector("source"); | |
bannerImageSource.srcset = ""; | |
const bannerImageImg = element.querySelector("img"); | |
bannerImageImg.src = value; | |
bannerImageImg.srcset = ""; | |
} | |
); | |
// Special case: Progress meter | |
const progressMeter = document.querySelector( | |
PAGE_ELEMENTS_SELECTORS.PROGRESS_METER | |
); | |
if (progressMeter) { | |
if (PAGE_ELEMENTS_CHANGES.PROGRESS_METER_BACKGROUND_COLOUR) { | |
progressMeter.style.setProperty( | |
"background-color", | |
PAGE_ELEMENTS_CHANGES.PROGRESS_METER_BACKGROUND_COLOUR, | |
"important" | |
); | |
} | |
if (PAGE_ELEMENTS_CHANGES.PROGRESS_METER_PROGRESS) { | |
progressMeter.style.setProperty( | |
"width", | |
PAGE_ELEMENTS_CHANGES.PROGRESS_METER_PROGRESS, | |
"important" | |
); | |
} | |
} | |
// Special case: Page content and in-content image | |
if (PAGE_ELEMENTS_CHANGES.PAGE_CONTENT_HTML) { | |
const pageContent = document.querySelector( | |
PAGE_ELEMENTS_SELECTORS.PAGE_CONTENT | |
); | |
if (pageContent) { | |
let newImageHTML = ""; | |
if (PAGE_ELEMENTS_CHANGES.IN_CONTENT_IMAGE_SRC) { | |
newImageHTML = ` | |
<div class="media-stretch"> | |
<img src="${PAGE_ELEMENTS_CHANGES.IN_CONTENT_IMAGE_SRC}"> | |
</div> | |
`; | |
} | |
pageContent.innerHTML = ` | |
<div id="insertedContent"> | |
${newImageHTML} | |
${PAGE_ELEMENTS_CHANGES.PAGE_CONTENT_HTML} | |
</div> | |
`; | |
} | |
} | |
// Fade out and remove the loading overlay | |
loadingOverlay.style.opacity = "0"; | |
// Remove overlay from DOM after transition completes | |
setTimeout(() => { | |
document.body.removeChild(loadingOverlay); | |
}, 500); // Matches the transition duration | |
}, 2000); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment