From 66c505e313ddd5f7703399d0d3752402d8e9ce9a Mon Sep 17 00:00:00 2001 From: DPN MW Date: Sun, 8 Mar 2026 01:58:59 -0400 Subject: [PATCH] Minor tweaks and bug fixes. --- .../javascripts/community_landing/landing.js | 3 +- .../community-landing-admin-tabs.js | 91 +++++++++++++++++-- .../stylesheets/community_landing/admin.css | 29 ++++++ .../stylesheets/community_landing/landing.css | 26 +++++- config/locales/en.yml | 43 ++++----- config/settings.yml | 23 +++-- lib/community_landing/page_builder.rb | 21 ++++- lib/community_landing/style_builder.rb | 13 ++- 8 files changed, 199 insertions(+), 50 deletions(-) diff --git a/assets/javascripts/community_landing/landing.js b/assets/javascripts/community_landing/landing.js index 283a641..d865342 100644 --- a/assets/javascripts/community_landing/landing.js +++ b/assets/javascripts/community_landing/landing.js @@ -173,8 +173,7 @@ function openVideoModal(url) { var ytId = parseYouTubeId(url); if (ytId) { - var origin = encodeURIComponent(window.location.origin); - videoPlayer.innerHTML = ''; + videoPlayer.innerHTML = ''; } else { videoPlayer.innerHTML = ''; } diff --git a/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js b/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js index 62636b2..6a272a1 100644 --- a/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js +++ b/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js @@ -25,8 +25,9 @@ const TABS = [ id: "hero", label: "Hero", settings: new Set([ - "hero_title", "hero_subtitle", "hero_card_enabled", "hero_background_image_url", - "hero_image_urls", "hero_image_max_height", + "hero_title", "hero_accent_word", "hero_subtitle", + "hero_card_enabled", "hero_image_first", "hero_content_width", + "hero_background_image_url", "hero_image_urls", "hero_image_max_height", "hero_primary_button_enabled", "hero_primary_button_label", "hero_primary_button_url", "hero_secondary_button_enabled", "hero_secondary_button_label", "hero_secondary_button_url", "hero_video_url", "hero_video_button_color", "hero_video_blur_on_hover", @@ -56,8 +57,7 @@ const TABS = [ settings: new Set([ "about_enabled", "about_heading_enabled", "about_heading", "about_title", "about_role", "about_body", "about_image_url", - "about_gradient_start", "about_gradient_mid", "about_gradient_end", - "about_background_image_url", + "about_card_color", "about_background_image_url", "about_bg_dark", "about_bg_light", "about_min_height", "about_border_style" ]) }, @@ -102,6 +102,18 @@ const TABS = [ } ]; +// Dark/light background pairs — light row gets merged into dark row +const BG_PAIRS = [ + ["hero_bg_dark", "hero_bg_light"], + ["hero_card_bg_dark", "hero_card_bg_light"], + ["stats_bg_dark", "stats_bg_light"], + ["about_bg_dark", "about_bg_light"], + ["topics_bg_dark", "topics_bg_light"], + ["groups_bg_dark", "groups_bg_light"], + ["app_cta_bg_dark", "app_cta_bg_light"], + ["footer_bg_dark", "footer_bg_light"], +]; + let currentTab = "settings"; let filterActive = false; let isActive = false; @@ -122,6 +134,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; const name = row.getAttribute("data-setting"); row.classList.toggle( "cl-tab-hidden", @@ -224,6 +238,71 @@ function cleanupTabs() { filterActive = false; } +/** + * Merge dark/light bg color pairs into a single row. + * Moves the light setting-value into the dark row and hides the light row. + */ +function mergeBgPairs() { + const container = getContainer(); + if (!container) return; + + BG_PAIRS.forEach(([darkName, lightName]) => { + const darkRow = container.querySelector(`.row.setting[data-setting="${darkName}"]`); + const lightRow = container.querySelector(`.row.setting[data-setting="${lightName}"]`); + if (!darkRow || !lightRow) return; + // Already merged + if (darkRow.querySelector(".cl-merged-value")) 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") + 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 + const darkValue = darkRow.querySelector(".setting-value"); + if (!darkValue) return; + + // Wrap existing dark value + const wrapper = document.createElement("div"); + wrapper.className = "cl-merged-value"; + + 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"; + }); +} + function buildTabsUI() { const container = getContainer(); if (!container) return false; @@ -284,7 +363,7 @@ function buildTabsUI() { }); container.classList.add("cl-tabs-active"); - + mergeBgPairs(); applyTabFilter(); return true; } @@ -329,7 +408,7 @@ function buildTabsUI() { } container.classList.add("cl-tabs-active"); - wrapBgPairs(); + mergeBgPairs(); applyTabFilter(); return true; } diff --git a/assets/stylesheets/community_landing/admin.css b/assets/stylesheets/community_landing/admin.css index 3b2ca66..34caa67 100644 --- a/assets/stylesheets/community_landing/admin.css +++ b/assets/stylesheets/community_landing/admin.css @@ -82,6 +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) ── */ + +.cl-merged-value { + display: flex; + gap: 24px; +} + +.cl-color-col { + flex: 1; + min-width: 0; +} + +.cl-color-col__label { + display: block; + font-size: var(--font-down-1); + font-weight: 600; + color: var(--primary-medium); + margin-bottom: 4px; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +@media (max-width: 767px) { + .cl-merged-value { + flex-direction: column; + gap: 12px; + } +} + /* ── When tabs are active, remove separator borders ── */ .cl-tabs-active .row.setting[data-setting] { diff --git a/assets/stylesheets/community_landing/landing.css b/assets/stylesheets/community_landing/landing.css index 15d753d..8b6cefe 100644 --- a/assets/stylesheets/community_landing/landing.css +++ b/assets/stylesheets/community_landing/landing.css @@ -30,7 +30,7 @@ --cl-radius-sm: 12px; --cl-radius-xs: 8px; --cl-stat-icon-color: #d4a24e; - --cl-about-gradient: linear-gradient(135deg, #0a0a14, #080812, #0a0a14); + --cl-about-card-bg: linear-gradient(135deg, #0a0a14, #080812, #0a0a14); --cl-app-gradient: linear-gradient(135deg, #d4a24e, #c4922e, #b8862e); --cl-blur: blur(12px); color-scheme: dark; @@ -59,7 +59,7 @@ --cl-orb-2: rgba(100, 150, 255, 0.05); --cl-gradient-text: linear-gradient(135deg, #c4922e, #a3711d, #c4922e); --cl-scrolled-nav: rgba(250, 249, 246, 0.85); - --cl-about-gradient: linear-gradient(135deg, #fdfcf9, #f9f8f4, #fdfcf9); + --cl-about-card-bg: linear-gradient(135deg, #fdfcf9, #f9f8f4, #fdfcf9); --cl-app-gradient: linear-gradient(135deg, #c4922e, #b3811d, #a3711d); --cl-blur: blur(16px); color-scheme: light; @@ -88,7 +88,7 @@ --cl-orb-1: rgba(212, 162, 78, 0.08); --cl-gradient-text: linear-gradient(135deg, #d4a24e, #b8862e, #d4a24e); --cl-scrolled-nav: rgba(250, 246, 240, 0.95); - --cl-about-gradient: linear-gradient(135deg, #fdf6ec, #fef9f0, #fdf6ec); + --cl-about-card-bg: linear-gradient(135deg, #fdf6ec, #fef9f0, #fdf6ec); --cl-app-gradient: linear-gradient(135deg, #d4a24e, #c4922e, #b8862e); color-scheme: light; } @@ -637,6 +637,18 @@ text-align: left; } +@media (min-width: 1024px) { + .cl-hero__content { + flex: 0 0 var(--cl-hero-content-w, 50%); + max-width: var(--cl-hero-content-w, 50%); + } + + .cl-hero__image { + flex: 1; + min-width: 0; + } +} + .cl-hero__title { font-size: clamp(2.5rem, 8vw, 4.5rem); font-weight: 900; @@ -691,9 +703,15 @@ height: auto; border-radius: var(--cl-radius); object-fit: cover; + object-position: center; transition: opacity 0.6s ease; } +/* ── Hero Image First (swap order) ── */ +.cl-hero--image-first .cl-hero__image { + order: -1; +} + /* ── Hero Card Mode ── */ .cl-hero--card .cl-hero__inner { background: var(--cl-hero-card-bg, var(--cl-card)); @@ -1046,7 +1064,7 @@ } .cl-about__card { - background: var(--cl-about-gradient); + background: var(--cl-about-card-bg); border: 1px solid var(--cl-border); border-radius: 20px; overflow: hidden; diff --git a/config/locales/en.yml b/config/locales/en.yml index 44a4080..a44d73e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -29,9 +29,12 @@ en: navbar_border_style: "Border style at the bottom of the navbar when scrolled." # ── 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. The last word is highlighted with your accent color." + 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." + hero_accent_word: "Which word in the title gets the accent shimmer animation. 0 = last word (default). 1 = first word, 2 = second word, etc." hero_subtitle: "Supporting text below the hero headline. Describe your community's purpose or value proposition." hero_card_enabled: "Display the hero content inside a rounded card container with border and shadow. When off, the hero uses a flat full-width layout." + hero_image_first: "Show the hero image above the text on mobile and to the left on desktop. When off, text appears first (default)." + hero_content_width: "Width of the text column as a percentage (20–80). The image column fills the remaining space. Default 50 gives equal columns." hero_background_image_url: "Full-bleed background image behind the hero section. In card mode, fills the card with a dark overlay. In flat mode, covers the entire section." hero_image_urls: "Images displayed on the right side of the hero. Add up to 5 URLs — a random one is shown on each page load." hero_image_max_height: "Maximum height in pixels for the hero image (100–1200)." @@ -44,12 +47,12 @@ en: 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_light: "Background color for the hero section in light mode. Hex value. Leave blank for default." + hero_bg_dark: "Section background color override. Dark (left) and light (right) color pickers. Leave blank for default." + hero_bg_light: "Light mode background for the hero section." hero_min_height: "Minimum height for the hero section in pixels. Set to 0 for auto height." hero_border_style: "Border style at the bottom of the hero section." - hero_card_bg_dark: "Background color for the hero card overlay in dark mode. Only applies when hero card mode is enabled. Leave blank for default glass effect." - hero_card_bg_light: "Background color for the hero card overlay in light mode. Only applies when hero card mode is enabled. Leave blank for default glass effect." + hero_card_bg_dark: "Hero card overlay background. Dark (left) and light (right) color pickers. Only applies in card mode. Leave blank for default glass effect." + hero_card_bg_light: "Light mode background for the hero card overlay." hero_card_opacity: "Opacity of the hero card background (0 to 1, default 0.85). Lower values make the card more transparent, showing the background image through." # ── 3. Stats Section ── @@ -68,25 +71,23 @@ en: 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_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_light: "Background color for the stats section in light mode. Leave blank for default." + stats_bg_dark: "Section background color override. Dark (left) and light (right) color pickers. Leave blank for default." + stats_bg_light: "Light mode background for the stats section." stats_min_height: "Minimum height for the stats section in pixels. Set to 0 for auto height." stats_border_style: "Border style at the bottom of the stats section." # ── 4. About Section ── - about_enabled: "━━ ROW 4: ABOUT ━━ — Show the About section: a gradient card with bold heading, decorative quote icon, community description, and author attribution (avatar, name, role). Supports a 3-color gradient background and optional overlay image." + about_enabled: "━━ ROW 4: ABOUT ━━ — Show the About section: a card with bold heading, decorative quote icon, community description, and author attribution (avatar, name, role)." about_heading_enabled: "Show the bold heading text at the top of the About card. Turn off to start with the quote icon and description." about_heading: "The heading text at the top of the About card (e.g. 'About Community', 'Our Story')." about_title: "Author or community name shown in the card's bottom attribution, next to the avatar." about_role: "Subtitle below the author name (e.g. 'Community Manager'). If blank, the site name is used." about_body: "Main body text for the About card. Supports basic HTML: p, a, strong, em, ul, li, br." about_image_url: "Small avatar image shown next to the author name. Square images work best." - about_gradient_start: "First color (left) of the About card's 3-color gradient. Hex value." - about_gradient_mid: "Middle color of the About card gradient. Hex value." - about_gradient_end: "Third color (right) of the About card gradient. Hex value." + about_card_color: "Background color for the About card. Leave blank for the default theme color." about_background_image_url: "Background image layered on top of the gradient. Use a subtle pattern or texture." - about_bg_dark: "Background color for the about section wrapper in dark mode. Leave blank for default." - about_bg_light: "Background color for the about section wrapper in light mode. Leave blank for default." + about_bg_dark: "Section background color override. Dark (left) and light (right) color pickers. Leave blank for default." + about_bg_light: "Light mode background for the about section." about_min_height: "Minimum height for the about section in pixels. Set to 0 for auto height." about_border_style: "Border style at the bottom of the about section." @@ -96,8 +97,8 @@ en: topics_title: "Heading text above the scrollable topic cards." topics_count: "Number of trending topic cards to display." topics_card_bg_color: "Background color for each trending topic card. Leave blank for default card styling." - topics_bg_dark: "Background color for the trending section in dark mode. Leave blank for default." - topics_bg_light: "Background color for the trending section in light mode. Leave blank for default." + topics_bg_dark: "Section background color override. Dark (left) and light (right) color pickers. Leave blank for default." + topics_bg_light: "Light mode background for the trending section." topics_min_height: "Minimum height for the trending section in pixels. Set to 0 for auto height." topics_border_style: "Border style at the bottom of the trending section." @@ -120,8 +121,8 @@ en: groups_count: "Number of group cards to display." groups_selected: "Show only specific groups. Enter group names separated by pipes (e.g. designers|developers|artists). Leave blank to auto-select public groups." groups_card_bg_color: "Background color for each space card. Leave blank for default card styling." - groups_bg_dark: "Background color for the spaces section in dark mode. Leave blank for default." - groups_bg_light: "Background color for the spaces section in light mode. Leave blank for default." + groups_bg_dark: "Section background color override. Dark (left) and light (right) color pickers. Leave blank for default." + 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." @@ -139,8 +140,8 @@ en: 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_image_url: "Promotional image on the right side of the CTA (e.g. phone mockup). PNG for transparent backgrounds." - app_cta_bg_dark: "Background color for the app CTA section in dark mode. Leave blank for default." - app_cta_bg_light: "Background color for the app CTA section in light mode. Leave blank for default." + 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." app_cta_min_height: "Minimum height for the app CTA section in pixels. Set to 0 for auto height." app_cta_border_style: "Border style at the bottom of the app CTA section." @@ -148,6 +149,6 @@ en: footer_description: "━━ ROW 9: FOOTER ━━ — Bottom of the page with logo, navigation links, copyright, and optional description. This adds a description paragraph above the footer bar." footer_text: "Optional HTML text inside the footer bar. Supports: p, a, strong, em, ul, li, br." footer_links: 'Footer navigation links as a JSON array. Format: [{"label":"Terms","url":"/tos"},{"label":"Privacy","url":"/privacy"}].' - footer_bg_dark: "Background color for the footer bar in dark mode. Leave blank for default." - footer_bg_light: "Background color for the footer bar in light mode. Leave blank for default." + footer_bg_dark: "Section background color override. Dark (left) and light (right) color pickers. Leave blank for default." + footer_bg_light: "Light mode background for the footer." footer_border_style: "Border style at the top of the footer bar." diff --git a/config/settings.yml b/config/settings.yml index 20c4a08..5197710 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -110,12 +110,25 @@ plugins: hero_title: default: "Welcome to Our Creative HeadQuarters" type: string + hero_accent_word: + default: 0 + type: integer + min: 0 + max: 50 hero_subtitle: default: "Are you ready to start your creative journey?" type: string hero_card_enabled: default: true type: bool + hero_image_first: + default: false + type: bool + hero_content_width: + default: 50 + type: integer + min: 20 + max: 80 hero_background_image_url: default: "" type: string @@ -283,14 +296,8 @@ plugins: about_image_url: default: "" type: string - about_gradient_start: - default: "fdf6ec" - type: color - about_gradient_mid: - default: "fef9f0" - type: color - about_gradient_end: - default: "fdf6ec" + about_card_color: + default: "" type: color about_background_image_url: default: "" diff --git a/lib/community_landing/page_builder.rb b/lib/community_landing/page_builder.rb index 1768934..f3bbf28 100644 --- a/lib/community_landing/page_builder.rb +++ b/lib/community_landing/page_builder.rb @@ -131,9 +131,11 @@ module CommunityLanding def render_hero hero_card = @s.hero_card_enabled rescue true + hero_img_first = @s.hero_image_first rescue false hero_bg_img = @s.hero_background_image_url.presence hero_border = @s.hero_border_style rescue "none" hero_min_h = @s.hero_min_height rescue 0 + content_w = @s.hero_content_width rescue 50 site_name = @s.title html = +"" @@ -142,14 +144,29 @@ module CommunityLanding hero_style_parts << "background-image: url('#{hero_bg_img}');" if hero_bg_img hero_style_parts << "border-bottom: 1px #{hero_border} var(--cl-border);" if hero_border.present? && hero_border != "none" hero_style_parts << "min-height: #{hero_min_h}px;" if hero_min_h.to_i > 0 + hero_style_parts << "--cl-hero-content-w: #{content_w.to_i}%;" if content_w.to_i != 50 hero_attr = hero_style_parts.any? ? " style=\"#{hero_style_parts.join(' ')}\"" : "" - html << "
\n" + hero_classes = "cl-hero" + hero_classes << " cl-hero--card" if hero_card + hero_classes << " cl-hero--image-first" if hero_img_first + html << "
\n" html << "
\n
\n" + # Accent word: 0 = last word (default), N = Nth word (1-indexed) title_words = @s.hero_title.to_s.split(" ") + accent_idx = (@s.hero_accent_word rescue 0).to_i if title_words.length > 1 - html << "

#{e(title_words[0..-2].join(' '))} #{e(title_words.last)}

\n" + # Convert to 0-based index; 0 means last word + target = accent_idx > 0 ? [accent_idx - 1, title_words.length - 1].min : title_words.length - 1 + before = title_words[0...target] + accent = title_words[target] + after = title_words[(target + 1)..] + parts = +"" + parts << "#{e(before.join(' '))} " if before.any? + parts << "#{e(accent)}" + parts << " #{e(after.join(' '))}" if after.any? + html << "

#{parts}

\n" else html << "

#{e(@s.hero_title)}

\n" end diff --git a/lib/community_landing/style_builder.rb b/lib/community_landing/style_builder.rb index 4ad95f9..88c3764 100644 --- a/lib/community_landing/style_builder.rb +++ b/lib/community_landing/style_builder.rb @@ -15,9 +15,7 @@ module CommunityLanding dark_bg = hex(@s.dark_bg_color) || "#06060f" light_bg = hex(@s.light_bg_color) || "#faf6f0" stat_icon = hex(@s.stat_icon_color) || accent - about_g1 = hex(@s.about_gradient_start) || "#fdf6ec" - about_g2 = hex(@s.about_gradient_mid) || "#fef9f0" - about_g3 = hex(@s.about_gradient_end) || "#fdf6ec" + about_card = hex(@s.about_card_color.presence) rescue nil about_bg_img = @s.about_background_image_url.presence app_g1 = hex(@s.app_cta_gradient_start) || accent app_g2 = hex(@s.app_cta_gradient_mid) || accent_hover @@ -45,7 +43,8 @@ module CommunityLanding hero_card_dark_val = "rgba(#{hero_card_dark_rgb}, #{hero_card_opacity})" hero_card_light_val = "rgba(#{hero_card_light_rgb}, #{hero_card_opacity})" - about_bg_extra = about_bg_img ? ", url('#{about_bg_img}') center/cover no-repeat" : "" + about_card_val = about_card || "var(--cl-card)" + about_card_css = about_bg_img ? "#{about_card_val}, url('#{about_bg_img}') center/cover no-repeat" : about_card_val video_btn_line = video_btn_bg ? "\n --cl-video-btn-bg: #{video_btn_bg};" : "" "