System Reworked v4

This commit is contained in:
2026-03-08 14:05:27 -04:00
parent 3442e615b2
commit adf3183cb8
8 changed files with 161 additions and 152 deletions

View File

@@ -12,6 +12,11 @@ module CommunityLanding
STAT_LIKES_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><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>'
STAT_CHATS_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>'
# Participation stat icons (16×16, inline with stat values)
PART_TOPICS_SVG = '<svg class="cl-participation-stat__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>'
PART_POSTS_SVG = '<svg class="cl-participation-stat__icon" 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>'
PART_LIKES_SVG = '<svg class="cl-participation-stat__icon" 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>'
PLAY_SVG = '<svg class="cl-icon-play" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"/></svg>'
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>'

View File

@@ -179,10 +179,10 @@ module CommunityLanding
html << theme_toggle
html << render_social_icons
if signin_enabled
html << "<a href=\"#{login_url}\" class=\"cl-navbar__link cl-btn--ghost\">#{button_with_icon(signin_label, :navbar_signin_icon, :navbar_signin_icon_position)}</a>\n"
html << "<a href=\"#{login_url}\" class=\"cl-navbar__link cl-btn--ghost\">#{button_with_icon(signin_label)}</a>\n"
end
if join_enabled
html << "<a href=\"#{login_url}\" class=\"cl-navbar__link cl-btn--primary\">#{button_with_icon(join_label, :navbar_join_icon, :navbar_join_icon_position)}</a>\n"
html << "<a href=\"#{login_url}\" class=\"cl-navbar__link cl-btn--primary\">#{button_with_icon(join_label)}</a>\n"
end
html << "</div>"
@@ -190,8 +190,8 @@ module CommunityLanding
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\">#{button_with_icon(signin_label, :navbar_signin_icon, :navbar_signin_icon_position)}</a>\n"
html << "<a href=\"#{login_url}\" class=\"cl-navbar__link cl-btn--primary\">#{button_with_icon(join_label, :navbar_join_icon, :navbar_join_icon_position)}</a>\n"
html << "<a href=\"#{login_url}\" class=\"cl-navbar__link cl-btn--ghost\">#{button_with_icon(signin_label)}</a>\n"
html << "<a href=\"#{login_url}\" class=\"cl-navbar__link cl-btn--primary\">#{button_with_icon(join_label)}</a>\n"
html << "</div>"
html << "</div></nav>\n"
html
@@ -250,8 +250,8 @@ module CommunityLanding
if primary_on || secondary_on
html << "<div class=\"cl-hero__actions\">\n"
html << "<a href=\"#{primary_url}\" class=\"cl-btn cl-btn--primary cl-btn--lg\">#{button_with_icon(primary_label, :hero_primary_button_icon, :hero_primary_button_icon_position)}</a>\n" if primary_on
html << "<a href=\"#{secondary_url}\" class=\"cl-btn cl-btn--ghost cl-btn--lg\">#{button_with_icon(secondary_label, :hero_secondary_button_icon, :hero_secondary_button_icon_position)}</a>\n" if secondary_on
html << "<a href=\"#{primary_url}\" class=\"cl-btn cl-btn--primary cl-btn--lg\">#{button_with_icon(primary_label)}</a>\n" if primary_on
html << "<a href=\"#{secondary_url}\" class=\"cl-btn cl-btn--ghost cl-btn--lg\">#{button_with_icon(secondary_label)}</a>\n" if secondary_on
html << "</div>\n"
end
@@ -297,7 +297,7 @@ module CommunityLanding
has_images = false
if hero_image_urls_raw
urls = hero_image_urls_raw.split("|").map(&:strip).reject(&:empty?).first(5)
urls = hero_image_urls_raw.split(/[|\n\r]+/).map(&:strip).reject(&:empty?).first(5)
if urls.any?
has_images = true
img_max_h = @s.hero_image_max_height rescue 500
@@ -341,7 +341,7 @@ module CommunityLanding
html = +""
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\"#{title_style(:stats_title_size)}>#{e(stats_title)}</h2>\n" if show_title
html << "<h2 class=\"cl-section-title\"#{title_style(:stats_title_size)}>#{button_with_icon(stats_title)}</h2>\n" if show_title
html << "<div class=\"cl-stats__grid\">\n"
html << stat_card(Icons::STAT_MEMBERS_SVG, stats[:members], @s.stat_members_label, icon_shape, card_style, round_nums, show_labels)
html << stat_card(Icons::STAT_TOPICS_SVG, stats[:topics], @s.stat_topics_label, icon_shape, card_style, round_nums, show_labels)
@@ -379,7 +379,7 @@ module CommunityLanding
# Right side — text content
html << "<div class=\"cl-about__right\">\n"
html << "<h2 class=\"cl-about__heading\"#{title_style(:about_title_size)}>#{e(about_heading)}</h2>\n" if about_heading_on
html << "<h2 class=\"cl-about__heading\"#{title_style(:about_title_size)}>#{button_with_icon(about_heading)}</h2>\n" if about_heading_on
html << Icons::QUOTE_SVG
html << "<div class=\"cl-about__body\">#{about_body}</div>\n" if about_body.present?
html << "<div class=\"cl-about__meta\">\n"
@@ -419,9 +419,14 @@ module CommunityLanding
title_text = @s.participation_title.presence || "Participation"
border = @s.participation_border_style rescue "none"
min_h = @s.participation_min_height rescue 0
topics_label = @s.participation_topics_label.presence || "Topics"
posts_label = @s.participation_posts_label.presence || "Posts"
likes_label = @s.participation_likes_label.presence || "Likes"
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\"#{title_style(:participation_title_size)}>#{e(title_text)}</h2>\n" if show_title
html << "<h2 class=\"cl-section-title\"#{title_style(:participation_title_size)}>#{button_with_icon(title_text)}</h2>\n" if show_title
stagger_class = @s.staggered_reveal_enabled ? " cl-stagger" : ""
html << "<div class=\"cl-participation__grid#{stagger_class}\">\n"
@@ -442,9 +447,9 @@ module CommunityLanding
html << "<p class=\"cl-participation-card__bio\">#{e(bio_text)}</p>\n"
html << "</div>\n"
html << "<div class=\"cl-participation-card__stats\">\n"
html << "<div class=\"cl-participation-stat\"><span class=\"cl-participation-stat__value\">#{topic_count}</span><span class=\"cl-participation-stat__label\">Topics</span></div>\n"
html << "<div class=\"cl-participation-stat\"><span class=\"cl-participation-stat__value\">#{post_count}</span><span class=\"cl-participation-stat__label\">Posts</span></div>\n"
html << "<div class=\"cl-participation-stat\"><span class=\"cl-participation-stat__value\">#{likes_received}</span><span class=\"cl-participation-stat__label\">Likes</span></div>\n"
html << participation_stat(topic_count, topics_label, Icons::PART_TOPICS_SVG)
html << participation_stat(post_count, posts_label, Icons::PART_POSTS_SVG)
html << participation_stat(likes_received, likes_label, Icons::PART_LIKES_SVG)
html << "</div>\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"
@@ -473,7 +478,7 @@ module CommunityLanding
html = +""
html << "<section class=\"cl-topics cl-anim\" id=\"cl-topics\"#{section_style(border, min_h)}><div class=\"cl-container\">\n"
html << "<h2 class=\"cl-section-title\"#{title_style(:topics_title_size)}>#{e(@s.topics_title)}</h2>\n" if show_title
html << "<h2 class=\"cl-section-title\"#{title_style(:topics_title_size)}>#{button_with_icon(@s.topics_title)}</h2>\n" if show_title
stagger_class = @s.staggered_reveal_enabled ? " cl-stagger" : ""
html << "<div class=\"cl-topics__grid#{stagger_class}\">\n"
@@ -505,14 +510,16 @@ module CommunityLanding
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"
groups_bg_img = (@s.splits_background_image_url.presence rescue nil)
section_style_parts = []
section_style_parts << "background: url('#{groups_bg_img}') center/cover no-repeat;" if groups_bg_img
section_attr = section_style_parts.any? ? " style=\"#{section_style_parts.join(' ')}\"" : ""
html << "<section class=\"cl-spaces cl-anim\" id=\"cl-splits\"#{section_attr}><div class=\"cl-container\">\n"
if has_groups && faq_on
# ── Split layout: both titles at same level ──
@@ -520,7 +527,7 @@ module CommunityLanding
# Left column: Groups
html << "<div class=\"cl-spaces__col\">\n"
html << "<h2 class=\"cl-section-title\"#{title_style(:groups_title_size)}>#{e(@s.groups_title)}</h2>\n" if show_title
html << "<h2 class=\"cl-section-title\"#{title_style(:groups_title_size)}>#{button_with_icon(@s.groups_title)}</h2>\n" if show_title
html << render_groups_grid(groups, show_desc, desc_max)
html << "</div>\n"
@@ -532,7 +539,7 @@ module CommunityLanding
html << "</div>\n"
elsif has_groups
# ── Groups only (full width) ──
html << "<h2 class=\"cl-section-title\"#{title_style(:groups_title_size)}>#{e(@s.groups_title)}</h2>\n" if show_title
html << "<h2 class=\"cl-section-title\"#{title_style(:groups_title_size)}>#{button_with_icon(@s.groups_title)}</h2>\n" if show_title
html << "<div class=\"cl-spaces__full\">\n"
html << render_groups_grid(groups, show_desc, desc_max)
html << "</div>\n"
@@ -592,7 +599,7 @@ module CommunityLanding
faq_raw = @s.faq_items.presence rescue nil
html = +""
html << "<h2 class=\"cl-section-title\"#{title_style(:faq_title_size)}>#{e(faq_title)}</h2>\n" if faq_title_on
html << "<h2 class=\"cl-section-title\"#{title_style(:faq_title_size)}>#{button_with_icon(faq_title)}</h2>\n" if faq_title_on
html << "<div class=\"cl-faq\">\n"
if faq_raw
@@ -632,7 +639,7 @@ module CommunityLanding
html = +""
html << "<section class=\"cl-app-cta cl-anim\" id=\"cl-app-cta\"#{section_style(border, min_h)}><div class=\"cl-container\">\n"
html << "<div class=\"cl-app-cta__inner\">\n<div class=\"cl-app-cta__content\">\n"
html << "<h2 class=\"cl-app-cta__headline\"#{title_style(:app_cta_title_size)}>#{e(@s.app_cta_headline)}</h2>\n"
html << "<h2 class=\"cl-app-cta__headline\"#{title_style(:app_cta_title_size)}>#{button_with_icon(@s.app_cta_headline)}</h2>\n"
html << "<p class=\"cl-app-cta__subtext\">#{e(@s.app_cta_subtext)}</p>\n" if @s.app_cta_subtext.present?
html << "<div class=\"cl-app-cta__badges\">\n"
@@ -818,14 +825,50 @@ module CommunityLanding
size > 0 ? " style=\"font-size: #{size}px\"" : ""
end
def button_with_icon(label, icon_setting, position_setting)
icon_name = (@s.public_send(icon_setting).presence rescue nil)
def participation_stat(count, raw_label, default_svg)
fa_enabled = (@s.fontawesome_enabled rescue false)
return e(label) unless icon_name && fa_enabled
# Parse "icon | Label" format — if present, use FA icon instead of default SVG
if fa_enabled && raw_label.include?("|")
parts = raw_label.split("|", 2).map(&:strip)
left, right = parts
if left.match?(/\A[\w-]+\z/) && left.length < 30
icon_html = "<i class=\"fa-solid fa-#{e(left)} cl-participation-stat__icon\"></i>"
label = right
elsif right.match?(/\A[\w-]+\z/) && right.length < 30
icon_html = "<i class=\"fa-solid fa-#{e(right)} cl-participation-stat__icon\"></i>"
label = left
else
icon_html = default_svg
label = raw_label
end
else
icon_html = default_svg
label = raw_label
end
"<div class=\"cl-participation-stat\">" \
"<span class=\"cl-participation-stat__value\">#{icon_html}#{count}</span>" \
"<span class=\"cl-participation-stat__label\">#{e(label)}</span>" \
"</div>\n"
end
position = (@s.public_send(position_setting) rescue "before")
icon_html = "<i class=\"fa-solid fa-#{e(icon_name)}\"></i>"
position == "after" ? "#{e(label)} #{icon_html}" : "#{icon_html} #{e(label)}"
def button_with_icon(raw_label)
fa_enabled = (@s.fontawesome_enabled rescue false)
return e(raw_label) unless fa_enabled && raw_label.include?("|")
parts = raw_label.split("|", 2).map(&:strip)
# Try to detect which side is the icon name (no spaces, short) vs label text
left, right = parts
if left.match?(/\A[\w-]+\z/) && left.length < 30
# "iconname | Label" — icon before
icon_html = "<i class=\"fa-solid fa-#{e(left)}\"></i>"
"#{icon_html} #{e(right)}"
elsif right.match?(/\A[\w-]+\z/) && right.length < 30
# "Label | iconname" — icon after
icon_html = "<i class=\"fa-solid fa-#{e(right)}\"></i>"
"#{e(left)} #{icon_html}"
else
e(raw_label)
end
end
end
end

View File

@@ -61,6 +61,10 @@ module CommunityLanding
part_bio_color = safe_hex(:participation_bio_color)
part_name_color = safe_hex(:participation_name_color)
part_meta_color = safe_hex(:participation_meta_color)
cta_headline_dark = safe_hex(:app_cta_headline_color_dark)
cta_headline_light = safe_hex(:app_cta_headline_color_light)
cta_subtext_dark = safe_hex(:app_cta_subtext_color_dark)
cta_subtext_light = safe_hex(:app_cta_subtext_color_light)
orb_color = safe_hex(:orb_color)
orb_opacity = [[@s.orb_opacity.to_i, 0].max, 100].min rescue 50
@@ -139,7 +143,9 @@ module CommunityLanding
--cl-participation-name-color: #{part_name_color || 'var(--cl-text-strong)'};
--cl-participation-meta-color: #{part_meta_color || 'var(--cl-participation-icon-color)'};
--cl-faq-card-bg: #{faq_card_dark || 'var(--cl-card)'};
--cl-app-gradient: linear-gradient(135deg, #{app_g1_dark}, #{app_g2_dark}, #{app_g3_dark});#{dark_extras}
--cl-app-gradient: linear-gradient(135deg, #{app_g1_dark}, #{app_g2_dark}, #{app_g3_dark});
--cl-cta-headline-color: #{cta_headline_dark || '#ffffff'};
--cl-cta-subtext-color: #{cta_subtext_dark || 'rgba(255, 255, 255, 0.75)'};#{dark_extras}
}
[data-theme=\"light\"] {
--cl-accent: #{accent};
@@ -169,7 +175,9 @@ module CommunityLanding
--cl-participation-name-color: #{part_name_color || 'var(--cl-text-strong)'};
--cl-participation-meta-color: #{part_meta_color || 'var(--cl-participation-icon-color)'};
--cl-faq-card-bg: #{faq_card_light || faq_card_dark || 'var(--cl-card)'};
--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}
--cl-app-gradient: linear-gradient(135deg, #{app_g1_light || app_g1_dark}, #{app_g2_light || app_g2_dark}, #{app_g3_light || app_g3_dark});
--cl-cta-headline-color: #{cta_headline_light || '#1a1a2e'};
--cl-cta-subtext-color: #{cta_subtext_light || 'rgba(26, 26, 46, 0.7)'};#{light_extras}
}
@media (prefers-color-scheme: light) {
:root:not([data-theme=\"dark\"]) {
@@ -197,7 +205,9 @@ module CommunityLanding
--cl-participation-name-color: #{part_name_color || 'var(--cl-text-strong)'};
--cl-participation-meta-color: #{part_meta_color || 'var(--cl-participation-icon-color)'};
--cl-faq-card-bg: #{faq_card_light || faq_card_dark || 'var(--cl-card)'};
--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}
--cl-app-gradient: linear-gradient(135deg, #{app_g1_light || app_g1_dark}, #{app_g2_light || app_g2_dark}, #{app_g3_light || app_g3_dark});
--cl-cta-headline-color: #{cta_headline_light || '#1a1a2e'};
--cl-cta-subtext-color: #{cta_subtext_light || 'rgba(26, 26, 46, 0.7)'};#{light_extras}
}
}
</style>\n"
@@ -212,7 +222,6 @@ module CommunityLanding
["#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)],
["#cl-footer", safe_hex(:footer_bg_dark), safe_hex(:footer_bg_light)],
]