From 7455e3bf149df8d3dd40059bf93c3d299d06607a Mon Sep 17 00:00:00 2001 From: DPN MW Date: Sun, 8 Mar 2026 21:04:48 -0400 Subject: [PATCH] Video Upload Fix and New PreLoader --- .../community-landing-admin-tabs.js | 47 +++++++++-- .../stylesheets/community_landing/admin.css | 6 ++ .../stylesheets/community_landing/landing.css | 63 ++++++++++++++ config/locales/en.yml | 10 +++ config/settings.yml | 30 +++++++ lib/community_landing/page_builder.rb | 82 ++++++++++++++++++- lib/community_landing/style_builder.rb | 11 +++ plugin.rb | 1 + 8 files changed, 244 insertions(+), 6 deletions(-) diff --git a/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js b/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js index 966f233..724043c 100644 --- a/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js +++ b/assets/javascripts/discourse/initializers/community-landing-admin-tabs.js @@ -43,6 +43,16 @@ const DESCRIPTIONS = { google_font_name: "Google Font family for body text. Must match exact Google Fonts name (e.g. 'Inter', 'Poppins'). Default: Outfit.", title_font_name: "Separate Google Font for titles and headings. Leave blank to use the body font.", + // ── Preloader ── + preloader_enabled: "Show a loading overlay with logo and progress counter while the page loads.", + preloader_logo_url: "Custom logo for the preloader. Leave blank to use the site logo.", + preloader_bg_dark: "Preloader background for dark mode.", + preloader_bg_light: "Preloader background for light mode.", + preloader_text_color_dark: "Percentage text color for dark mode.", + preloader_text_color_light: "Percentage text color for light mode.", + preloader_bar_color: "Progress bar color. Leave blank for accent color.", + preloader_min_duration: "Minimum display time (ms). Prevents a flash on fast connections.", + // ── Icons ── icon_library: "Icon library for buttons and titles. 'fontawesome' = FA 6 Free, 'google' = Material Symbols Outlined. Syntax: \"icon | Label\" or \"icon | size | Label\" (size in px).", @@ -266,7 +276,11 @@ const TABS = [ "orb_color", "orb_opacity", "scroll_animation", "staggered_reveal_enabled", "dynamic_background_enabled", "mouse_parallax_enabled", "scroll_progress_enabled", - "google_font_name", "title_font_name", "icon_library" + "google_font_name", "title_font_name", "icon_library", + "preloader_enabled", "preloader_logo_url", + "preloader_bg_dark", "preloader_bg_light", + "preloader_text_color_dark", "preloader_text_color_light", + "preloader_bar_color", "preloader_min_duration" ]) }, { @@ -434,6 +448,7 @@ const IMAGE_UPLOAD_SETTINGS = { android_app_badge_image_url: { label: "Upload Badge", multi: false }, app_cta_image_url: { label: "Upload Image", multi: false }, splits_background_image_url: { label: "Upload Image", multi: false }, + preloader_logo_url: { label: "Upload Logo", multi: false }, }; let currentTab = "settings"; @@ -1052,10 +1067,32 @@ function updatePreviewThumbnail(wrapper, settingName) { const preview = wrapper.querySelector(".cl-upload-preview"); if (preview) preview.style.display = "none"; } else { - const preview = wrapper.querySelector(".cl-upload-preview"); - if (!preview) return; - preview.src = url; - preview.style.display = url ? "" : "none"; + const isVideo = cfg && (cfg.accept || "").includes("video"); + if (isVideo) { + // Video: show text label instead of broken img preview + let label = wrapper.querySelector(".cl-upload-preview-label"); + if (!label) { + label = document.createElement("span"); + label.className = "cl-upload-preview-label"; + // Insert before remove button if it exists + const removeBtn = wrapper.querySelector(".cl-upload-remove"); + if (removeBtn) { + wrapper.insertBefore(label, removeBtn); + } else { + wrapper.appendChild(label); + } + } + label.textContent = url ? "Video uploaded" : ""; + label.style.display = url ? "" : "none"; + // Hide img preview if exists + const preview = wrapper.querySelector(".cl-upload-preview"); + if (preview) preview.style.display = "none"; + } else { + const preview = wrapper.querySelector(".cl-upload-preview"); + if (!preview) return; + preview.src = url; + preview.style.display = url ? "" : "none"; + } } } diff --git a/assets/stylesheets/community_landing/admin.css b/assets/stylesheets/community_landing/admin.css index 22451d4..08c9379 100644 --- a/assets/stylesheets/community_landing/admin.css +++ b/assets/stylesheets/community_landing/admin.css @@ -449,6 +449,12 @@ html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="f font-style: italic; } +.cl-upload-preview-label { + font-size: var(--font-down-1); + color: var(--success); + font-weight: 600; +} + .cl-upload-notice { margin-top: 6px; font-size: var(--font-down-2); diff --git a/assets/stylesheets/community_landing/landing.css b/assets/stylesheets/community_landing/landing.css index e49b941..5a97224 100644 --- a/assets/stylesheets/community_landing/landing.css +++ b/assets/stylesheets/community_landing/landing.css @@ -2131,6 +2131,69 @@ html, .cl-body { padding: 1rem 0 0; } +/* ═══════════════════════════════════════════════════════════════════ + PRELOADER + ═══════════════════════════════════════════════════════════════════ */ +.cl-preloader { + position: fixed; + inset: 0; + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; + background: var(--cl-preloader-bg, var(--cl-bg, #06060f)); + transition: opacity 0.45s ease, visibility 0.45s ease; +} + +.cl-preloader--hide { + opacity: 0; + visibility: hidden; + pointer-events: none; +} + +.cl-preloader__content { + display: flex; + flex-direction: column; + align-items: center; + gap: 1.5rem; +} + +.cl-preloader__logo { + max-width: 120px; + max-height: 60px; + width: auto; + height: auto; + object-fit: contain; +} + +.cl-preloader__counter { + font-size: 1.5rem; + font-weight: 700; + color: var(--cl-preloader-text, var(--cl-text-strong, #ffffff)); + font-variant-numeric: tabular-nums; + letter-spacing: 0.04em; +} + +.cl-preloader__bar { + width: 180px; + height: 3px; + background: var(--cl-preloader-bar-track, rgba(255, 255, 255, 0.1)); + border-radius: 3px; + overflow: hidden; +} + +.cl-preloader__bar-fill { + height: 100%; + width: 0%; + background: var(--cl-preloader-bar, var(--cl-accent, #d4a24e)); + border-radius: 3px; + transition: width 0.1s ease-out; +} + +[data-theme="light"] .cl-preloader__bar { + background: rgba(0, 0, 0, 0.08); +} + /* ═══════════════════════════════════════════════════════════════════ REDUCED MOTION ═══════════════════════════════════════════════════════════════════ */ diff --git a/config/locales/en.yml b/config/locales/en.yml index c3ce444..0d8859c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -37,6 +37,16 @@ en: google_font_name: "━━ FONTS ━━ — Google Font family name for body text. Must match exact Google Fonts name (e.g. 'Inter', 'Poppins'). Default: Outfit." title_font_name: "Separate Google Font for section titles and headings. Leave blank to use the body font. Must match exact Google Fonts name." + # ── Preloader ── + preloader_enabled: "━━ PRELOADER ━━ — Show a full-screen loading overlay with logo and progress counter while the page loads." + preloader_logo_url: "Custom logo image for the preloader. Leave blank to use the site logo." + preloader_bg_dark: "Preloader background color for dark mode. Leave blank for default (matches site background)." + preloader_bg_light: "Preloader background color for light mode." + preloader_text_color_dark: "Percentage counter text color for dark mode." + preloader_text_color_light: "Percentage counter text color for light mode." + preloader_bar_color: "Progress bar color. Leave blank to use the accent color." + preloader_min_duration: "Minimum time (ms) the preloader is shown, even if loading finishes sooner. Prevents a flash on fast connections. Default: 800." + # ── Icons ── icon_library: "━━ ICONS ━━ — Icon library for buttons and section titles. 'fontawesome' loads FontAwesome 6 Free; 'google' loads Google Material Symbols Outlined. Use the pipe syntax in labels: \"icon_name | Label\"." diff --git a/config/settings.yml b/config/settings.yml index 81e1f2d..d12fa9f 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -122,6 +122,36 @@ plugins: default: "" type: string + # ══════════════════════════════════════════ + # Preloader + # ══════════════════════════════════════════ + preloader_enabled: + default: false + type: bool + preloader_logo_url: + default: "" + type: string + preloader_bg_dark: + default: "" + type: color + preloader_bg_light: + default: "" + type: color + preloader_text_color_dark: + default: "" + type: color + preloader_text_color_light: + default: "" + type: color + preloader_bar_color: + default: "" + type: color + preloader_min_duration: + default: 800 + type: integer + min: 0 + max: 5000 + # ══════════════════════════════════════════ # Icons # ══════════════════════════════════════════ diff --git a/lib/community_landing/page_builder.rb b/lib/community_landing/page_builder.rb index a618d2d..5769a4b 100644 --- a/lib/community_landing/page_builder.rb +++ b/lib/community_landing/page_builder.rb @@ -26,6 +26,7 @@ module CommunityLanding html = +"" html << render_head html << "\n" + html << render_preloader if (@s.preloader_enabled rescue false) if @s.dynamic_background_enabled html << "
\n" end @@ -147,6 +148,83 @@ module CommunityLanding html end + # ── PRELOADER ── + + def render_preloader + logo = (@s.preloader_logo_url.presence rescue nil) || logo_dark_url + min_ms = (@s.preloader_min_duration rescue 800).to_i.clamp(0, 5000) + + html = +"" + html << "
\n" + html << "
\n" + if logo + html << " \"\"\n" + end + html << "
0%
\n" + html << "
\n" + html << "
\n" + html << "
\n" + + # Inline script — must run immediately, before any other resources + html << "\n" + html + end + # ── 1. NAVBAR ── def render_navbar @@ -770,7 +848,9 @@ module CommunityLanding end def render_video_modal - return "" unless (@s.hero_video_url.presence rescue nil) + has_video = (@s.hero_video_url.presence rescue nil) || + (@s.hero_video_upload.presence rescue nil) + return "" unless has_video html = +"" html << "
\n" diff --git a/lib/community_landing/style_builder.rb b/lib/community_landing/style_builder.rb index 0b9f9fd..a85e04e 100644 --- a/lib/community_landing/style_builder.rb +++ b/lib/community_landing/style_builder.rb @@ -67,6 +67,11 @@ module CommunityLanding cta_subtext_light = safe_hex(:app_cta_subtext_color_light) footer_text_dark = safe_hex(:footer_text_color_dark) footer_text_light = safe_hex(:footer_text_color_light) + preloader_bg_dark = safe_hex(:preloader_bg_dark) + preloader_bg_light = safe_hex(:preloader_bg_light) + preloader_text_dark = safe_hex(:preloader_text_color_dark) + preloader_text_light = safe_hex(:preloader_text_color_light) + preloader_bar = safe_hex(:preloader_bar_color) orb_color = safe_hex(:orb_color) orb_opacity = [[@s.orb_opacity.to_i, 0].max, 100].min rescue 50 @@ -104,6 +109,9 @@ module CommunityLanding dark_extras << "\n --cl-video-btn-glow: rgba(#{video_btn_rgb}, 0.35);" end dark_extras << "\n --cl-footer-text: #{footer_text_dark};" if footer_text_dark + dark_extras << "\n --cl-preloader-bg: #{preloader_bg_dark};" if preloader_bg_dark + dark_extras << "\n --cl-preloader-text: #{preloader_text_dark};" if preloader_text_dark + dark_extras << "\n --cl-preloader-bar: #{preloader_bar};" if preloader_bar light_extras = +"" light_extras << "\n --cl-navbar-signin-color: #{navbar_signin_light || navbar_signin_dark};" if navbar_signin_light || navbar_signin_dark @@ -117,6 +125,9 @@ module CommunityLanding light_extras << "\n --cl-video-btn-glow: rgba(#{video_btn_rgb}, 0.25);" end light_extras << "\n --cl-footer-text: #{footer_text_light || footer_text_dark};" if footer_text_light || footer_text_dark + light_extras << "\n --cl-preloader-bg: #{preloader_bg_light || preloader_bg_dark};" if preloader_bg_light || preloader_bg_dark + light_extras << "\n --cl-preloader-text: #{preloader_text_light || preloader_text_dark};" if preloader_text_light || preloader_text_dark + light_extras << "\n --cl-preloader-bar: #{preloader_bar};" if preloader_bar "