mirror of
https://github.com/dpnmw/community-landing.git
synced 2026-03-18 09:27:16 +00:00
Video Upload Fix and New PreLoader
This commit is contained in:
@@ -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.",
|
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.",
|
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 ──
|
// ── 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).",
|
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",
|
"orb_color", "orb_opacity",
|
||||||
"scroll_animation", "staggered_reveal_enabled", "dynamic_background_enabled",
|
"scroll_animation", "staggered_reveal_enabled", "dynamic_background_enabled",
|
||||||
"mouse_parallax_enabled", "scroll_progress_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 },
|
android_app_badge_image_url: { label: "Upload Badge", multi: false },
|
||||||
app_cta_image_url: { label: "Upload Image", multi: false },
|
app_cta_image_url: { label: "Upload Image", multi: false },
|
||||||
splits_background_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";
|
let currentTab = "settings";
|
||||||
@@ -1052,10 +1067,32 @@ function updatePreviewThumbnail(wrapper, settingName) {
|
|||||||
const preview = wrapper.querySelector(".cl-upload-preview");
|
const preview = wrapper.querySelector(".cl-upload-preview");
|
||||||
if (preview) preview.style.display = "none";
|
if (preview) preview.style.display = "none";
|
||||||
} else {
|
} else {
|
||||||
const preview = wrapper.querySelector(".cl-upload-preview");
|
const isVideo = cfg && (cfg.accept || "").includes("video");
|
||||||
if (!preview) return;
|
if (isVideo) {
|
||||||
preview.src = url;
|
// Video: show text label instead of broken img preview
|
||||||
preview.style.display = url ? "" : "none";
|
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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -449,6 +449,12 @@ html.dark-scheme .admin-detail:not(.cl-tabs-active) .row.setting[data-setting="f
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cl-upload-preview-label {
|
||||||
|
font-size: var(--font-down-1);
|
||||||
|
color: var(--success);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.cl-upload-notice {
|
.cl-upload-notice {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
font-size: var(--font-down-2);
|
font-size: var(--font-down-2);
|
||||||
|
|||||||
@@ -2131,6 +2131,69 @@ html, .cl-body {
|
|||||||
padding: 1rem 0 0;
|
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
|
REDUCED MOTION
|
||||||
═══════════════════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════════════════ */
|
||||||
|
|||||||
@@ -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."
|
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."
|
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 ──
|
# ── 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\"."
|
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\"."
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,36 @@ plugins:
|
|||||||
default: ""
|
default: ""
|
||||||
type: string
|
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
|
# Icons
|
||||||
# ══════════════════════════════════════════
|
# ══════════════════════════════════════════
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ module CommunityLanding
|
|||||||
html = +""
|
html = +""
|
||||||
html << render_head
|
html << render_head
|
||||||
html << "<body class=\"cl-body\">\n"
|
html << "<body class=\"cl-body\">\n"
|
||||||
|
html << render_preloader if (@s.preloader_enabled rescue false)
|
||||||
if @s.dynamic_background_enabled
|
if @s.dynamic_background_enabled
|
||||||
html << "<div class=\"cl-orb-container\"><div class=\"cl-orb cl-orb--1\"></div><div class=\"cl-orb cl-orb--2\"></div></div>\n"
|
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
|
end
|
||||||
@@ -147,6 +148,83 @@ module CommunityLanding
|
|||||||
html
|
html
|
||||||
end
|
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 << "<div id=\"cl-preloader\" class=\"cl-preloader\">\n"
|
||||||
|
html << " <div class=\"cl-preloader__content\">\n"
|
||||||
|
if logo
|
||||||
|
html << " <img src=\"#{e(logo)}\" alt=\"\" class=\"cl-preloader__logo\">\n"
|
||||||
|
end
|
||||||
|
html << " <div class=\"cl-preloader__counter\" id=\"cl-preloader-pct\">0%</div>\n"
|
||||||
|
html << " <div class=\"cl-preloader__bar\"><div class=\"cl-preloader__bar-fill\" id=\"cl-preloader-bar\"></div></div>\n"
|
||||||
|
html << " </div>\n"
|
||||||
|
html << "</div>\n"
|
||||||
|
|
||||||
|
# Inline script — must run immediately, before any other resources
|
||||||
|
html << "<script>\n"
|
||||||
|
html << "(function() {\n"
|
||||||
|
html << " var el = document.getElementById('cl-preloader');\n"
|
||||||
|
html << " var pct = document.getElementById('cl-preloader-pct');\n"
|
||||||
|
html << " var bar = document.getElementById('cl-preloader-bar');\n"
|
||||||
|
html << " var minMs = #{min_ms};\n"
|
||||||
|
html << " var start = Date.now();\n"
|
||||||
|
html << " var current = 0;\n"
|
||||||
|
html << " var target = 0;\n"
|
||||||
|
html << " var done = false;\n"
|
||||||
|
html << "\n"
|
||||||
|
html << " function update() {\n"
|
||||||
|
html << " if (current < target) {\n"
|
||||||
|
html << " current += (target - current) * 0.15;\n"
|
||||||
|
html << " if (target - current < 0.5) current = target;\n"
|
||||||
|
html << " }\n"
|
||||||
|
html << " var v = Math.round(current);\n"
|
||||||
|
html << " pct.textContent = v + '%';\n"
|
||||||
|
html << " bar.style.width = v + '%';\n"
|
||||||
|
html << " if (done && current >= 100) {\n"
|
||||||
|
html << " var elapsed = Date.now() - start;\n"
|
||||||
|
html << " var remaining = Math.max(0, minMs - elapsed);\n"
|
||||||
|
html << " setTimeout(function() {\n"
|
||||||
|
html << " el.classList.add('cl-preloader--hide');\n"
|
||||||
|
html << " setTimeout(function() { el.remove(); }, 500);\n"
|
||||||
|
html << " }, remaining);\n"
|
||||||
|
html << " return;\n"
|
||||||
|
html << " }\n"
|
||||||
|
html << " requestAnimationFrame(update);\n"
|
||||||
|
html << " }\n"
|
||||||
|
html << "\n"
|
||||||
|
html << " // Track image loading\n"
|
||||||
|
html << " window.addEventListener('DOMContentLoaded', function() {\n"
|
||||||
|
html << " var imgs = document.querySelectorAll('img');\n"
|
||||||
|
html << " var total = imgs.length || 1;\n"
|
||||||
|
html << " var loaded = 0;\n"
|
||||||
|
html << " function tick() {\n"
|
||||||
|
html << " loaded++;\n"
|
||||||
|
html << " target = Math.min(95, Math.round((loaded / total) * 95));\n"
|
||||||
|
html << " }\n"
|
||||||
|
html << " imgs.forEach(function(img) {\n"
|
||||||
|
html << " if (img.complete) { tick(); return; }\n"
|
||||||
|
html << " img.addEventListener('load', tick);\n"
|
||||||
|
html << " img.addEventListener('error', tick);\n"
|
||||||
|
html << " });\n"
|
||||||
|
html << " if (imgs.length === 0) target = 95;\n"
|
||||||
|
html << " });\n"
|
||||||
|
html << "\n"
|
||||||
|
html << " window.addEventListener('load', function() {\n"
|
||||||
|
html << " target = 100;\n"
|
||||||
|
html << " done = true;\n"
|
||||||
|
html << " });\n"
|
||||||
|
html << "\n"
|
||||||
|
html << " requestAnimationFrame(update);\n"
|
||||||
|
html << "})();\n"
|
||||||
|
html << "</script>\n"
|
||||||
|
html
|
||||||
|
end
|
||||||
|
|
||||||
# ── 1. NAVBAR ──
|
# ── 1. NAVBAR ──
|
||||||
|
|
||||||
def render_navbar
|
def render_navbar
|
||||||
@@ -770,7 +848,9 @@ module CommunityLanding
|
|||||||
end
|
end
|
||||||
|
|
||||||
def render_video_modal
|
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 = +""
|
||||||
html << "<div class=\"cl-video-modal\" id=\"cl-video-modal\">\n"
|
html << "<div class=\"cl-video-modal\" id=\"cl-video-modal\">\n"
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ module CommunityLanding
|
|||||||
cta_subtext_light = safe_hex(:app_cta_subtext_color_light)
|
cta_subtext_light = safe_hex(:app_cta_subtext_color_light)
|
||||||
footer_text_dark = safe_hex(:footer_text_color_dark)
|
footer_text_dark = safe_hex(:footer_text_color_dark)
|
||||||
footer_text_light = safe_hex(:footer_text_color_light)
|
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_color = safe_hex(:orb_color)
|
||||||
orb_opacity = [[@s.orb_opacity.to_i, 0].max, 100].min rescue 50
|
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);"
|
dark_extras << "\n --cl-video-btn-glow: rgba(#{video_btn_rgb}, 0.35);"
|
||||||
end
|
end
|
||||||
dark_extras << "\n --cl-footer-text: #{footer_text_dark};" if footer_text_dark
|
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 = +""
|
||||||
light_extras << "\n --cl-navbar-signin-color: #{navbar_signin_light || navbar_signin_dark};" if navbar_signin_light || navbar_signin_dark
|
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);"
|
light_extras << "\n --cl-video-btn-glow: rgba(#{video_btn_rgb}, 0.25);"
|
||||||
end
|
end
|
||||||
light_extras << "\n --cl-footer-text: #{footer_text_light || footer_text_dark};" if footer_text_light || footer_text_dark
|
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
|
||||||
|
|
||||||
"<style>
|
"<style>
|
||||||
:root, [data-theme=\"dark\"] {
|
:root, [data-theme=\"dark\"] {
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ after_initialize do
|
|||||||
about_background_image_url ios_app_badge_image_url
|
about_background_image_url ios_app_badge_image_url
|
||||||
android_app_badge_image_url app_cta_image_url
|
android_app_badge_image_url app_cta_image_url
|
||||||
splits_background_image_url hero_video_upload
|
splits_background_image_url hero_video_upload
|
||||||
|
preloader_logo_url
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
# POST /community-landing/admin/pin-upload
|
# POST /community-landing/admin/pin-upload
|
||||||
|
|||||||
Reference in New Issue
Block a user