UI Improvements v5

This commit is contained in:
2026-03-08 15:24:47 -04:00
parent b46c70a221
commit c35f312f8c
6 changed files with 143 additions and 22 deletions

View File

@@ -21,6 +21,7 @@ const DESCRIPTIONS = {
logo_dark_url: "Logo image URL for dark mode. Shown in navbar and footer. Leave blank to show site name as text.",
logo_light_url: "Logo image URL for light mode. If not set, the dark logo is used for both themes.",
logo_height: "Logo height in pixels (1680). Applies to both navbar and footer logos.",
logo_use_accent_color: "Tint the logo to match the accent color. Works best with monochrome SVG or PNG logos.",
footer_logo_url: "Override logo for the footer only. If not set, the navbar logo is reused.",
// ── Colors ──
@@ -71,7 +72,9 @@ const DESCRIPTIONS = {
hero_card_enabled: "Display hero content inside a rounded card with border and shadow.",
hero_image_first: "Show hero image above text on mobile / left on desktop. Off = text first.",
hero_background_image_url: "Full-bleed background image behind the hero. In card mode, fills the card with overlay.",
hero_image_urls: "Images for the right side of the hero. One URL per line, up to 5. One is shown randomly per page load. Use the Add Image button or paste URLs.",
hero_image_url: "Single hero image displayed on the right side of the hero. Use the upload button or paste a URL.",
hero_multiple_images_enabled: "Enable multiple hero images (up to 5) that rotate randomly on each page load. Disables the single image upload.",
hero_image_urls: "Images for the right side of the hero. One URL per line, up to 5. One is shown randomly per page load.",
hero_image_max_height: "Maximum height for the hero image in pixels (1001200).",
hero_primary_button_enabled: "Show the primary CTA button in the hero.",
hero_primary_button_label: "Primary button text. Use 'icon | Label' for FA icon before or 'Label | icon' for after (e.g. 'rocket | Get Started').",
@@ -253,7 +256,7 @@ const TABS = [
"community_landing_enabled",
"section_order", "custom_css",
"meta_description", "og_image_url", "favicon_url", "json_ld_enabled",
"logo_dark_url", "logo_light_url", "logo_height", "footer_logo_url",
"logo_dark_url", "logo_light_url", "logo_height", "logo_use_accent_color", "footer_logo_url",
"accent_color", "accent_hover_color", "dark_bg_color", "light_bg_color",
"orb_color", "orb_opacity",
"scroll_animation", "staggered_reveal_enabled", "dynamic_background_enabled",
@@ -280,7 +283,7 @@ const TABS = [
settings: new Set([
"hero_title", "hero_accent_word", "hero_subtitle", "hero_title_size",
"hero_card_enabled", "hero_image_first",
"hero_background_image_url", "hero_image_urls", "hero_image_max_height",
"hero_background_image_url", "hero_image_url", "hero_multiple_images_enabled", "hero_image_urls", "hero_image_max_height",
"hero_primary_button_enabled", "hero_primary_button_label", "hero_primary_button_url",
"hero_secondary_button_enabled", "hero_secondary_button_label", "hero_secondary_button_url",
"hero_primary_btn_color_dark", "hero_primary_btn_color_light",
@@ -415,7 +418,7 @@ const IMAGE_UPLOAD_SETTINGS = {
logo_light_url: { label: "Upload Logo", multi: false },
footer_logo_url: { label: "Upload Logo", multi: false },
hero_background_image_url: { label: "Upload Image", multi: false },
hero_image_urls: { label: "Add Image", multi: true },
hero_image_url: { label: "Upload Image", multi: false },
about_image_url: { label: "Upload Image", multi: false },
about_background_image_url: { label: "Upload Image", multi: false },
ios_app_badge_image_url: { label: "Upload Badge", multi: false },
@@ -510,8 +513,10 @@ function handleTabClick(container, tabId) {
clearDisabledNotice(container);
updateActiveStates(tabId);
applyTabFilter();
applyHeroImageVisibility(container);
injectUploadButtons();
updateDisabledNotice(container);
listenForHeroImageToggle(container);
}
/**
@@ -760,8 +765,10 @@ function buildTabsUI() {
cleanBooleanLabels();
injectUploadButtons();
applyTabFilter();
applyHeroImageVisibility(container);
updateDisabledNotice(container);
listenForEnableToggles(container);
listenForHeroImageToggle(container);
return true;
}
@@ -809,8 +816,10 @@ function buildTabsUI() {
cleanBooleanLabels();
injectUploadButtons();
applyTabFilter();
applyHeroImageVisibility(container);
updateDisabledNotice(container);
listenForEnableToggles(container);
listenForHeroImageToggle(container);
return true;
}
@@ -832,6 +841,48 @@ function listenForEnableToggles(container) {
});
}
// ── Conditional Visibility: Hero Single vs Multi Image ──
const HERO_IMAGE_CONDITIONAL = {
toggle: "hero_multiple_images_enabled",
whenOff: ["hero_image_url"], // single image with upload button
whenOn: ["hero_image_urls"], // multi-image textarea (paste URLs)
};
function applyHeroImageVisibility(container) {
const toggleRow = container.querySelector(
`.row.setting[data-setting="${HERO_IMAGE_CONDITIONAL.toggle}"]`
);
if (!toggleRow) return;
const cb = toggleRow.querySelector('input[type="checkbox"]');
const multiEnabled = cb ? cb.checked : false;
HERO_IMAGE_CONDITIONAL.whenOff.forEach((name) => {
const row = container.querySelector(`.row.setting[data-setting="${name}"]`);
if (row) row.classList.toggle("cl-tab-hidden", multiEnabled);
});
HERO_IMAGE_CONDITIONAL.whenOn.forEach((name) => {
const row = container.querySelector(`.row.setting[data-setting="${name}"]`);
if (row) row.classList.toggle("cl-tab-hidden", !multiEnabled);
});
}
function listenForHeroImageToggle(container) {
const toggleRow = container.querySelector(
`.row.setting[data-setting="${HERO_IMAGE_CONDITIONAL.toggle}"]`
);
if (!toggleRow) return;
const cb = toggleRow.querySelector('input[type="checkbox"]');
if (!cb || cb.dataset.clHeroToggleListening) return;
cb.dataset.clHeroToggleListening = "1";
cb.addEventListener("change", () => {
applyHeroImageVisibility(container);
});
}
// ── Image Upload Helpers ──
function getCsrfToken() {

View File

@@ -128,6 +128,7 @@
/* ── Smooth Scroll ── */
html {
scroll-behavior: smooth;
overflow-x: hidden;
}
/* ── Focus-visible Accessibility ── */
@@ -393,10 +394,12 @@ html {
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
max-width: 100vw;
z-index: 1000;
padding: 0.85rem 0;
transition: all 0.3s ease;
overflow: hidden;
}
.cl-navbar.scrolled {
@@ -422,6 +425,7 @@ html {
.cl-navbar__inner {
max-width: 1200px;
width: 100%;
margin: 0 auto;
padding: 0 1.25rem;
display: flex;
@@ -456,6 +460,24 @@ html {
object-fit: contain;
}
/* Accent-colored logo via CSS mask — hidden <img> inside provides natural dimensions */
.cl-logo--accent {
display: inline-block;
background-color: var(--cl-accent);
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: left center;
mask-position: left center;
}
.cl-logo--accent img {
display: block;
width: auto;
visibility: hidden;
}
.cl-navbar__site-name {
font-size: 1.05rem;
font-weight: 700;
@@ -778,6 +800,7 @@ html {
/* ── Hero Image ── */
.cl-hero__image {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
justify-content: center;
@@ -1010,20 +1033,40 @@ html {
}
.cl-stats__grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
display: flex;
gap: 0.6rem;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scroll-snap-type: x mandatory;
scroll-padding: 0 1rem;
padding-bottom: 4px;
-ms-overflow-style: none;
scrollbar-width: none;
}
.cl-stats__grid::-webkit-scrollbar {
display: none;
}
.cl-stats__grid > * {
flex: 0 0 calc(70% - 0.3rem);
scroll-snap-align: start;
}
@media (min-width: 480px) {
.cl-stats__grid {
grid-template-columns: repeat(3, 1fr);
.cl-stats__grid > * {
flex: 0 0 calc(45% - 0.4rem);
}
}
@media (min-width: 768px) {
.cl-stats__grid {
grid-template-columns: repeat(5, 1fr);
overflow-x: visible;
scroll-snap-type: none;
}
.cl-stats__grid > * {
flex: 1 1 0;
}
}

View File

@@ -19,6 +19,7 @@ en:
logo_dark_url: "━━ BRANDING ━━ — Logo image URL for dark mode. Displayed in the navbar and footer. Leave blank to show the site name as text."
logo_light_url: "Logo image URL for light mode. If not set, the dark logo is used for both themes."
logo_height: "Logo height in pixels (1680). Applies to both the navbar and footer logos."
logo_use_accent_color: "Tint the logo to match the accent color. Works best with monochrome SVG or PNG logos."
footer_logo_url: "Override logo specifically for the footer. If not set, the navbar logo is reused."
# ── Appearance: Color Scheme ──
@@ -65,7 +66,9 @@ en:
hero_card_enabled: "Display the hero content inside a rounded card container with border and shadow. When off, the hero uses a flat full-width layout."
hero_image_first: "Show the hero image above the text on mobile and to the left on desktop. When off, text appears first (default)."
hero_background_image_url: "Full-bleed background image behind the hero section. In card mode, fills the card with a dark overlay. In flat mode, covers the entire section."
hero_image_urls: "Images displayed on the right side of the hero. Add up to 5 URLs — a random one is shown on each page load."
hero_image_url: "Single hero image displayed on the right side of the hero. Use the upload button or paste a URL."
hero_multiple_images_enabled: "Enable multiple hero images (up to 5) that rotate randomly on each page load. Disables the single image upload."
hero_image_urls: "Images displayed on the right side of the hero. One URL per line, up to 5. A random one is shown on each page load."
hero_image_max_height: "Maximum height in pixels for the hero image (1001200)."
hero_primary_button_enabled: "Show the primary CTA button in the hero section."
hero_primary_button_label: "Text on the primary (filled, accent-colored) CTA button."

View File

@@ -50,6 +50,9 @@ plugins:
type: integer
min: 16
max: 80
logo_use_accent_color:
default: false
type: bool
footer_logo_url:
default: ""
type: string
@@ -211,6 +214,12 @@ plugins:
hero_background_image_url:
default: ""
type: string
hero_image_url:
default: ""
type: string
hero_multiple_images_enabled:
default: false
type: bool
hero_image_urls:
default: ""
type: text_area

View File

@@ -27,16 +27,20 @@ module CommunityLanding
parts.any? ? " style=\"#{parts.join(' ')}\"" : ""
end
def logo_img(url, alt, css_class, height)
"<img src=\"#{url}\" alt=\"#{e(alt)}\" class=\"#{css_class}\" style=\"height: #{height}px;\">"
def logo_img(url, alt, css_class, height, accent: false)
if accent
"<span class=\"#{css_class} cl-logo--accent\" style=\"height: #{height}px; -webkit-mask-image: url('#{url}'); mask-image: url('#{url}');\"><img src=\"#{url}\" alt=\"#{e(alt)}\" style=\"height: #{height}px; visibility: hidden;\"></span>"
else
"<img src=\"#{url}\" alt=\"#{e(alt)}\" class=\"#{css_class}\" style=\"height: #{height}px;\">"
end
end
def render_logo(dark_url, light_url, site_name, base_class, height)
def render_logo(dark_url, light_url, site_name, base_class, height, accent: false)
if dark_url && light_url
logo_img(dark_url, site_name, "#{base_class} cl-logo--dark", height) +
logo_img(light_url, site_name, "#{base_class} cl-logo--light", height)
logo_img(dark_url, site_name, "#{base_class} cl-logo--dark", height, accent: accent) +
logo_img(light_url, site_name, "#{base_class} cl-logo--light", height, accent: accent)
else
logo_img(dark_url || light_url, site_name, base_class, height)
logo_img(dark_url || light_url, site_name, base_class, height, accent: accent)
end
end
end

View File

@@ -165,8 +165,9 @@ module CommunityLanding
html << "<div class=\"cl-navbar__inner\">\n"
html << "<div class=\"cl-navbar__left\">"
html << "<a href=\"/\" class=\"cl-navbar__brand\">"
logo_accent = (@s.logo_use_accent_color rescue false)
if has_logo?
html << render_logo(logo_dark_url, logo_light_url, site_name, "cl-navbar__logo", logo_height)
html << render_logo(logo_dark_url, logo_light_url, site_name, "cl-navbar__logo", logo_height, accent: logo_accent)
else
html << "<span class=\"cl-navbar__site-name\">#{e(site_name)}</span>"
end
@@ -291,13 +292,22 @@ module CommunityLanding
html << "</div>\n"
hero_image_urls_raw = (@s.hero_image_urls.presence rescue nil)
hero_multi = (@s.hero_multiple_images_enabled rescue false)
if hero_multi
hero_image_urls_raw = (@s.hero_image_urls.presence rescue nil)
else
hero_image_urls_raw = (@s.hero_image_url.presence rescue nil)
end
hero_video = (@s.hero_video_url.presence rescue nil)
blur_attr = (@s.hero_video_blur_on_hover rescue true) ? " data-blur-hover=\"true\"" : ""
has_images = false
if hero_image_urls_raw
urls = hero_image_urls_raw.split(/[|\n\r]+/).map(&:strip).reject(&:empty?).first(5)
if hero_multi
urls = hero_image_urls_raw.split(/[|\n\r]+/).map(&:strip).reject(&:empty?).first(5)
else
urls = [hero_image_urls_raw.strip]
end
if urls.any?
has_images = true
img_max_h = @s.hero_image_max_height rescue 500
@@ -685,10 +695,11 @@ module CommunityLanding
html << "<div class=\"cl-footer__brand\">"
flogo = @s.footer_logo_url.presence
footer_accent = (@s.logo_use_accent_color rescue false)
if flogo
html << "<img src=\"#{flogo}\" alt=\"#{e(site_name)}\" class=\"cl-footer__logo\" style=\"height: #{logo_height}px;\">"
html << logo_img(flogo, site_name, "cl-footer__logo", logo_height, accent: footer_accent)
elsif has_logo?
html << render_logo(logo_dark_url, logo_light_url, site_name, "cl-footer__logo", logo_height)
html << render_logo(logo_dark_url, logo_light_url, site_name, "cl-footer__logo", logo_height, accent: footer_accent)
else
html << "<span class=\"cl-footer__site-name\">#{e(site_name)}</span>"
end