mirror of
https://github.com/dpnmw/community-landing.git
synced 2026-03-18 09:27:16 +00:00
Major overhaul and options inclusion
This commit is contained in:
@@ -211,4 +211,19 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// 8. FAQ EXCLUSIVE ACCORDION
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
$$("details[data-faq-exclusive]").forEach(function (detail) {
|
||||
detail.addEventListener("toggle", function () {
|
||||
if (detail.open) {
|
||||
$$("details[data-faq-exclusive]").forEach(function (other) {
|
||||
if (other !== detail && other.open) {
|
||||
other.removeAttribute("open");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
@@ -6,6 +6,8 @@ const TABS = [
|
||||
label: "Settings",
|
||||
settings: new Set([
|
||||
"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",
|
||||
"accent_color", "accent_hover_color", "dark_bg_color", "light_bg_color",
|
||||
"scroll_animation", "staggered_reveal_enabled", "dynamic_background_enabled",
|
||||
@@ -20,7 +22,9 @@ const TABS = [
|
||||
"navbar_signin_color_dark", "navbar_signin_color_light",
|
||||
"navbar_join_label", "navbar_join_enabled",
|
||||
"navbar_join_color_dark", "navbar_join_color_light",
|
||||
"navbar_bg_color", "navbar_border_style"
|
||||
"navbar_bg_color", "navbar_border_style",
|
||||
"social_twitter_url", "social_facebook_url", "social_instagram_url",
|
||||
"social_youtube_url", "social_tiktok_url", "social_github_url"
|
||||
])
|
||||
},
|
||||
{
|
||||
@@ -44,6 +48,18 @@ const TABS = [
|
||||
"contributors_days", "contributors_count"
|
||||
])
|
||||
},
|
||||
{
|
||||
id: "participation",
|
||||
label: "Participation",
|
||||
settings: new Set([
|
||||
"participation_enabled", "participation_title_enabled",
|
||||
"participation_title", "participation_bio_max_length",
|
||||
"participation_icon_color",
|
||||
"participation_card_bg_dark", "participation_card_bg_light",
|
||||
"participation_bg_dark", "participation_bg_light",
|
||||
"participation_min_height", "participation_border_style"
|
||||
])
|
||||
},
|
||||
{
|
||||
id: "stats",
|
||||
label: "Stats",
|
||||
@@ -79,12 +95,14 @@ const TABS = [
|
||||
},
|
||||
{
|
||||
id: "groups",
|
||||
label: "Spaces",
|
||||
label: "Spaces & FAQ",
|
||||
settings: new Set([
|
||||
"groups_enabled", "groups_title_enabled", "groups_title", "groups_count",
|
||||
"groups_selected",
|
||||
"groups_show_description", "groups_description_max_length",
|
||||
"groups_card_bg_dark", "groups_card_bg_light",
|
||||
"groups_bg_dark", "groups_bg_light", "groups_min_height", "groups_border_style"
|
||||
"groups_bg_dark", "groups_bg_light", "groups_min_height", "groups_border_style",
|
||||
"faq_enabled", "faq_title_enabled", "faq_title", "faq_items"
|
||||
])
|
||||
},
|
||||
{
|
||||
@@ -95,7 +113,9 @@ const TABS = [
|
||||
"ios_app_badge_image_url", "android_app_badge_image_url",
|
||||
"app_badge_height", "app_badge_style",
|
||||
"app_cta_headline", "app_cta_subtext",
|
||||
"app_cta_gradient_start", "app_cta_gradient_mid", "app_cta_gradient_end",
|
||||
"app_cta_gradient_start_dark", "app_cta_gradient_start_light",
|
||||
"app_cta_gradient_mid_dark", "app_cta_gradient_mid_light",
|
||||
"app_cta_gradient_end_dark", "app_cta_gradient_end_light",
|
||||
"app_cta_image_url",
|
||||
"app_cta_bg_dark", "app_cta_bg_light", "app_cta_min_height", "app_cta_border_style"
|
||||
])
|
||||
@@ -121,6 +141,9 @@ const BG_PAIRS = [
|
||||
["hero_bg_dark", "hero_bg_light"],
|
||||
["hero_card_bg_dark", "hero_card_bg_light"],
|
||||
["contributors_pill_bg_dark", "contributors_pill_bg_light"],
|
||||
// Participation
|
||||
["participation_card_bg_dark", "participation_card_bg_light"],
|
||||
["participation_bg_dark", "participation_bg_light"],
|
||||
// Stats
|
||||
["stat_card_bg_dark", "stat_card_bg_light"],
|
||||
["stats_bg_dark", "stats_bg_light"],
|
||||
@@ -134,6 +157,9 @@ const BG_PAIRS = [
|
||||
["groups_card_bg_dark", "groups_card_bg_light"],
|
||||
["groups_bg_dark", "groups_bg_light"],
|
||||
// App CTA
|
||||
["app_cta_gradient_start_dark", "app_cta_gradient_start_light"],
|
||||
["app_cta_gradient_mid_dark", "app_cta_gradient_mid_light"],
|
||||
["app_cta_gradient_end_dark", "app_cta_gradient_end_light"],
|
||||
["app_cta_bg_dark", "app_cta_bg_light"],
|
||||
// Footer
|
||||
["footer_bg_dark", "footer_bg_light"],
|
||||
@@ -159,8 +185,8 @@ function applyTabFilter() {
|
||||
if (!tab) return;
|
||||
|
||||
container.querySelectorAll(".row.setting[data-setting]").forEach((row) => {
|
||||
// Keep merged light rows permanently hidden
|
||||
if (row.classList.contains("cl-merged-hidden")) return;
|
||||
// Skip rows inside a merge wrapper — handled at wrapper level
|
||||
if (row.closest(".cl-merge-wrapper")) return;
|
||||
const name = row.getAttribute("data-setting");
|
||||
row.classList.toggle(
|
||||
"cl-tab-hidden",
|
||||
@@ -168,6 +194,17 @@ function applyTabFilter() {
|
||||
);
|
||||
});
|
||||
|
||||
// Handle merge wrappers — show/hide based on dark row's setting
|
||||
container.querySelectorAll(".cl-merge-wrapper").forEach((wrapper) => {
|
||||
const darkRow = wrapper.querySelector(".cl-merged-dark");
|
||||
if (!darkRow) return;
|
||||
const name = darkRow.getAttribute("data-setting");
|
||||
wrapper.classList.toggle(
|
||||
"cl-tab-hidden",
|
||||
!filterActive && !tab.settings.has(name)
|
||||
);
|
||||
});
|
||||
|
||||
// Update filter-active dimming on native nav or standalone tab bar
|
||||
const nativeNav = document.querySelector(".d-nav-submenu__tabs");
|
||||
if (nativeNav) {
|
||||
@@ -256,6 +293,17 @@ function cleanupTabs() {
|
||||
container.querySelectorAll(".cl-tab-hidden").forEach((el) => {
|
||||
el.classList.remove("cl-tab-hidden");
|
||||
});
|
||||
|
||||
// Unwrap merge wrappers — restore rows to their original position
|
||||
container.querySelectorAll(".cl-merge-wrapper").forEach((wrapper) => {
|
||||
const parent = wrapper.parentNode;
|
||||
while (wrapper.firstChild) {
|
||||
const child = wrapper.firstChild;
|
||||
child.classList.remove("cl-merged-dark", "cl-merged-light");
|
||||
parent.insertBefore(child, wrapper);
|
||||
}
|
||||
wrapper.remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Reset state
|
||||
@@ -264,8 +312,9 @@ function cleanupTabs() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge dark/light bg color pairs into a single row.
|
||||
* Moves the light setting-value into the dark row and hides the light row.
|
||||
* Merge dark/light bg color pairs into a single visual row.
|
||||
* Uses a CSS wrapper approach — both rows stay intact in the DOM
|
||||
* (preserving Ember bindings and undo/reset buttons).
|
||||
*/
|
||||
function mergeBgPairs() {
|
||||
const container = getContainer();
|
||||
@@ -276,55 +325,41 @@ function mergeBgPairs() {
|
||||
const lightRow = container.querySelector(`.row.setting[data-setting="${lightName}"]`);
|
||||
if (!darkRow || !lightRow) return;
|
||||
// Already merged
|
||||
if (darkRow.querySelector(".cl-merged-value")) return;
|
||||
if (darkRow.classList.contains("cl-merged-dark")) return;
|
||||
|
||||
const lightValue = lightRow.querySelector(".setting-value");
|
||||
const lightLabel = lightRow.querySelector(".setting-label");
|
||||
if (!lightValue) return;
|
||||
|
||||
// Rename the dark row label to just show the base name (e.g. "Hero BG" instead of "Hero BG dark")
|
||||
// Rename the dark row label (remove " dark" suffix)
|
||||
const darkH3 = darkRow.querySelector(".setting-label h3");
|
||||
if (darkH3) {
|
||||
darkH3.textContent = darkH3.textContent.replace(/\s*dark$/i, "").trim();
|
||||
}
|
||||
|
||||
// Create a wrapper that holds both color pickers side by side
|
||||
// Add "Dark" / "Light" labels to each row's setting-value
|
||||
const darkValue = darkRow.querySelector(".setting-value");
|
||||
if (!darkValue) return;
|
||||
const lightValue = lightRow.querySelector(".setting-value");
|
||||
if (darkValue && !darkValue.querySelector(".cl-color-col__label")) {
|
||||
const lbl = document.createElement("span");
|
||||
lbl.className = "cl-color-col__label";
|
||||
lbl.textContent = "Dark";
|
||||
darkValue.insertBefore(lbl, darkValue.firstChild);
|
||||
}
|
||||
if (lightValue && !lightValue.querySelector(".cl-color-col__label")) {
|
||||
const lbl = document.createElement("span");
|
||||
lbl.className = "cl-color-col__label";
|
||||
lbl.textContent = "Light";
|
||||
lightValue.insertBefore(lbl, lightValue.firstChild);
|
||||
}
|
||||
|
||||
// Wrap existing dark value
|
||||
// Wrap both rows in a flex container
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "cl-merged-value";
|
||||
wrapper.className = "cl-merge-wrapper";
|
||||
darkRow.parentNode.insertBefore(wrapper, darkRow);
|
||||
wrapper.appendChild(darkRow);
|
||||
wrapper.appendChild(lightRow);
|
||||
|
||||
const darkCol = document.createElement("div");
|
||||
darkCol.className = "cl-color-col";
|
||||
const darkLbl = document.createElement("span");
|
||||
darkLbl.className = "cl-color-col__label";
|
||||
darkLbl.textContent = "Dark";
|
||||
darkCol.appendChild(darkLbl);
|
||||
// Move dark value's children into the column
|
||||
while (darkValue.firstChild) {
|
||||
darkCol.appendChild(darkValue.firstChild);
|
||||
}
|
||||
|
||||
const lightCol = document.createElement("div");
|
||||
lightCol.className = "cl-color-col";
|
||||
const lightLbl = document.createElement("span");
|
||||
lightLbl.className = "cl-color-col__label";
|
||||
lightLbl.textContent = "Light";
|
||||
lightCol.appendChild(lightLbl);
|
||||
// Move light value's children into the column
|
||||
while (lightValue.firstChild) {
|
||||
lightCol.appendChild(lightValue.firstChild);
|
||||
}
|
||||
|
||||
wrapper.appendChild(darkCol);
|
||||
wrapper.appendChild(lightCol);
|
||||
darkValue.appendChild(wrapper);
|
||||
|
||||
// Hide the now-empty light row permanently
|
||||
lightRow.classList.add("cl-merged-hidden");
|
||||
lightRow.style.display = "none";
|
||||
// Mark rows for CSS styling
|
||||
darkRow.classList.add("cl-merged-dark");
|
||||
lightRow.classList.add("cl-merged-light");
|
||||
// Light row is NOT hidden — it stays in the DOM with full Ember bindings
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -82,16 +82,35 @@ html.dark-scheme .cl-admin-tabs .cl-admin-tab:hover {
|
||||
color: var(--primary, #ddd);
|
||||
}
|
||||
|
||||
/* ── Merged dark/light color pairs (two pickers in one row) ── */
|
||||
/* ── Merged dark/light color pairs (wrapper approach) ── */
|
||||
|
||||
.cl-merged-value {
|
||||
.cl-merge-wrapper {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.cl-color-col {
|
||||
.cl-merge-wrapper > .row.setting {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-bottom: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* Hide the light row's label + description — dark row's label covers both */
|
||||
.cl-merge-wrapper > .cl-merged-light > .setting-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cl-merge-wrapper > .cl-merged-light .desc {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Light row's value area fills the full width since label is hidden */
|
||||
.cl-tabs-active .cl-merge-wrapper > .cl-merged-light > .setting-value {
|
||||
width: 100%;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.cl-color-col__label {
|
||||
@@ -105,9 +124,13 @@ html.dark-scheme .cl-admin-tabs .cl-admin-tab:hover {
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.cl-merged-value {
|
||||
.cl-merge-wrapper {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.cl-merge-wrapper > .cl-merged-light > .setting-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +148,12 @@ html.dark-scheme .cl-admin-tabs .cl-admin-tab:hover {
|
||||
|
||||
/* All plugin settings spacing */
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="community_landing_enabled"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="section_order"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="custom_css"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="meta_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="og_image_url"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="favicon_url"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="json_ld_enabled"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="logo_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="accent_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="dark_bg_color"],
|
||||
@@ -137,25 +166,34 @@ html.dark-scheme .cl-admin-tabs .cl-admin-tab:hover {
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="about_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="topics_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="contributors_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="participation_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="groups_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="faq_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="show_app_ctas"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="ios_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="android_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="app_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="social_"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting^="footer_"] {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Section separator borders (fallback only) */
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="section_order"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="custom_css"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="meta_description"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="logo_dark_url"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="accent_color"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="scroll_animation"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="social_twitter_url"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="navbar_signin_label"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="hero_title"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="stats_enabled"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="about_enabled"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="participation_enabled"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="topics_enabled"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="groups_enabled"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="faq_enabled"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="show_app_ctas"],
|
||||
.admin-detail:not(.cl-tabs-active) .row.setting[data-setting="footer_description"] {
|
||||
border-top: 2px solid rgba(0, 0, 0, 0.12);
|
||||
@@ -164,18 +202,23 @@ html.dark-scheme .cl-admin-tabs .cl-admin-tab:hover {
|
||||
}
|
||||
|
||||
/* Dark mode separators (fallback only) */
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="section_order"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="custom_css"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="meta_description"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="logo_dark_url"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="accent_color"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="scroll_animation"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="social_twitter_url"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="navbar_signin_label"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="hero_title"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="stats_enabled"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="about_enabled"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="participation_enabled"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="topics_enabled"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="groups_enabled"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="faq_enabled"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="show_app_ctas"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="footer_description"],
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="custom_css"] {
|
||||
html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="footer_description"] {
|
||||
border-top-color: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
|
||||
@@ -125,10 +125,49 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ── Smooth Scroll ── */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* ── Focus-visible Accessibility ── */
|
||||
.cl-body a:focus-visible,
|
||||
.cl-body button:focus-visible,
|
||||
.cl-body [role="button"]:focus-visible,
|
||||
.cl-body summary:focus-visible {
|
||||
outline: 2px solid var(--cl-accent);
|
||||
outline-offset: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.cl-body .cl-btn:focus-visible {
|
||||
outline: 2px solid var(--cl-accent);
|
||||
outline-offset: 3px;
|
||||
box-shadow: 0 0 0 4px var(--cl-accent-glow);
|
||||
}
|
||||
|
||||
.cl-body .cl-topic-card:focus-visible,
|
||||
.cl-body .cl-space-card:focus-visible,
|
||||
.cl-body .cl-participation-card:focus-visible,
|
||||
.cl-body .cl-stat-card:focus-visible,
|
||||
.cl-body .cl-creator-pill:focus-visible {
|
||||
outline: 2px solid var(--cl-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.cl-body .cl-theme-toggle:focus-visible {
|
||||
outline: 2px solid var(--cl-accent);
|
||||
outline-offset: 2px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.cl-body .cl-navbar__hamburger:focus-visible {
|
||||
outline: 2px solid var(--cl-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ── Background FX ── */
|
||||
.cl-orb-container {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
@@ -394,6 +433,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cl-navbar__brand {
|
||||
@@ -413,6 +454,9 @@
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.cl-navbar__right {
|
||||
@@ -430,6 +474,7 @@
|
||||
.cl-navbar__hamburger {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
gap: 4px;
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -572,6 +617,49 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Social Icons ── */
|
||||
.cl-social-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.cl-social-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
color: var(--cl-muted);
|
||||
transition: color 0.2s, background 0.2s;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.cl-social-icon:hover {
|
||||
color: var(--cl-accent);
|
||||
background: var(--cl-accent-subtle);
|
||||
}
|
||||
|
||||
.cl-social-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.cl-navbar__mobile-menu .cl-social-icons {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.cl-navbar__mobile-menu .cl-social-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.cl-navbar__mobile-menu .cl-social-icon svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* Logo theme switching */
|
||||
.cl-logo--light {
|
||||
display: none;
|
||||
@@ -737,7 +825,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8px 32px var(--cl-accent-glow), 0 0 0 0 var(--cl-accent-glow);
|
||||
--_play-glow: var(--cl-video-btn-glow, var(--cl-accent-glow));
|
||||
box-shadow: 0 8px 32px var(--_play-glow), 0 0 0 0 var(--_play-glow);
|
||||
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
animation: cl-play-pulse 2s infinite;
|
||||
}
|
||||
@@ -745,7 +834,7 @@
|
||||
.cl-hero-play:hover {
|
||||
transform: translate(-50%, -50%) scale(1.1);
|
||||
background: var(--cl-video-btn-bg, var(--cl-accent-hover));
|
||||
box-shadow: 0 12px 40px var(--cl-accent-glow);
|
||||
box-shadow: 0 12px 40px var(--_play-glow);
|
||||
}
|
||||
|
||||
.cl-hero-play__icon {
|
||||
@@ -761,8 +850,8 @@
|
||||
}
|
||||
|
||||
@keyframes cl-play-pulse {
|
||||
0%, 100% { box-shadow: 0 8px 32px var(--cl-accent-glow), 0 0 0 0 var(--cl-accent-glow); }
|
||||
50% { box-shadow: 0 8px 32px var(--cl-accent-glow), 0 0 0 16px rgba(212, 162, 78, 0); }
|
||||
0%, 100% { box-shadow: 0 8px 32px var(--_play-glow), 0 0 0 0 var(--_play-glow); }
|
||||
50% { box-shadow: 0 8px 32px var(--_play-glow), 0 0 0 16px transparent; }
|
||||
}
|
||||
|
||||
.cl-hero__image--video-only {
|
||||
@@ -1163,6 +1252,105 @@
|
||||
color: var(--cl-muted);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
5b. PARTICIPATION — testimonial-style bio cards
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
.cl-participation {
|
||||
padding: 4rem 0;
|
||||
}
|
||||
|
||||
.cl-participation__grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.cl-participation__grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.cl-participation__grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.cl-participation-card {
|
||||
background: var(--cl-participation-card-bg, var(--cl-card));
|
||||
border: 1px solid var(--cl-border);
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1),
|
||||
box-shadow 0.3s cubic-bezier(0.16, 1, 0.3, 1),
|
||||
border-color 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.cl-participation-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
border-color: var(--cl-border-hover);
|
||||
}
|
||||
|
||||
.cl-participation-card__quote {
|
||||
color: var(--cl-participation-icon-color, var(--cl-accent));
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.cl-participation-card__quote svg {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.cl-participation-card__bio {
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.65;
|
||||
color: var(--cl-text);
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cl-participation-card__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--cl-border);
|
||||
}
|
||||
|
||||
.cl-participation-card__avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--cl-participation-icon-color, var(--cl-accent));
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cl-participation-card__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.cl-participation-card__name {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: var(--cl-text-strong);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.cl-participation-card__count {
|
||||
font-size: 0.8rem;
|
||||
color: var(--cl-participation-icon-color, var(--cl-accent));
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
5. TRENDING DISCUSSIONS — 4-per-row grid
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
@@ -1349,36 +1537,49 @@
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
7. COMMUNITY SPACES — compact horizontal pills with accent stripe
|
||||
7. COMMUNITY SPACES + FAQ — split layout
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
.cl-spaces {
|
||||
padding: 1.5rem 0 2rem;
|
||||
padding: 2.5rem 0 3rem;
|
||||
}
|
||||
|
||||
/* ── Split layout: groups left, FAQ right ── */
|
||||
.cl-spaces__split {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
align-items: start;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.cl-spaces__full {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cl-spaces__full .cl-faq {
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
/* ── Groups grid (2×2 inside left column) ── */
|
||||
.cl-spaces__grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
@media (max-width: 479px) {
|
||||
.cl-spaces__grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.cl-spaces__grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Group cards ── */
|
||||
.cl-space-card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
padding: 0.6rem 1rem 0.6rem 0.6rem;
|
||||
padding: 0.8rem 1rem;
|
||||
background: var(--cl-space-card-bg, var(--cl-card));
|
||||
border: 1px solid var(--cl-border);
|
||||
border-left: 3px solid var(--space-color);
|
||||
@@ -1408,6 +1609,7 @@
|
||||
flex-shrink: 0;
|
||||
background: var(--space-color);
|
||||
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.cl-space-card:hover .cl-space-card__icon {
|
||||
@@ -1427,7 +1629,6 @@
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Card body */
|
||||
.cl-space-card__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1451,6 +1652,109 @@
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.cl-space-card__desc {
|
||||
margin: 0.25rem 0 0;
|
||||
font-size: 0.78rem;
|
||||
color: var(--cl-text);
|
||||
line-height: 1.45;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── FAQ Accordion ── */
|
||||
.cl-faq {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.cl-faq__title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 800;
|
||||
color: var(--cl-text-strong);
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
.cl-faq__item {
|
||||
border-bottom: 1px solid var(--cl-border);
|
||||
}
|
||||
|
||||
.cl-faq__item:first-of-type {
|
||||
border-top: 1px solid var(--cl-border);
|
||||
}
|
||||
|
||||
.cl-faq__question {
|
||||
padding: 1rem 2rem 1rem 0;
|
||||
font-size: 0.92rem;
|
||||
font-weight: 700;
|
||||
color: var(--cl-text-strong);
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
position: relative;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.cl-faq__question::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cl-faq__question::marker {
|
||||
display: none;
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* Chevron indicator */
|
||||
.cl-faq__question::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-right: 2px solid var(--cl-muted);
|
||||
border-bottom: 2px solid var(--cl-muted);
|
||||
transform: translateY(-60%) rotate(45deg);
|
||||
transition: transform 0.3s ease, border-color 0.2s;
|
||||
}
|
||||
|
||||
.cl-faq__item[open] > .cl-faq__question::after {
|
||||
transform: translateY(-30%) rotate(-135deg);
|
||||
border-color: var(--cl-accent);
|
||||
}
|
||||
|
||||
.cl-faq__question:hover {
|
||||
color: var(--cl-accent);
|
||||
}
|
||||
|
||||
.cl-faq__answer {
|
||||
padding: 0 0 1rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--cl-text);
|
||||
line-height: 1.7;
|
||||
animation: cl-faq-open 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes cl-faq-open {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Mobile: stack split vertically ── */
|
||||
@media (max-width: 767px) {
|
||||
.cl-spaces__split {
|
||||
grid-template-columns: 1fr;
|
||||
min-height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
8. APP CTA — split layout with gradient
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
@@ -1710,6 +2014,10 @@
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
.cl-anim,
|
||||
.cl-hero__content,
|
||||
.cl-hero__image,
|
||||
@@ -1726,4 +2034,8 @@
|
||||
.cl-hero-play {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.cl-faq__answer {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,18 @@ en:
|
||||
# ── Master Switch ──
|
||||
community_landing_enabled: "Enable the community landing page."
|
||||
|
||||
# ── Layout ──
|
||||
section_order: "━━ LAYOUT ━━ — Order of content sections, pipe-separated. IDs: hero, stats, about, participation, topics, groups, app_cta. Navbar and footer are always fixed."
|
||||
|
||||
# ── Custom CSS ──
|
||||
custom_css: "━━ CUSTOM CSS ━━ — Raw CSS injected after all plugin styles. Use for overrides and tweaks. No style tags needed."
|
||||
|
||||
# ── SEO & Meta ──
|
||||
meta_description: "━━ SEO & META ━━ — Custom meta description for search engines and social sharing. If blank, the hero subtitle is used."
|
||||
og_image_url: "Custom Open Graph image URL for social sharing (1200×630px recommended). If blank, the site logo is used."
|
||||
favicon_url: "Custom favicon URL (.ico, .png, .svg). If blank, the browser default is used."
|
||||
json_ld_enabled: "Add JSON-LD structured data (Organization + WebSite schema) for search engines."
|
||||
|
||||
# ── Branding: Logo ──
|
||||
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."
|
||||
@@ -29,6 +41,12 @@ en:
|
||||
navbar_join_color_light: "Light mode background for the join button."
|
||||
navbar_bg_color: "Custom background color for the navbar when scrolled. Leave blank for the default frosted glass effect."
|
||||
navbar_border_style: "Border style at the bottom of the navbar when scrolled."
|
||||
social_twitter_url: "━━ SOCIAL LINKS ━━ — Twitter / X profile URL. Leave blank to hide. Icons appear in the navbar before the auth buttons."
|
||||
social_facebook_url: "Facebook page or profile URL. Leave blank to hide."
|
||||
social_instagram_url: "Instagram profile URL. Leave blank to hide."
|
||||
social_youtube_url: "YouTube channel URL. Leave blank to hide."
|
||||
social_tiktok_url: "TikTok profile URL. Leave blank to hide."
|
||||
social_github_url: "GitHub organization or profile URL. Leave blank to hide."
|
||||
|
||||
# ── 2. Hero Section ──
|
||||
hero_title: "━━ ROW 2: HERO ━━ — Large welcome area at the top with headline, subtitle, CTA buttons, and optional imagery. This is the main headline text."
|
||||
@@ -122,7 +140,20 @@ en:
|
||||
contributors_pill_bg_dark: "Creator pill background color. Dark (left) and light (right) pickers. Leave blank for default glass styling."
|
||||
contributors_pill_bg_light: "Light mode background for creator pills."
|
||||
contributors_days: "Lookback period in days for calculating top contributors."
|
||||
contributors_count: "Number of top contributors to fetch (top 3 are shown in the hero)."
|
||||
contributors_count: "Number of top contributors to fetch (top 3 are shown in the hero, 4–10 appear in the Participation section)."
|
||||
|
||||
# ── 5b. Participation ──
|
||||
participation_enabled: "━━ ROW 5b: PARTICIPATION ━━ — Show the Participation section: testimonial-style cards displaying leaderboard positions 4–10 with their public bio/summary. Only users who have written a bio are shown. Uses the same contributor data and lookback period as the Hero Creators."
|
||||
participation_title_enabled: "Show the section heading above the participation cards."
|
||||
participation_title: "Heading text above the participation cards."
|
||||
participation_bio_max_length: "Maximum number of characters to show from each user's bio (50–500). Longer bios are truncated with an ellipsis."
|
||||
participation_icon_color: "Color for the decorative quote icon on each participation card. Leave blank to use the accent color."
|
||||
participation_card_bg_dark: "Participation card background color. Dark (left) and light (right) pickers. Leave blank for default card styling."
|
||||
participation_card_bg_light: "Light mode background for participation cards."
|
||||
participation_bg_dark: "Section background color override. Dark (left) and light (right) color pickers. Leave blank for default."
|
||||
participation_bg_light: "Light mode background for the participation section."
|
||||
participation_min_height: "Minimum height for the participation section in pixels. Set to 0 for auto height."
|
||||
participation_border_style: "Border style at the bottom of the participation section."
|
||||
|
||||
# ── 7. Community Spaces ──
|
||||
groups_enabled: "━━ ROW 7: SPACES ━━ — Show the Community Spaces section: a grid of colorful cards representing your public groups. Each card shows a colored icon (with group's first letter or flair), group name, and member count. Only public, non-automatic groups are shown."
|
||||
@@ -136,6 +167,14 @@ en:
|
||||
groups_bg_light: "Light mode background for the spaces section."
|
||||
groups_min_height: "Minimum height for the spaces section in pixels. Set to 0 for auto height."
|
||||
groups_border_style: "Border style at the bottom of the spaces section."
|
||||
groups_show_description: "Show group description text (from the group's bio) below the group name on each card."
|
||||
groups_description_max_length: "Maximum characters for group description text (30–500). Longer descriptions are truncated."
|
||||
|
||||
# ── 7b. FAQ Accordion ──
|
||||
faq_enabled: "━━ FAQ ACCORDION ━━ — Show an FAQ accordion alongside the Spaces section. Only one item opens at a time."
|
||||
faq_title_enabled: "Show a heading above the FAQ accordion."
|
||||
faq_title: "Heading text above the FAQ accordion."
|
||||
faq_items: 'FAQ items as a JSON array. Format: [{"q":"Question","a":"Answer"}]. HTML is supported in answers.'
|
||||
|
||||
# ── 8. App Download CTA ──
|
||||
show_app_ctas: "━━ ROW 8: APP CTA ━━ — Show the App Download CTA: a gradient banner promoting your mobile app with headline, subtitle, download badges (App Store / Google Play), and optional promotional image. Requires at least one app store URL."
|
||||
@@ -147,9 +186,12 @@ en:
|
||||
app_badge_style: "Badge border-radius: rounded (soft corners), pill (fully rounded), or square (minimal rounding)."
|
||||
app_cta_headline: "Bold headline text in the app download banner."
|
||||
app_cta_subtext: "Supporting text below the headline."
|
||||
app_cta_gradient_start: "First color (left) of the app CTA 3-color gradient. Hex value."
|
||||
app_cta_gradient_mid: "Middle color of the app CTA gradient. Hex value."
|
||||
app_cta_gradient_end: "Third color (right) of the app CTA gradient. Hex value."
|
||||
app_cta_gradient_start_dark: "Gradient start color. Dark (left) and light (right) pickers. Leave blank for accent color."
|
||||
app_cta_gradient_start_light: "Light mode gradient start color."
|
||||
app_cta_gradient_mid_dark: "Gradient middle color. Dark (left) and light (right) pickers. Leave blank for accent hover color."
|
||||
app_cta_gradient_mid_light: "Light mode gradient middle color."
|
||||
app_cta_gradient_end_dark: "Gradient end color. Dark (left) and light (right) pickers. Leave blank for accent hover color."
|
||||
app_cta_gradient_end_light: "Light mode gradient end color."
|
||||
app_cta_image_url: "Promotional image on the right side of the CTA (e.g. phone mockup). PNG for transparent backgrounds."
|
||||
app_cta_bg_dark: "Section background color override. Dark (left) and light (right) color pickers. Leave blank for default."
|
||||
app_cta_bg_light: "Light mode background for the app CTA section."
|
||||
|
||||
@@ -6,6 +6,36 @@ plugins:
|
||||
default: true
|
||||
type: bool
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# Layout & Section Order
|
||||
# ══════════════════════════════════════════
|
||||
section_order:
|
||||
default: "hero|stats|about|participation|topics|groups|app_cta"
|
||||
type: string
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# Custom CSS
|
||||
# ══════════════════════════════════════════
|
||||
custom_css:
|
||||
default: ""
|
||||
type: text_area
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# SEO & Meta
|
||||
# ══════════════════════════════════════════
|
||||
meta_description:
|
||||
default: ""
|
||||
type: string
|
||||
og_image_url:
|
||||
default: ""
|
||||
type: string
|
||||
favicon_url:
|
||||
default: ""
|
||||
type: string
|
||||
json_ld_enabled:
|
||||
default: true
|
||||
type: bool
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# Branding: Logo
|
||||
# ══════════════════════════════════════════
|
||||
@@ -109,6 +139,24 @@ plugins:
|
||||
- solid
|
||||
- dashed
|
||||
- dotted
|
||||
social_twitter_url:
|
||||
default: ""
|
||||
type: string
|
||||
social_facebook_url:
|
||||
default: ""
|
||||
type: string
|
||||
social_instagram_url:
|
||||
default: ""
|
||||
type: string
|
||||
social_youtube_url:
|
||||
default: ""
|
||||
type: string
|
||||
social_tiktok_url:
|
||||
default: ""
|
||||
type: string
|
||||
social_github_url:
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# 2. Hero Section
|
||||
@@ -427,6 +475,52 @@ plugins:
|
||||
default: 10
|
||||
type: integer
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# 5b. Participation (Leaderboard Bios)
|
||||
# ══════════════════════════════════════════
|
||||
participation_enabled:
|
||||
default: true
|
||||
type: bool
|
||||
participation_title_enabled:
|
||||
default: true
|
||||
type: bool
|
||||
participation_title:
|
||||
default: "Participation"
|
||||
type: string
|
||||
participation_bio_max_length:
|
||||
default: 150
|
||||
type: integer
|
||||
min: 50
|
||||
max: 500
|
||||
participation_icon_color:
|
||||
default: ""
|
||||
type: color
|
||||
participation_card_bg_dark:
|
||||
default: ""
|
||||
type: color
|
||||
participation_card_bg_light:
|
||||
default: ""
|
||||
type: color
|
||||
participation_bg_dark:
|
||||
default: ""
|
||||
type: color
|
||||
participation_bg_light:
|
||||
default: ""
|
||||
type: color
|
||||
participation_min_height:
|
||||
default: 0
|
||||
type: integer
|
||||
min: 0
|
||||
max: 2000
|
||||
participation_border_style:
|
||||
default: "none"
|
||||
type: enum
|
||||
choices:
|
||||
- none
|
||||
- solid
|
||||
- dashed
|
||||
- dotted
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# 7. Community Spaces Section
|
||||
# ══════════════════════════════════════════
|
||||
@@ -470,6 +564,30 @@ plugins:
|
||||
- solid
|
||||
- dashed
|
||||
- dotted
|
||||
groups_show_description:
|
||||
default: true
|
||||
type: bool
|
||||
groups_description_max_length:
|
||||
default: 100
|
||||
type: integer
|
||||
min: 30
|
||||
max: 500
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# 7b. FAQ Accordion
|
||||
# ══════════════════════════════════════════
|
||||
faq_enabled:
|
||||
default: false
|
||||
type: bool
|
||||
faq_title_enabled:
|
||||
default: true
|
||||
type: bool
|
||||
faq_title:
|
||||
default: "Frequently Asked Questions"
|
||||
type: string
|
||||
faq_items:
|
||||
default: '[{"q":"What is this community about?","a":"A creative community focused on sharing knowledge and building together."},{"q":"How do I join?","a":"Click the Get Started button to create your free account."},{"q":"Is it free?","a":"Yes! Basic membership is completely free."}]'
|
||||
type: text_area
|
||||
|
||||
# ══════════════════════════════════════════
|
||||
# 8. App Download CTA Section
|
||||
@@ -507,15 +625,24 @@ plugins:
|
||||
app_cta_subtext:
|
||||
default: "Available free on iOS and Android"
|
||||
type: string
|
||||
app_cta_gradient_start:
|
||||
app_cta_gradient_start_dark:
|
||||
default: "d4a24e"
|
||||
type: color
|
||||
app_cta_gradient_mid:
|
||||
app_cta_gradient_start_light:
|
||||
default: ""
|
||||
type: color
|
||||
app_cta_gradient_mid_dark:
|
||||
default: "c4922e"
|
||||
type: color
|
||||
app_cta_gradient_end:
|
||||
app_cta_gradient_mid_light:
|
||||
default: ""
|
||||
type: color
|
||||
app_cta_gradient_end_dark:
|
||||
default: "b8862e"
|
||||
type: color
|
||||
app_cta_gradient_end_light:
|
||||
default: ""
|
||||
type: color
|
||||
app_cta_image_url:
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
@@ -11,6 +11,7 @@ module CommunityLanding
|
||||
if s.contributors_enabled
|
||||
User
|
||||
.joins(:posts)
|
||||
.includes(:user_profile)
|
||||
.where(posts: { created_at: s.contributors_days.days.ago.. })
|
||||
.where.not(username: %w[system discobot])
|
||||
.where(active: true, staged: false)
|
||||
|
||||
@@ -17,6 +17,14 @@ module CommunityLanding
|
||||
COMMENT_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>'
|
||||
HEART_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>'
|
||||
|
||||
# Social media icons (18×18, fill currentColor)
|
||||
SOCIAL_TWITTER_SVG = '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'
|
||||
SOCIAL_FACEBOOK_SVG = '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'
|
||||
SOCIAL_INSTAGRAM_SVG = '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'
|
||||
SOCIAL_YOUTUBE_SVG = '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>'
|
||||
SOCIAL_TIKTOK_SVG = '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg>'
|
||||
SOCIAL_GITHUB_SVG = '<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'
|
||||
|
||||
IOS_BADGE_SVG = '<svg viewBox="0 0 24 24" fill="currentColor" width="20" height="20"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>'
|
||||
ANDROID_BADGE_SVG = '<svg viewBox="0 0 24 24" fill="currentColor" width="20" height="20"><path d="M17.6 11.48l1.56-2.7a.43.43 0 00-.16-.59.43.43 0 00-.59.16l-1.58 2.73A9.9 9.9 0 0012 10.07a9.9 9.9 0 00-4.83 1.01L5.59 8.35a.43.43 0 00-.59-.16.43.43 0 00-.16.59l1.56 2.7A10.16 10.16 0 002 18h20a10.16 10.16 0 00-4.4-6.52zM7 15.5a1 1 0 110-2 1 1 0 010 2zm10 0a1 1 0 110-2 1 1 0 010 2z"/></svg>'
|
||||
end
|
||||
|
||||
@@ -4,6 +4,16 @@ module CommunityLanding
|
||||
class PageBuilder
|
||||
include Helpers
|
||||
|
||||
SECTION_MAP = {
|
||||
"hero" => :render_hero,
|
||||
"stats" => :render_stats,
|
||||
"about" => :render_about,
|
||||
"participation" => :render_participation,
|
||||
"topics" => :render_topics,
|
||||
"groups" => :render_groups,
|
||||
"app_cta" => :render_app_cta,
|
||||
}.freeze
|
||||
|
||||
def initialize(data:, css:, js:)
|
||||
@data = data
|
||||
@css = css
|
||||
@@ -20,12 +30,14 @@ module CommunityLanding
|
||||
html << "<div class=\"cl-orb-container\"><div class=\"cl-orb cl-orb--1\"></div><div class=\"cl-orb cl-orb--2\"></div></div>\n"
|
||||
end
|
||||
html << render_navbar
|
||||
html << render_hero
|
||||
html << render_stats
|
||||
html << render_about
|
||||
html << render_topics
|
||||
html << render_groups
|
||||
html << render_app_cta
|
||||
|
||||
# Reorderable sections
|
||||
order = (@s.section_order.presence rescue nil) || "hero|stats|about|participation|topics|groups|app_cta"
|
||||
order.split("|").map(&:strip).each do |section_id|
|
||||
method_name = SECTION_MAP[section_id]
|
||||
html << send(method_name) if method_name
|
||||
end
|
||||
|
||||
html << render_footer_desc
|
||||
html << render_footer
|
||||
html << render_video_modal
|
||||
@@ -43,6 +55,12 @@ module CommunityLanding
|
||||
anim_class = @s.scroll_animation rescue "fade_up"
|
||||
anim_class = "none" if anim_class.blank?
|
||||
og_logo = logo_dark_url || logo_light_url
|
||||
base_url = Discourse.base_url
|
||||
|
||||
# SEO overrides
|
||||
meta_desc = (@s.meta_description.presence rescue nil) || @s.hero_subtitle
|
||||
og_image = (@s.og_image_url.presence rescue nil) || og_logo
|
||||
favicon = (@s.favicon_url.presence rescue nil)
|
||||
|
||||
html = +""
|
||||
html << "<!DOCTYPE html>\n<html lang=\"en\""
|
||||
@@ -55,18 +73,52 @@ module CommunityLanding
|
||||
html << "<link href=\"https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap\" rel=\"stylesheet\">\n"
|
||||
html << "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, viewport-fit=cover\">\n"
|
||||
html << "<meta name=\"color-scheme\" content=\"dark light\">\n"
|
||||
|
||||
# Favicon
|
||||
if favicon
|
||||
ftype = case favicon.to_s.split(".").last.downcase
|
||||
when "svg" then "image/svg+xml"
|
||||
when "png" then "image/png"
|
||||
else "image/x-icon"
|
||||
end
|
||||
html << "<link rel=\"icon\" type=\"#{ftype}\" href=\"#{e(favicon)}\">\n"
|
||||
end
|
||||
|
||||
# Title & meta
|
||||
html << "<title>#{e(@s.hero_title)} | #{e(site_name)}</title>\n"
|
||||
html << "<meta name=\"description\" content=\"#{e(@s.hero_subtitle)}\">\n"
|
||||
html << "<meta name=\"description\" content=\"#{e(meta_desc)}\">\n"
|
||||
|
||||
# Open Graph
|
||||
html << "<meta property=\"og:type\" content=\"website\">\n"
|
||||
html << "<meta property=\"og:title\" content=\"#{e(@s.hero_title)}\">\n"
|
||||
html << "<meta property=\"og:description\" content=\"#{e(@s.hero_subtitle)}\">\n"
|
||||
html << "<meta property=\"og:image\" content=\"#{og_logo}\">\n" if og_logo
|
||||
html << "<meta property=\"og:description\" content=\"#{e(meta_desc)}\">\n"
|
||||
html << "<meta property=\"og:url\" content=\"#{base_url}\">\n"
|
||||
html << "<meta property=\"og:site_name\" content=\"#{e(site_name)}\">\n"
|
||||
html << "<meta property=\"og:image\" content=\"#{og_image}\">\n" if og_image
|
||||
|
||||
# Twitter card
|
||||
html << "<meta name=\"twitter:card\" content=\"summary_large_image\">\n"
|
||||
html << "<link rel=\"canonical\" href=\"#{Discourse.base_url}\">\n"
|
||||
html << "<meta name=\"twitter:title\" content=\"#{e(@s.hero_title)}\">\n"
|
||||
html << "<meta name=\"twitter:description\" content=\"#{e(meta_desc)}\">\n"
|
||||
html << "<meta name=\"twitter:image\" content=\"#{og_image}\">\n" if og_image
|
||||
|
||||
html << "<link rel=\"canonical\" href=\"#{base_url}\">\n"
|
||||
|
||||
# JSON-LD structured data
|
||||
if (@s.json_ld_enabled rescue true)
|
||||
html << "<script type=\"application/ld+json\">\n#{render_json_ld(site_name, base_url, og_logo)}\n</script>\n"
|
||||
end
|
||||
|
||||
html << "<style>\n#{@css}\n</style>\n"
|
||||
html << @styles.color_overrides
|
||||
html << @styles.section_backgrounds
|
||||
|
||||
# Custom CSS (injected last so it can override everything)
|
||||
custom_css = @s.custom_css.presence rescue nil
|
||||
if custom_css
|
||||
html << "<style id=\"cl-custom-css\">\n#{custom_css}\n</style>\n"
|
||||
end
|
||||
|
||||
html << "</head>\n"
|
||||
html
|
||||
end
|
||||
@@ -105,6 +157,7 @@ module CommunityLanding
|
||||
|
||||
html << "<div class=\"cl-navbar__right\">"
|
||||
html << theme_toggle
|
||||
html << render_social_icons
|
||||
if signin_enabled
|
||||
html << "<a href=\"#{login_url}\" class=\"cl-navbar__link cl-btn--ghost\">#{e(signin_label)}</a>\n"
|
||||
end
|
||||
@@ -116,6 +169,7 @@ module CommunityLanding
|
||||
html << "<button class=\"cl-navbar__hamburger\" id=\"cl-hamburger\" aria-label=\"Toggle menu\"><span></span><span></span><span></span></button>\n"
|
||||
html << "<div class=\"cl-navbar__mobile-menu\" id=\"cl-nav-links\">\n"
|
||||
html << theme_toggle
|
||||
html << render_social_icons
|
||||
html << "<a href=\"#{login_url}\" class=\"cl-navbar__link cl-btn--ghost\">#{e(signin_label)}</a>\n"
|
||||
html << "<a href=\"#{login_url}\" class=\"cl-navbar__link cl-btn--primary\">#{e(join_label)}</a>\n"
|
||||
html << "</div>"
|
||||
@@ -319,6 +373,57 @@ module CommunityLanding
|
||||
html
|
||||
end
|
||||
|
||||
# ── 5b. PARTICIPATION ──
|
||||
|
||||
def render_participation
|
||||
return "" unless (@s.participation_enabled rescue true)
|
||||
|
||||
contributors = @data[:contributors]
|
||||
return "" unless contributors&.length.to_i > 3
|
||||
|
||||
# Positions 4–10: users with a public bio
|
||||
bio_max = (@s.participation_bio_max_length rescue 150).to_i
|
||||
candidates = contributors[3..9] || []
|
||||
users_with_bio = candidates.select { |u| u.user_profile&.bio_excerpt.present? rescue false }
|
||||
return "" if users_with_bio.empty?
|
||||
|
||||
show_title = @s.participation_title_enabled rescue true
|
||||
title_text = @s.participation_title.presence || "Participation"
|
||||
border = @s.participation_border_style rescue "none"
|
||||
min_h = @s.participation_min_height rescue 0
|
||||
count_label = @s.contributors_count_label.presence || ""
|
||||
show_count = @s.contributors_count_label_enabled rescue true
|
||||
|
||||
html = +""
|
||||
html << "<section class=\"cl-participation cl-anim\" id=\"cl-participation\"#{section_style(border, min_h)}><div class=\"cl-container\">\n"
|
||||
html << "<h2 class=\"cl-section-title\">#{e(title_text)}</h2>\n" if show_title
|
||||
stagger_class = @s.staggered_reveal_enabled ? " cl-stagger" : ""
|
||||
html << "<div class=\"cl-participation__grid#{stagger_class}\">\n"
|
||||
|
||||
users_with_bio.each do |user|
|
||||
avatar_url = user.avatar_template.to_s.gsub("{size}", "120")
|
||||
activity_count = user.attributes["post_count"].to_i rescue 0
|
||||
bio_raw = user.user_profile.bio_excerpt.to_s
|
||||
bio_text = bio_raw.length > bio_max ? "#{bio_raw[0...bio_max]}..." : bio_raw
|
||||
count_prefix = show_count && count_label.present? ? "#{e(count_label)} " : ""
|
||||
|
||||
html << "<div class=\"cl-participation-card\">\n"
|
||||
html << "<div class=\"cl-participation-card__quote\">#{Icons::QUOTE_SVG}</div>\n"
|
||||
html << "<p class=\"cl-participation-card__bio\">#{e(bio_text)}</p>\n"
|
||||
html << "<div class=\"cl-participation-card__footer\">\n"
|
||||
html << "<img src=\"#{avatar_url}\" alt=\"#{e(user.username)}\" class=\"cl-participation-card__avatar\" loading=\"lazy\">\n"
|
||||
html << "<div class=\"cl-participation-card__meta\">\n"
|
||||
html << "<span class=\"cl-participation-card__name\">@#{e(user.username)}</span>\n"
|
||||
html << "<span class=\"cl-participation-card__count\">#{count_prefix}#{activity_count}</span>\n"
|
||||
html << "</div>\n"
|
||||
html << "</div>\n"
|
||||
html << "</div>\n"
|
||||
end
|
||||
|
||||
html << "</div>\n</div></section>\n"
|
||||
html
|
||||
end
|
||||
|
||||
# ── 5. TRENDING DISCUSSIONS ──
|
||||
|
||||
def render_topics
|
||||
@@ -355,49 +460,107 @@ module CommunityLanding
|
||||
html
|
||||
end
|
||||
|
||||
# ── 7. COMMUNITY SPACES ──
|
||||
# ── 7. COMMUNITY SPACES + FAQ ──
|
||||
|
||||
def render_groups
|
||||
groups = @data[:groups]
|
||||
return "" unless @s.groups_enabled && groups&.any?
|
||||
groups = @data[:groups]
|
||||
faq_on = (@s.faq_enabled rescue false)
|
||||
has_groups = @s.groups_enabled && groups&.any?
|
||||
|
||||
border = @s.groups_border_style rescue "none"
|
||||
min_h = @s.groups_min_height rescue 0
|
||||
return "" unless has_groups || faq_on
|
||||
|
||||
border = @s.groups_border_style rescue "none"
|
||||
min_h = @s.groups_min_height rescue 0
|
||||
show_title = @s.groups_title_enabled rescue true
|
||||
show_desc = @s.groups_show_description rescue true
|
||||
desc_max = (@s.groups_description_max_length rescue 100).to_i
|
||||
|
||||
html = +""
|
||||
html << "<section class=\"cl-spaces cl-anim\" id=\"cl-groups\"#{section_style(border, min_h)}><div class=\"cl-container\">\n"
|
||||
html << "<h2 class=\"cl-section-title\">#{e(@s.groups_title)}</h2>\n" if show_title
|
||||
stagger_class = @s.staggered_reveal_enabled ? " cl-stagger" : ""
|
||||
html << "<div class=\"cl-spaces__grid#{stagger_class}\">\n"
|
||||
|
||||
groups.each do |group|
|
||||
display_name = group.full_name.presence || group.name.tr("_-", " ").gsub(/\b\w/, &:upcase)
|
||||
hue = group.name.bytes.sum % 360
|
||||
sat = 55 + (group.name.bytes.first.to_i % 15)
|
||||
light = 45 + (group.name.bytes.last.to_i % 12)
|
||||
icon_color = "hsl(#{hue}, #{sat}%, #{light}%)"
|
||||
layout_class = (has_groups && faq_on) ? "cl-spaces__split" : "cl-spaces__full"
|
||||
html << "<div class=\"#{layout_class}\">\n"
|
||||
|
||||
html << "<a href=\"#{login_url}\" class=\"cl-space-card\" style=\"--space-color: #{icon_color}\">\n"
|
||||
html << "<div class=\"cl-space-card__icon\">"
|
||||
if group.flair_url.present?
|
||||
html << "<img src=\"#{group.flair_url}\" alt=\"\">"
|
||||
else
|
||||
html << "<span class=\"cl-space-card__letter\">#{group.name[0].upcase}</span>"
|
||||
# ── Left: Groups Grid ──
|
||||
if has_groups
|
||||
stagger_class = @s.staggered_reveal_enabled ? " cl-stagger" : ""
|
||||
html << "<div class=\"cl-spaces__left\">\n"
|
||||
html << "<div class=\"cl-spaces__grid#{stagger_class}\">\n"
|
||||
|
||||
groups.each do |group|
|
||||
display_name = group.full_name.presence || group.name.tr("_-", " ").gsub(/\b\w/, &:upcase)
|
||||
hue = group.name.bytes.sum % 360
|
||||
sat = 55 + (group.name.bytes.first.to_i % 15)
|
||||
light = 45 + (group.name.bytes.last.to_i % 12)
|
||||
icon_color = "hsl(#{hue}, #{sat}%, #{light}%)"
|
||||
|
||||
# Group description from bio_raw
|
||||
desc_text = nil
|
||||
if show_desc
|
||||
raw_bio = (group.bio_raw.to_s.strip rescue "")
|
||||
if raw_bio.present?
|
||||
plain = raw_bio.gsub(/<[^>]*>/, "").strip
|
||||
desc_text = plain.length > desc_max ? "#{plain[0...desc_max]}..." : plain
|
||||
end
|
||||
end
|
||||
|
||||
html << "<a href=\"#{login_url}\" class=\"cl-space-card\" style=\"--space-color: #{icon_color}\">\n"
|
||||
html << "<div class=\"cl-space-card__icon\">"
|
||||
if group.flair_url.present?
|
||||
html << "<img src=\"#{group.flair_url}\" alt=\"\">"
|
||||
else
|
||||
html << "<span class=\"cl-space-card__letter\">#{group.name[0].upcase}</span>"
|
||||
end
|
||||
html << "</div>\n"
|
||||
html << "<div class=\"cl-space-card__body\">\n"
|
||||
html << "<span class=\"cl-space-card__name\">#{e(display_name)}</span>\n"
|
||||
html << "<span class=\"cl-space-card__sub\">#{group.user_count} members</span>\n"
|
||||
html << "<p class=\"cl-space-card__desc\">#{e(desc_text)}</p>\n" if desc_text
|
||||
html << "</div>\n"
|
||||
html << "</a>\n"
|
||||
end
|
||||
html << "</div>\n"
|
||||
html << "<div class=\"cl-space-card__body\">\n"
|
||||
html << "<span class=\"cl-space-card__name\">#{e(display_name)}</span>\n"
|
||||
html << "<span class=\"cl-space-card__sub\">#{group.user_count} members</span>\n"
|
||||
html << "</div>\n"
|
||||
html << "</a>\n"
|
||||
|
||||
html << "</div>\n</div>\n"
|
||||
end
|
||||
|
||||
# ── Right: FAQ Accordion ──
|
||||
html << render_faq if faq_on
|
||||
|
||||
html << "</div>\n</div></section>\n"
|
||||
html
|
||||
end
|
||||
|
||||
def render_faq
|
||||
faq_title_on = @s.faq_title_enabled rescue true
|
||||
faq_title = @s.faq_title.presence || "Frequently Asked Questions"
|
||||
faq_raw = @s.faq_items.presence rescue nil
|
||||
|
||||
html = +""
|
||||
html << "<div class=\"cl-faq\">\n"
|
||||
html << "<h3 class=\"cl-faq__title\">#{e(faq_title)}</h3>\n" if faq_title_on
|
||||
|
||||
if faq_raw
|
||||
begin
|
||||
items = JSON.parse(faq_raw)
|
||||
items.each do |item|
|
||||
q = item["q"].to_s
|
||||
a = item["a"].to_s
|
||||
next if q.blank?
|
||||
html << "<details class=\"cl-faq__item\" data-faq-exclusive>\n"
|
||||
html << "<summary class=\"cl-faq__question\">#{e(q)}</summary>\n"
|
||||
html << "<div class=\"cl-faq__answer\">#{a}</div>\n"
|
||||
html << "</details>\n"
|
||||
end
|
||||
rescue JSON::ParserError
|
||||
# Invalid JSON — silently skip
|
||||
end
|
||||
end
|
||||
|
||||
html << "</div>\n"
|
||||
html
|
||||
end
|
||||
|
||||
# ── 8. APP CTA ──
|
||||
|
||||
def render_app_cta
|
||||
@@ -534,10 +697,38 @@ module CommunityLanding
|
||||
html
|
||||
end
|
||||
|
||||
def render_social_icons
|
||||
icons = {
|
||||
social_twitter_url: Icons::SOCIAL_TWITTER_SVG,
|
||||
social_facebook_url: Icons::SOCIAL_FACEBOOK_SVG,
|
||||
social_instagram_url: Icons::SOCIAL_INSTAGRAM_SVG,
|
||||
social_youtube_url: Icons::SOCIAL_YOUTUBE_SVG,
|
||||
social_tiktok_url: Icons::SOCIAL_TIKTOK_SVG,
|
||||
social_github_url: Icons::SOCIAL_GITHUB_SVG,
|
||||
}
|
||||
|
||||
links = +""
|
||||
icons.each do |setting, svg|
|
||||
url = (@s.public_send(setting).presence rescue nil)
|
||||
next unless url
|
||||
links << "<a href=\"#{e(url)}\" class=\"cl-social-icon\" target=\"_blank\" rel=\"noopener noreferrer\" aria-label=\"#{setting.to_s.split('_')[1].capitalize}\">#{svg}</a>\n"
|
||||
end
|
||||
return "" if links.empty?
|
||||
|
||||
"<div class=\"cl-social-icons\">#{links}</div>\n"
|
||||
end
|
||||
|
||||
def theme_toggle
|
||||
"<button class=\"cl-theme-toggle\" aria-label=\"Toggle theme\">#{Icons::SUN_SVG}#{Icons::MOON_SVG}</button>\n"
|
||||
end
|
||||
|
||||
def render_json_ld(site_name, base_url, logo_url)
|
||||
org = { "@type" => "Organization", "name" => site_name, "url" => base_url }
|
||||
org["logo"] = logo_url if logo_url
|
||||
website = { "@type" => "WebSite", "name" => site_name, "url" => base_url }
|
||||
{ "@context" => "https://schema.org", "@graph" => [org, website] }.to_json
|
||||
end
|
||||
|
||||
def login_url
|
||||
"/login"
|
||||
end
|
||||
|
||||
@@ -16,9 +16,12 @@ module CommunityLanding
|
||||
light_bg = (hex(@s.light_bg_color) rescue nil) || "#faf6f0"
|
||||
stat_icon = (hex(@s.stat_icon_color) rescue nil) || accent
|
||||
about_bg_img = (@s.about_background_image_url.presence rescue nil)
|
||||
app_g1 = (hex(@s.app_cta_gradient_start) rescue nil) || accent
|
||||
app_g2 = (hex(@s.app_cta_gradient_mid) rescue nil) || accent_hover
|
||||
app_g3 = (hex(@s.app_cta_gradient_end) rescue nil) || accent_hover
|
||||
app_g1_dark = safe_hex(:app_cta_gradient_start_dark) || accent
|
||||
app_g1_light = safe_hex(:app_cta_gradient_start_light)
|
||||
app_g2_dark = safe_hex(:app_cta_gradient_mid_dark) || accent_hover
|
||||
app_g2_light = safe_hex(:app_cta_gradient_mid_light)
|
||||
app_g3_dark = safe_hex(:app_cta_gradient_end_dark) || accent_hover
|
||||
app_g3_light = safe_hex(:app_cta_gradient_end_light)
|
||||
stat_icon_bg = hex(@s.stat_icon_bg_color.presence) rescue nil
|
||||
stat_counter = hex(@s.stat_counter_color.presence) rescue nil
|
||||
video_btn_bg = hex(@s.hero_video_button_color.presence) rescue nil
|
||||
@@ -48,6 +51,9 @@ module CommunityLanding
|
||||
topic_card_light = safe_hex(:topics_card_bg_light)
|
||||
space_card_dark = safe_hex(:groups_card_bg_dark)
|
||||
space_card_light = safe_hex(:groups_card_bg_light)
|
||||
part_card_dark = safe_hex(:participation_card_bg_dark)
|
||||
part_card_light = safe_hex(:participation_card_bg_light)
|
||||
part_icon_color = safe_hex(:participation_icon_color)
|
||||
|
||||
accent_rgb = hex_to_rgb(accent)
|
||||
stat_icon_rgb = hex_to_rgb(stat_icon)
|
||||
@@ -74,7 +80,11 @@ module CommunityLanding
|
||||
dark_extras << "\n --cl-primary-btn-bg: #{primary_btn_dark};" if primary_btn_dark
|
||||
dark_extras << "\n --cl-secondary-btn-bg: #{secondary_btn_dark};" if secondary_btn_dark
|
||||
dark_extras << "\n --cl-pill-bg: #{pill_bg_dark};" if pill_bg_dark
|
||||
dark_extras << "\n --cl-video-btn-bg: #{video_btn_bg};" if video_btn_bg
|
||||
if video_btn_bg
|
||||
video_btn_rgb = hex_to_rgb(video_btn_bg)
|
||||
dark_extras << "\n --cl-video-btn-bg: #{video_btn_bg};"
|
||||
dark_extras << "\n --cl-video-btn-glow: rgba(#{video_btn_rgb}, 0.35);"
|
||||
end
|
||||
|
||||
light_extras = +""
|
||||
light_extras << "\n --cl-navbar-signin-color: #{navbar_signin_light || navbar_signin_dark};" if navbar_signin_light || navbar_signin_dark
|
||||
@@ -82,7 +92,11 @@ module CommunityLanding
|
||||
light_extras << "\n --cl-primary-btn-bg: #{primary_btn_light || primary_btn_dark};" if primary_btn_light || primary_btn_dark
|
||||
light_extras << "\n --cl-secondary-btn-bg: #{secondary_btn_light || secondary_btn_dark};" if secondary_btn_light || secondary_btn_dark
|
||||
light_extras << "\n --cl-pill-bg: #{pill_bg_light || pill_bg_dark};" if pill_bg_light || pill_bg_dark
|
||||
light_extras << "\n --cl-video-btn-bg: #{video_btn_bg};" if video_btn_bg
|
||||
if video_btn_bg
|
||||
video_btn_rgb ||= hex_to_rgb(video_btn_bg)
|
||||
light_extras << "\n --cl-video-btn-bg: #{video_btn_bg};"
|
||||
light_extras << "\n --cl-video-btn-glow: rgba(#{video_btn_rgb}, 0.25);"
|
||||
end
|
||||
|
||||
"<style>
|
||||
:root, [data-theme=\"dark\"] {
|
||||
@@ -103,7 +117,9 @@ module CommunityLanding
|
||||
--cl-topic-card-bg: #{topic_card_dark || 'var(--cl-card)'};
|
||||
--cl-hero-card-bg: #{hero_card_dark_val};
|
||||
--cl-about-card-bg: #{about_dark_css};
|
||||
--cl-app-gradient: linear-gradient(135deg, #{app_g1}, #{app_g2}, #{app_g3});#{dark_extras}
|
||||
--cl-participation-card-bg: #{part_card_dark || 'var(--cl-card)'};
|
||||
--cl-participation-icon-color: #{part_icon_color || 'var(--cl-accent)'};
|
||||
--cl-app-gradient: linear-gradient(135deg, #{app_g1_dark}, #{app_g2_dark}, #{app_g3_dark});#{dark_extras}
|
||||
}
|
||||
[data-theme=\"light\"] {
|
||||
--cl-accent: #{accent};
|
||||
@@ -123,7 +139,9 @@ module CommunityLanding
|
||||
--cl-topic-card-bg: #{topic_card_light || topic_card_dark || 'var(--cl-card)'};
|
||||
--cl-hero-card-bg: #{hero_card_light_val};
|
||||
--cl-about-card-bg: #{about_light_css};
|
||||
--cl-app-gradient: linear-gradient(135deg, #{app_g1}, #{app_g2}, #{app_g3});#{light_extras}
|
||||
--cl-participation-card-bg: #{part_card_light || part_card_dark || 'var(--cl-card)'};
|
||||
--cl-participation-icon-color: #{part_icon_color || 'var(--cl-accent)'};
|
||||
--cl-app-gradient: linear-gradient(135deg, #{app_g1_light || app_g1_dark}, #{app_g2_light || app_g2_dark}, #{app_g3_light || app_g3_dark});#{light_extras}
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root:not([data-theme=\"dark\"]) {
|
||||
@@ -141,7 +159,9 @@ module CommunityLanding
|
||||
--cl-space-card-bg: #{space_card_light || space_card_dark || 'var(--cl-card)'};
|
||||
--cl-topic-card-bg: #{topic_card_light || topic_card_dark || 'var(--cl-card)'};
|
||||
--cl-about-card-bg: #{about_light_css};
|
||||
--cl-app-gradient: linear-gradient(135deg, #{app_g1}, #{app_g2}, #{app_g3});#{light_extras}
|
||||
--cl-participation-card-bg: #{part_card_light || part_card_dark || 'var(--cl-card)'};
|
||||
--cl-participation-icon-color: #{part_icon_color || 'var(--cl-accent)'};
|
||||
--cl-app-gradient: linear-gradient(135deg, #{app_g1_light || app_g1_dark}, #{app_g2_light || app_g2_dark}, #{app_g3_light || app_g3_dark});#{light_extras}
|
||||
}
|
||||
}
|
||||
</style>\n"
|
||||
@@ -154,6 +174,7 @@ module CommunityLanding
|
||||
["#cl-hero", safe_hex(:hero_bg_dark), safe_hex(:hero_bg_light)],
|
||||
["#cl-stats-row", safe_hex(:stats_bg_dark), safe_hex(:stats_bg_light)],
|
||||
["#cl-about", safe_hex(:about_bg_dark), safe_hex(:about_bg_light)],
|
||||
["#cl-participation", safe_hex(:participation_bg_dark), safe_hex(:participation_bg_light)],
|
||||
["#cl-topics", safe_hex(:topics_bg_dark), safe_hex(:topics_bg_light)],
|
||||
["#cl-groups", safe_hex(:groups_bg_dark), safe_hex(:groups_bg_light)],
|
||||
["#cl-app-cta", safe_hex(:app_cta_bg_dark), safe_hex(:app_cta_bg_light)],
|
||||
|
||||
Reference in New Issue
Block a user