Skip to content

Instantly share code, notes, and snippets.

@edjw
Last active March 19, 2025 22:33
Show Gist options
  • Save edjw/2a0be893bb67e601681f512916a88602 to your computer and use it in GitHub Desktop.
Save edjw/2a0be893bb67e601681f512916a88602 to your computer and use it in GitHub Desktop.
page-builder-prototype
// 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