UI Revamped v5

Landing page section migration and ui improvements.
This commit is contained in:
2026-03-07 20:57:59 -04:00
parent 2eb0d79394
commit 8700b35f3f
7 changed files with 153 additions and 105 deletions

View File

@@ -118,11 +118,19 @@
// ═══════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════
// 6. STAT COUNTER // 6. STAT COUNTER
// ═══════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════
function formatRounded(n) {
if (n < 1000) return n.toLocaleString();
if (n < 10000) return (n / 1000).toFixed(1).replace(/\.0$/, "") + "K";
if (n < 1000000) return Math.round(n / 1000) + "K";
return (n / 1000000).toFixed(1).replace(/\.0$/, "") + "M";
}
function animateCount(el) { function animateCount(el) {
if (el.classList.contains("counted")) return; if (el.classList.contains("counted")) return;
el.classList.add("counted"); el.classList.add("counted");
var target = parseInt(el.getAttribute("data-count"), 10); var target = parseInt(el.getAttribute("data-count"), 10);
if (isNaN(target) || target === 0) return; if (isNaN(target) || target === 0) return;
var round = el.getAttribute("data-round") === "true";
var duration = 2000; var duration = 2000;
var start = null; var start = null;
@@ -131,9 +139,10 @@
function step(ts) { function step(ts) {
if (!start) start = ts; if (!start) start = ts;
var p = Math.min((ts - start) / duration, 1); var p = Math.min((ts - start) / duration, 1);
el.textContent = Math.floor(target * ease(p)).toLocaleString(); var current = Math.floor(target * ease(p));
el.textContent = round ? formatRounded(current) : current.toLocaleString();
if (p < 1) requestAnimationFrame(step); if (p < 1) requestAnimationFrame(step);
else el.textContent = target.toLocaleString(); else el.textContent = round ? formatRounded(target) : target.toLocaleString();
} }
requestAnimationFrame(step); requestAnimationFrame(step);
} }

View File

@@ -27,7 +27,7 @@ const TABS = [
"hero_image_urls", "hero_image_max_height", "hero_image_urls", "hero_image_max_height",
"hero_primary_button_label", "hero_primary_button_url", "hero_primary_button_label", "hero_primary_button_url",
"hero_secondary_button_label", "hero_secondary_button_url", "hero_secondary_button_label", "hero_secondary_button_url",
"hero_video_url", "hero_video_url", "hero_video_button_color", "hero_video_blur_on_hover",
"hero_bg_dark", "hero_bg_light", "hero_min_height", "hero_border_style" "hero_bg_dark", "hero_bg_light", "hero_min_height", "hero_border_style"
]) ])
}, },
@@ -37,7 +37,7 @@ const TABS = [
settings: new Set([ settings: new Set([
"stats_title", "stat_icon_color", "stat_icon_bg_color", "stat_icon_shape", "stat_counter_color", "stats_title", "stat_icon_color", "stat_icon_bg_color", "stat_icon_shape", "stat_counter_color",
"stat_members_label", "stat_topics_label", "stat_posts_label", "stat_members_label", "stat_topics_label", "stat_posts_label",
"stat_likes_label", "stat_chats_label", "stat_likes_label", "stat_chats_label", "stat_round_numbers",
"stats_bg_dark", "stats_bg_light", "stats_min_height", "stats_border_style" "stats_bg_dark", "stats_bg_light", "stats_min_height", "stats_border_style"
]) ])
}, },
@@ -184,7 +184,8 @@ function buildTabsUI() {
if (!container) return false; if (!container) return false;
// Already injected — just re-apply filter // Already injected — just re-apply filter
if (container.querySelector(".cl-admin-tab")) { // Search broadly: native tabs may be a sibling of container, not a child
if (document.querySelector(".cl-admin-tab")) {
applyTabFilter(); applyTabFilter();
return true; return true;
} }
@@ -199,7 +200,10 @@ function buildTabsUI() {
if (!hasOurs) return false; if (!hasOurs) return false;
// ── Strategy 1: Inject into native Discourse tab bar ── // ── Strategy 1: Inject into native Discourse tab bar ──
const nativeTabsEl = container.querySelector(".admin-plugin-config-area__tabs"); // Native tabs may be a sibling of our container, so search at page level
const page = container.closest(".admin-plugin-config-page") || container.parentElement;
const nativeTabsEl = (page && page.querySelector(".admin-plugin-config-area__tabs")) ||
document.querySelector(".admin-plugin-config-area__tabs");
if (nativeTabsEl) { if (nativeTabsEl) {
// Find the native "Settings" link and hook into it // Find the native "Settings" link and hook into it
const nativeLink = nativeTabsEl.querySelector("a"); const nativeLink = nativeTabsEl.querySelector("a");
@@ -326,7 +330,7 @@ export default {
return; return;
} }
const c = getContainer(); const c = getContainer();
if (c && !c.querySelector(".cl-admin-tab")) { if (c && !document.querySelector(".cl-admin-tab")) {
buildTabsUI(); buildTabsUI();
} }
}, 500); }, 500);

View File

@@ -739,7 +739,7 @@
height: 80px; height: 80px;
border-radius: 50%; border-radius: 50%;
border: none; border: none;
background: var(--cl-accent); background: var(--cl-video-btn-bg, var(--cl-accent));
color: #fff; color: #fff;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
@@ -752,7 +752,7 @@
.cl-hero-play:hover { .cl-hero-play:hover {
transform: translate(-50%, -50%) scale(1.1); transform: translate(-50%, -50%) scale(1.1);
background: var(--cl-accent-hover); 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(--cl-accent-glow);
} }
@@ -882,26 +882,44 @@
} }
} }
/* ── Blur hero image on play button hover ── */
.cl-hero__image:has(.cl-hero-play[data-blur-hover="true"]:hover) .cl-hero__image-img {
filter: blur(4px);
transition: filter 0.4s ease;
}
.cl-hero__image .cl-hero__image-img {
transition: filter 0.4s ease;
}
/* ── Hero Creators (top 3 pills in hero) ── */
.cl-hero__creators {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 2rem;
}
/* ═══════════════════════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════════════════════
3. PREMIUM STATS — icon with bg, label, animated counter 3. COMPACT STATS BAR — icon + count + label inline
═══════════════════════════════════════════════════════════════════ */ ═══════════════════════════════════════════════════════════════════ */
.cl-stats { .cl-stats {
padding: 2.5rem 0 2rem; padding: 2rem 0 1.5rem;
} }
.cl-stats__grid { .cl-stats__grid {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 0.75rem; gap: 0.6rem;
} }
@media (min-width: 640px) { @media (min-width: 480px) {
.cl-stats__grid { .cl-stats__grid {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
} }
} }
@media (min-width: 1024px) { @media (min-width: 768px) {
.cl-stats__grid { .cl-stats__grid {
grid-template-columns: repeat(5, 1fr); grid-template-columns: repeat(5, 1fr);
} }
@@ -909,14 +927,13 @@
.cl-stat-card { .cl-stat-card {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
align-items: center; align-items: center;
gap: 1rem; gap: 0.75rem;
padding: 2.5rem 1.5rem; padding: 1rem 1.25rem;
background: var(--cl-card); background: var(--cl-card);
border: 1px solid var(--cl-border); border: 1px solid var(--cl-border);
border-radius: var(--cl-radius); border-radius: var(--cl-radius-sm);
text-align: center;
backdrop-filter: var(--cl-blur); backdrop-filter: var(--cl-blur);
-webkit-backdrop-filter: var(--cl-blur); -webkit-backdrop-filter: var(--cl-blur);
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
@@ -924,18 +941,18 @@
.cl-stat-card:hover { .cl-stat-card:hover {
border-color: var(--cl-accent); border-color: var(--cl-accent);
transform: translateY(-10px); transform: translateY(-4px);
box-shadow: 0 20px 40px var(--cl-shadow); box-shadow: 0 12px 28px var(--cl-shadow);
background: var(--cl-glass); background: var(--cl-glass);
} }
/* Icon with background */
.cl-stat-card__icon-wrap { .cl-stat-card__icon-wrap {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 56px; width: 36px;
height: 56px; height: 36px;
flex-shrink: 0;
background: var(--cl-stat-icon-bg); background: var(--cl-stat-icon-bg);
color: var(--cl-stat-icon-color); color: var(--cl-stat-icon-color);
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
@@ -948,8 +965,8 @@
} }
.cl-stat-card__icon-wrap svg { .cl-stat-card__icon-wrap svg {
width: 28px; width: 18px;
height: 28px; height: 18px;
} }
.cl-stat-icon--circle { .cl-stat-icon--circle {
@@ -957,24 +974,32 @@
} }
.cl-stat-icon--rounded { .cl-stat-icon--rounded {
border-radius: 16px; border-radius: 10px;
}
.cl-stat-card__text {
display: flex;
flex-direction: column;
min-width: 0;
}
.cl-stat-card__value {
font-size: 1.3rem;
font-weight: 900;
color: var(--cl-stat-counter-color, var(--cl-text-strong));
letter-spacing: -0.02em;
line-height: 1.1;
} }
.cl-stat-card__label { .cl-stat-card__label {
font-size: 0.85rem; font-size: 0.72rem;
color: var(--cl-muted); color: var(--cl-muted);
font-weight: 700; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
} white-space: nowrap;
overflow: hidden;
/* Counter */ text-overflow: ellipsis;
.cl-stat-card__value {
font-size: 2rem;
font-weight: 900;
color: var(--cl-stat-counter-color, var(--cl-text-strong));
letter-spacing: -0.02em;
line-height: 1;
} }
/* ═══════════════════════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════════════════════
@@ -1266,7 +1291,7 @@
} }
/* ═══════════════════════════════════════════════════════════════════ /* ═══════════════════════════════════════════════════════════════════
7. COMMUNITY SPACES — colored header cards 7. COMMUNITY SPACES — compact horizontal pills with accent stripe
═══════════════════════════════════════════════════════════════════ */ ═══════════════════════════════════════════════════════════════════ */
.cl-spaces { .cl-spaces {
padding: 1.5rem 0 2rem; padding: 1.5rem 0 2rem;
@@ -1274,85 +1299,61 @@
.cl-spaces__grid { .cl-spaces__grid {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: 1fr;
gap: 0.85rem; gap: 0.6rem;
} }
@media (min-width: 480px) { @media (min-width: 480px) {
.cl-spaces__grid { .cl-spaces__grid {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(2, 1fr);
} }
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.cl-spaces__grid { .cl-spaces__grid {
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1024px) {
.cl-spaces__grid {
grid-template-columns: repeat(5, 1fr);
} }
} }
.cl-space-card { .cl-space-card {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
align-items: center;
gap: 0.75rem;
padding: 0.6rem 1rem 0.6rem 0.6rem;
background: var(--cl-space-card-bg, var(--cl-card)); background: var(--cl-space-card-bg, var(--cl-card));
border: 1px solid var(--cl-border); border: 1px solid var(--cl-border);
border-radius: var(--cl-radius); border-left: 3px solid var(--space-color);
border-radius: var(--cl-radius-sm);
text-decoration: none; text-decoration: none;
overflow: hidden; transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);
backdrop-filter: var(--cl-blur); backdrop-filter: var(--cl-blur);
-webkit-backdrop-filter: var(--cl-blur); -webkit-backdrop-filter: var(--cl-blur);
} }
.cl-space-card:hover { .cl-space-card:hover {
border-color: var(--cl-accent); border-color: var(--cl-accent);
transform: translateY(-10px); border-left-color: var(--space-color);
box-shadow: 0 25px 50px var(--cl-shadow); transform: translateY(-4px);
} box-shadow: 0 12px 28px var(--cl-shadow);
background: var(--cl-glass);
/* Colored header band */
.cl-space-card__header {
background: var(--space-color);
padding: 2.5rem 1rem;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.cl-space-card__header::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.1));
pointer-events: none;
} }
.cl-space-card__icon { .cl-space-card__icon {
width: 64px; width: 32px;
height: 64px; height: 32px;
border-radius: 18px; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden; overflow: hidden;
background: rgba(255, 255, 255, 0.25); flex-shrink: 0;
backdrop-filter: blur(10px); background: var(--space-color);
-webkit-backdrop-filter: blur(10px); transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
position: relative;
z-index: 1;
border: 1px solid rgba(255, 255, 255, 0.2);
transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
} }
.cl-space-card:hover .cl-space-card__icon { .cl-space-card:hover .cl-space-card__icon {
transform: scale(1.15) rotate(3deg); transform: scale(1.1) rotate(5deg);
} }
.cl-space-card__icon img { .cl-space-card__icon img {
@@ -1362,23 +1363,21 @@
} }
.cl-space-card__letter { .cl-space-card__letter {
font-size: 1.8rem; font-size: 0.9rem;
font-weight: 900; font-weight: 900;
color: #fff; color: #fff;
line-height: 1; line-height: 1;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
} }
/* Card body */ /* Card body */
.cl-space-card__body { .cl-space-card__body {
padding: 1.25rem 1.25rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.25rem; min-width: 0;
} }
.cl-space-card__name { .cl-space-card__name {
font-size: 0.95rem; font-size: 0.88rem;
font-weight: 800; font-weight: 800;
color: var(--cl-text-strong); color: var(--cl-text-strong);
white-space: nowrap; white-space: nowrap;
@@ -1387,7 +1386,7 @@
} }
.cl-space-card__sub { .cl-space-card__sub {
font-size: 0.78rem; font-size: 0.72rem;
color: var(--cl-muted); color: var(--cl-muted);
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;

View File

@@ -36,6 +36,8 @@ en:
hero_secondary_button_label: "Text on the secondary (outlined) CTA button." hero_secondary_button_label: "Text on the secondary (outlined) CTA button."
hero_secondary_button_url: "URL the secondary button links to." hero_secondary_button_url: "URL the secondary button links to."
hero_video_url: "URL for a hero video. Supports MP4 and YouTube links. A play button appears in the hero area; clicking opens a lightbox modal with the video." hero_video_url: "URL for a hero video. Supports MP4 and YouTube links. A play button appears in the hero area; clicking opens a lightbox modal with the video."
hero_video_button_color: "Custom background color for the hero video play button. Leave blank to use the accent color."
hero_video_blur_on_hover: "Apply a blur effect to the hero image when hovering the play button."
hero_bg_dark: "Background color for the hero section in dark mode. Hex value. Leave blank for default." hero_bg_dark: "Background color for the hero section in dark mode. Hex value. Leave blank for default."
hero_bg_light: "Background color for the hero section in light mode. Hex value. Leave blank for default." hero_bg_light: "Background color for the hero section in light mode. Hex value. Leave blank for default."
hero_min_height: "Minimum height for the hero section in pixels. Set to 0 for auto height." hero_min_height: "Minimum height for the hero section in pixels. Set to 0 for auto height."
@@ -52,6 +54,7 @@ en:
stat_posts_label: "Custom label for the Posts stat card." stat_posts_label: "Custom label for the Posts stat card."
stat_likes_label: "Custom label for the Likes stat card." stat_likes_label: "Custom label for the Likes stat card."
stat_chats_label: "Custom label for the Chats stat card. Shows total chat messages if the Chat plugin is active." stat_chats_label: "Custom label for the Chats stat card. Shows total chat messages if the Chat plugin is active."
stat_round_numbers: "Round large numbers for a cleaner display: 1000 becomes 1K, 12345 becomes 12.3K, 1234567 becomes 1.2M."
stats_bg_dark: "Background color for the stats section in dark mode. Leave blank for default." stats_bg_dark: "Background color for the stats section in dark mode. Leave blank for default."
stats_bg_light: "Background color for the stats section in light mode. Leave blank for default." stats_bg_light: "Background color for the stats section in light mode. Leave blank for default."
stats_min_height: "Minimum height for the stats section in pixels. Set to 0 for auto height." stats_min_height: "Minimum height for the stats section in pixels. Set to 0 for auto height."

View File

@@ -130,6 +130,12 @@ plugins:
hero_video_url: hero_video_url:
default: "" default: ""
type: string type: string
hero_video_button_color:
default: ""
type: color
hero_video_blur_on_hover:
default: true
type: bool
hero_bg_dark: hero_bg_dark:
default: "" default: ""
type: color type: color
@@ -186,6 +192,9 @@ plugins:
stat_chats_label: stat_chats_label:
default: "Chats" default: "Chats"
type: string type: string
stat_round_numbers:
default: false
type: bool
stats_bg_dark: stats_bg_dark:
default: "" default: ""
type: color type: color

View File

@@ -157,10 +157,30 @@ module CommunityLanding
html << "<div class=\"cl-hero__actions\">\n" html << "<div class=\"cl-hero__actions\">\n"
html << "<a href=\"#{primary_url}\" class=\"cl-btn cl-btn--primary cl-btn--lg\">#{e(primary_label)}</a>\n" html << "<a href=\"#{primary_url}\" class=\"cl-btn cl-btn--primary cl-btn--lg\">#{e(primary_label)}</a>\n"
html << "<a href=\"#{secondary_url}\" class=\"cl-btn cl-btn--ghost cl-btn--lg\">#{e(secondary_label)}</a>\n" html << "<a href=\"#{secondary_url}\" class=\"cl-btn cl-btn--ghost cl-btn--lg\">#{e(secondary_label)}</a>\n"
html << "</div>\n</div>\n" html << "</div>\n"
# Hero creators (top 3)
contributors = @data[:contributors]
if (@s.contributors_enabled rescue false) && contributors&.any?
top3 = contributors.first(3)
html << "<div class=\"cl-hero__creators\">\n"
top3.each do |user|
avatar_url = user.avatar_template.gsub("{size}", "120")
activity_count = user.attributes["post_count"].to_i rescue 0
html << "<a href=\"#{login_url}\" class=\"cl-creator-pill\">\n"
html << "<img src=\"#{avatar_url}\" alt=\"#{e(user.username)}\" class=\"cl-creator-pill__avatar\" loading=\"lazy\">\n"
html << "<span class=\"cl-creator-pill__name\">@#{e(user.username)}</span>\n"
html << "<span class=\"cl-creator-pill__count\">#{activity_count}</span>\n"
html << "</a>\n"
end
html << "</div>\n"
end
html << "</div>\n"
hero_image_urls_raw = @s.hero_image_urls.presence hero_image_urls_raw = @s.hero_image_urls.presence
hero_video = @s.hero_video_url.presence rescue nil 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 has_images = false
if hero_image_urls_raw if hero_image_urls_raw
@@ -171,7 +191,7 @@ module CommunityLanding
html << "<div class=\"cl-hero__image\" data-hero-images=\"#{e(urls.to_json)}\">\n" html << "<div class=\"cl-hero__image\" data-hero-images=\"#{e(urls.to_json)}\">\n"
html << "<img src=\"#{urls.first}\" alt=\"#{e(site_name)}\" class=\"cl-hero__image-img\" style=\"max-height: #{img_max_h}px;\">\n" html << "<img src=\"#{urls.first}\" alt=\"#{e(site_name)}\" class=\"cl-hero__image-img\" style=\"max-height: #{img_max_h}px;\">\n"
if hero_video if hero_video
html << "<button class=\"cl-hero-play\" data-video-url=\"#{e(hero_video)}\" aria-label=\"Play video\">" html << "<button class=\"cl-hero-play\" data-video-url=\"#{e(hero_video)}\"#{blur_attr} aria-label=\"Play video\">"
html << "<span class=\"cl-hero-play__icon\">#{Icons::PLAY_SVG}</span>" html << "<span class=\"cl-hero-play__icon\">#{Icons::PLAY_SVG}</span>"
html << "</button>\n" html << "</button>\n"
end end
@@ -181,7 +201,7 @@ module CommunityLanding
if hero_video && !has_images if hero_video && !has_images
html << "<div class=\"cl-hero__image cl-hero__image--video-only\">\n" html << "<div class=\"cl-hero__image cl-hero__image--video-only\">\n"
html << "<button class=\"cl-hero-play\" data-video-url=\"#{e(hero_video)}\" aria-label=\"Play video\">" html << "<button class=\"cl-hero-play\" data-video-url=\"#{e(hero_video)}\"#{blur_attr} aria-label=\"Play video\">"
html << "<span class=\"cl-hero-play__icon\">#{Icons::PLAY_SVG}</span>" html << "<span class=\"cl-hero-play__icon\">#{Icons::PLAY_SVG}</span>"
html << "</button>\n" html << "</button>\n"
html << "</div>\n" html << "</div>\n"
@@ -199,16 +219,17 @@ module CommunityLanding
border = @s.stats_border_style rescue "none" border = @s.stats_border_style rescue "none"
min_h = @s.stats_min_height rescue 0 min_h = @s.stats_min_height rescue 0
icon_shape = @s.stat_icon_shape rescue "circle" icon_shape = @s.stat_icon_shape rescue "circle"
round_nums = @s.stat_round_numbers rescue false
html = +"" html = +""
html << "<section class=\"cl-stats cl-anim\" id=\"cl-stats-row\"#{section_style(border, min_h)}><div class=\"cl-container\">\n" html << "<section class=\"cl-stats cl-anim\" id=\"cl-stats-row\"#{section_style(border, min_h)}><div class=\"cl-container\">\n"
html << "<h2 class=\"cl-section-title\">#{e(stats_title)}</h2>\n" html << "<h2 class=\"cl-section-title\">#{e(stats_title)}</h2>\n"
html << "<div class=\"cl-stats__grid\">\n" html << "<div class=\"cl-stats__grid\">\n"
html << stat_card(Icons::STAT_MEMBERS_SVG, stats[:members], @s.stat_members_label, icon_shape) html << stat_card(Icons::STAT_MEMBERS_SVG, stats[:members], @s.stat_members_label, icon_shape, round_nums)
html << stat_card(Icons::STAT_TOPICS_SVG, stats[:topics], @s.stat_topics_label, icon_shape) html << stat_card(Icons::STAT_TOPICS_SVG, stats[:topics], @s.stat_topics_label, icon_shape, round_nums)
html << stat_card(Icons::STAT_POSTS_SVG, stats[:posts], @s.stat_posts_label, icon_shape) html << stat_card(Icons::STAT_POSTS_SVG, stats[:posts], @s.stat_posts_label, icon_shape, round_nums)
html << stat_card(Icons::STAT_LIKES_SVG, stats[:likes], @s.stat_likes_label, icon_shape) html << stat_card(Icons::STAT_LIKES_SVG, stats[:likes], @s.stat_likes_label, icon_shape, round_nums)
html << stat_card(Icons::STAT_CHATS_SVG, stats[:chats], @s.stat_chats_label, icon_shape) html << stat_card(Icons::STAT_CHATS_SVG, stats[:chats], @s.stat_chats_label, icon_shape, round_nums)
html << "</div>\n</div></section>\n" html << "</div>\n</div></section>\n"
html html
end end
@@ -339,8 +360,7 @@ module CommunityLanding
light = 45 + (group.name.bytes.last.to_i % 12) light = 45 + (group.name.bytes.last.to_i % 12)
icon_color = "hsl(#{hue}, #{sat}%, #{light}%)" icon_color = "hsl(#{hue}, #{sat}%, #{light}%)"
html << "<a href=\"#{login_url}\" class=\"cl-space-card\">\n" html << "<a href=\"#{login_url}\" class=\"cl-space-card\" style=\"--space-color: #{icon_color}\">\n"
html << "<div class=\"cl-space-card__header\" style=\"--space-color: #{icon_color}\">\n"
html << "<div class=\"cl-space-card__icon\">" html << "<div class=\"cl-space-card__icon\">"
if group.flair_url.present? if group.flair_url.present?
html << "<img src=\"#{group.flair_url}\" alt=\"\">" html << "<img src=\"#{group.flair_url}\" alt=\"\">"
@@ -348,7 +368,6 @@ module CommunityLanding
html << "<span class=\"cl-space-card__letter\">#{group.name[0].upcase}</span>" html << "<span class=\"cl-space-card__letter\">#{group.name[0].upcase}</span>"
end end
html << "</div>\n" html << "</div>\n"
html << "</div>\n"
html << "<div class=\"cl-space-card__body\">\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__name\">#{e(display_name)}</span>\n"
html << "<span class=\"cl-space-card__sub\">#{group.user_count} members</span>\n" html << "<span class=\"cl-space-card__sub\">#{group.user_count} members</span>\n"
@@ -447,12 +466,15 @@ module CommunityLanding
# ── Shared helpers ── # ── Shared helpers ──
def stat_card(icon_svg, count, label, icon_shape = "circle") def stat_card(icon_svg, count, label, icon_shape = "circle", round_numbers = false)
shape_class = icon_shape == "rounded" ? "cl-stat-icon--rounded" : "cl-stat-icon--circle" shape_class = icon_shape == "rounded" ? "cl-stat-icon--rounded" : "cl-stat-icon--circle"
round_attr = round_numbers ? ' data-round="true"' : ''
"<div class=\"cl-stat-card\">\n" \ "<div class=\"cl-stat-card\">\n" \
"<div class=\"cl-stat-card__icon-wrap #{shape_class}\">#{icon_svg}</div>\n" \ "<div class=\"cl-stat-card__icon-wrap #{shape_class}\">#{icon_svg}</div>\n" \
"<div class=\"cl-stat-card__text\">\n" \
"<span class=\"cl-stat-card__value\" data-count=\"#{count}\"#{round_attr}>0</span>\n" \
"<span class=\"cl-stat-card__label\">#{e(label)}</span>\n" \ "<span class=\"cl-stat-card__label\">#{e(label)}</span>\n" \
"<span class=\"cl-stat-card__value\" data-count=\"#{count}\">0</span>\n" \ "</div>\n" \
"</div>\n" "</div>\n"
end end

View File

@@ -26,6 +26,7 @@ module CommunityLanding
stat_counter = hex(@s.stat_counter_color.presence) rescue nil stat_counter = hex(@s.stat_counter_color.presence) rescue nil
space_card_bg = hex(@s.groups_card_bg_color.presence) rescue nil space_card_bg = hex(@s.groups_card_bg_color.presence) rescue nil
topic_card_bg = hex(@s.topics_card_bg_color.presence) rescue nil topic_card_bg = hex(@s.topics_card_bg_color.presence) rescue nil
video_btn_bg = hex(@s.hero_video_button_color.presence) rescue nil
accent_rgb = hex_to_rgb(accent) accent_rgb = hex_to_rgb(accent)
stat_icon_rgb = hex_to_rgb(stat_icon) stat_icon_rgb = hex_to_rgb(stat_icon)
@@ -35,6 +36,7 @@ module CommunityLanding
topic_card_bg_val = topic_card_bg || "var(--cl-card)" topic_card_bg_val = topic_card_bg || "var(--cl-card)"
about_bg_extra = about_bg_img ? ", url('#{about_bg_img}') center/cover no-repeat" : "" about_bg_extra = about_bg_img ? ", url('#{about_bg_img}') center/cover no-repeat" : ""
video_btn_line = video_btn_bg ? "\n --cl-video-btn-bg: #{video_btn_bg};" : ""
"<style> "<style>
:root, [data-theme=\"dark\"] { :root, [data-theme=\"dark\"] {
@@ -51,7 +53,7 @@ module CommunityLanding
--cl-stat-icon-bg: #{stat_icon_bg_val}; --cl-stat-icon-bg: #{stat_icon_bg_val};
--cl-stat-counter-color: #{stat_counter_val}; --cl-stat-counter-color: #{stat_counter_val};
--cl-space-card-bg: #{space_card_bg_val}; --cl-space-card-bg: #{space_card_bg_val};
--cl-topic-card-bg: #{topic_card_bg_val}; --cl-topic-card-bg: #{topic_card_bg_val};#{video_btn_line}
--cl-about-gradient: linear-gradient(135deg, #{about_g1}, #{about_g2}, #{about_g3})#{about_bg_extra}; --cl-about-gradient: linear-gradient(135deg, #{about_g1}, #{about_g2}, #{about_g3})#{about_bg_extra};
--cl-app-gradient: linear-gradient(135deg, #{app_g1}, #{app_g2}, #{app_g3}); --cl-app-gradient: linear-gradient(135deg, #{app_g1}, #{app_g2}, #{app_g3});
} }
@@ -69,7 +71,7 @@ module CommunityLanding
--cl-stat-icon-bg: #{stat_icon_bg_val}; --cl-stat-icon-bg: #{stat_icon_bg_val};
--cl-stat-counter-color: #{stat_counter_val}; --cl-stat-counter-color: #{stat_counter_val};
--cl-space-card-bg: #{space_card_bg_val}; --cl-space-card-bg: #{space_card_bg_val};
--cl-topic-card-bg: #{topic_card_bg_val}; --cl-topic-card-bg: #{topic_card_bg_val};#{video_btn_line}
--cl-about-gradient: linear-gradient(135deg, #{about_g1}, #{about_g2}, #{about_g3})#{about_bg_extra}; --cl-about-gradient: linear-gradient(135deg, #{about_g1}, #{about_g2}, #{about_g3})#{about_bg_extra};
--cl-app-gradient: linear-gradient(135deg, #{app_g1}, #{app_g2}, #{app_g3}); --cl-app-gradient: linear-gradient(135deg, #{app_g1}, #{app_g2}, #{app_g3});
} }