diff --git a/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js b/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js
index cb53f91..3eaf17c 100644
--- a/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js
+++ b/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js
@@ -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 (16–80). 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 (100–1200).",
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() {
diff --git a/assets/stylesheets/community_landing/landing.css b/assets/stylesheets/community_landing/landing.css
index a282eae..157a532 100644
--- a/assets/stylesheets/community_landing/landing.css
+++ b/assets/stylesheets/community_landing/landing.css
@@ -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 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;
}
}
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 1362526..4842f26 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -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 (16–80). 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 (100–1200)."
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."
diff --git a/config/settings.yml b/config/settings.yml
index 8fecd76..7832c82 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -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
diff --git a/lib/community_landing/helpers.rb b/lib/community_landing/helpers.rb
index 297eb2e..dbf325f 100644
--- a/lib/community_landing/helpers.rb
+++ b/lib/community_landing/helpers.rb
@@ -27,16 +27,20 @@ module CommunityLanding
parts.any? ? " style=\"#{parts.join(' ')}\"" : ""
end
- def logo_img(url, alt, css_class, height)
- "
"
+ def logo_img(url, alt, css_class, height, accent: false)
+ if accent
+ "
"
+ else
+ "
"
+ 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
diff --git a/lib/community_landing/page_builder.rb b/lib/community_landing/page_builder.rb
index b7182c8..2b7c419 100644
--- a/lib/community_landing/page_builder.rb
+++ b/lib/community_landing/page_builder.rb
@@ -165,8 +165,9 @@ module CommunityLanding
html << "