diff --git a/assets/javascripts/community_landing/landing.js b/assets/javascripts/community_landing/landing.js
index 716e6cc..f08431b 100644
--- a/assets/javascripts/community_landing/landing.js
+++ b/assets/javascripts/community_landing/landing.js
@@ -25,7 +25,26 @@
var progressBar = $(".cl-progress-bar");
// ═══════════════════════════════════════════════════════════════════
- // 2. NAVBAR & SCROLL
+ // 2. HERO IMAGE RANDOM CYCLE
+ // ═══════════════════════════════════════════════════════════════════
+ (function initHeroImage() {
+ var container = $(".cl-hero__image[data-hero-images]");
+ if (!container) return;
+ try {
+ var images = JSON.parse(container.getAttribute("data-hero-images"));
+ if (!images || images.length < 2) return;
+ var img = $(".cl-hero__image-img", container);
+ if (!img) return;
+ var pick = images[Math.floor(Math.random() * images.length)];
+ img.style.opacity = "0";
+ img.src = pick;
+ img.onload = function () { img.style.opacity = ""; };
+ img.onerror = function () { img.src = images[0]; img.style.opacity = ""; };
+ } catch (e) {}
+ })();
+
+ // ═══════════════════════════════════════════════════════════════════
+ // 3. NAVBAR & SCROLL
// ═══════════════════════════════════════════════════════════════════
var navbar = $("#cl-navbar");
if (navbar) {
@@ -53,7 +72,7 @@
}
// ═══════════════════════════════════════════════════════════════════
- // 3. ENHANCED REVEAL (Staggered)
+ // 4. ENHANCED REVEAL (Staggered)
// ═══════════════════════════════════════════════════════════════════
if ("IntersectionObserver" in window) {
var revealObserver = new IntersectionObserver(function (entries) {
@@ -74,7 +93,7 @@
}
// ═══════════════════════════════════════════════════════════════════
- // 4. MOUSE PARALLAX
+ // 5. MOUSE PARALLAX
// ═══════════════════════════════════════════════════════════════════
var heroImage = $(".cl-hero__image-img");
var orbs = $$(".cl-orb");
@@ -97,7 +116,7 @@
}
// ═══════════════════════════════════════════════════════════════════
- // 5. STAT COUNTER
+ // 6. STAT COUNTER
// ═══════════════════════════════════════════════════════════════════
function animateCount(el) {
if (el.classList.contains("counted")) return;
@@ -130,4 +149,57 @@
var sr = $("#cl-stats-row"); if (sr) statsObs.observe(sr);
}
+ // ═══════════════════════════════════════════════════════════════════
+ // 7. VIDEO MODAL
+ // ═══════════════════════════════════════════════════════════════════
+ var videoModal = $("#cl-video-modal");
+ var videoPlayer = $("#cl-video-player");
+
+ if (videoModal && videoPlayer) {
+ function parseYouTubeId(url) {
+ var match = url.match(/(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))([^&?#]+)/);
+ return match ? match[1] : null;
+ }
+
+ function openVideoModal(url) {
+ var ytId = parseYouTubeId(url);
+ if (ytId) {
+ videoPlayer.innerHTML = '';
+ } else {
+ videoPlayer.innerHTML = '';
+ }
+ videoModal.classList.add("active");
+ document.body.style.overflow = "hidden";
+ }
+
+ function closeVideoModal() {
+ videoModal.classList.remove("active");
+ videoPlayer.innerHTML = "";
+ document.body.style.overflow = "";
+ }
+
+ $$(".cl-hero-play").forEach(function (btn) {
+ btn.addEventListener("click", function () {
+ var url = btn.getAttribute("data-video-url");
+ if (url) openVideoModal(url);
+ });
+ });
+
+ var closeBtn = $(".cl-video-modal__close", videoModal);
+ if (closeBtn) {
+ closeBtn.addEventListener("click", closeVideoModal);
+ }
+
+ var backdrop = $(".cl-video-modal__backdrop", videoModal);
+ if (backdrop) {
+ backdrop.addEventListener("click", closeVideoModal);
+ }
+
+ document.addEventListener("keydown", function (e) {
+ if (e.key === "Escape" && videoModal.classList.contains("active")) {
+ closeVideoModal();
+ }
+ });
+ }
+
})();
diff --git a/assets/stylesheets/community_landing/landing.css b/assets/stylesheets/community_landing/landing.css
index 862a424..b9ea8ff 100644
--- a/assets/stylesheets/community_landing/landing.css
+++ b/assets/stylesheets/community_landing/landing.css
@@ -362,6 +362,18 @@
padding: 0.55rem 0;
}
+.cl-progress-bar {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 3px;
+ width: 0%;
+ background: var(--cl-accent);
+ z-index: 1001;
+ transition: width 0.1s linear;
+ border-radius: 0 2px 2px 0;
+}
+
.cl-navbar__inner {
max-width: 1200px;
margin: 0 auto;
@@ -663,6 +675,213 @@
gap: 1rem;
}
+/* ── Hero Image ── */
+.cl-hero__image {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+}
+
+.cl-hero__image-img {
+ width: 100%;
+ height: auto;
+ border-radius: var(--cl-radius);
+ object-fit: cover;
+ transition: opacity 0.6s ease;
+}
+
+/* ── Hero Background Image (flat mode) ── */
+.cl-hero__bg {
+ position: absolute;
+ inset: 0;
+ background-size: cover;
+ background-position: center;
+ z-index: 0;
+ opacity: 0.25;
+}
+
+.cl-hero__bg::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(to bottom, transparent 60%, var(--cl-bg));
+}
+
+/* ── Hero Card Mode ── */
+.cl-hero--card .cl-hero__inner {
+ background: var(--cl-card);
+ border: 1px solid var(--cl-border);
+ border-radius: var(--cl-radius);
+ padding: 3rem 2.5rem;
+ backdrop-filter: var(--cl-blur);
+ -webkit-backdrop-filter: var(--cl-blur);
+ box-shadow: 0 8px 32px var(--cl-shadow);
+ background-size: cover;
+ background-position: center;
+}
+
+@media (min-width: 1024px) {
+ .cl-hero--card .cl-hero__inner {
+ padding: 4rem 3.5rem;
+ }
+}
+
+/* ── Hero Video Play Button ── */
+.cl-hero-play {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 5;
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ border: none;
+ background: var(--cl-accent);
+ color: #fff;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 8px 32px var(--cl-accent-glow), 0 0 0 0 var(--cl-accent-glow);
+ transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
+ animation: cl-play-pulse 2s infinite;
+}
+
+.cl-hero-play:hover {
+ transform: translate(-50%, -50%) scale(1.1);
+ background: var(--cl-accent-hover);
+ box-shadow: 0 12px 40px var(--cl-accent-glow);
+}
+
+.cl-hero-play__icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-left: 4px;
+}
+
+.cl-hero-play__icon svg {
+ width: 32px;
+ height: 32px;
+}
+
+@keyframes cl-play-pulse {
+ 0%, 100% { box-shadow: 0 8px 32px var(--cl-accent-glow), 0 0 0 0 var(--cl-accent-glow); }
+ 50% { box-shadow: 0 8px 32px var(--cl-accent-glow), 0 0 0 16px rgba(212, 162, 78, 0); }
+}
+
+.cl-hero__image--video-only {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 200px;
+ position: relative;
+}
+
+.cl-hero__image--video-only .cl-hero-play {
+ position: relative;
+ top: auto;
+ left: auto;
+ transform: none;
+ width: 100px;
+ height: 100px;
+}
+
+.cl-hero__image--video-only .cl-hero-play:hover {
+ transform: scale(1.1);
+}
+
+.cl-hero__image--video-only .cl-hero-play__icon svg {
+ width: 40px;
+ height: 40px;
+}
+
+/* ── Video Modal / Lightbox ── */
+.cl-video-modal {
+ display: none;
+ position: fixed;
+ inset: 0;
+ z-index: 10000;
+ align-items: center;
+ justify-content: center;
+}
+
+.cl-video-modal.active {
+ display: flex;
+}
+
+.cl-video-modal__backdrop {
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.85);
+ backdrop-filter: blur(8px);
+ -webkit-backdrop-filter: blur(8px);
+}
+
+.cl-video-modal__content {
+ position: relative;
+ z-index: 1;
+ width: 90vw;
+ max-width: 960px;
+ aspect-ratio: 16 / 9;
+ border-radius: var(--cl-radius);
+ overflow: hidden;
+ box-shadow: 0 32px 64px rgba(0, 0, 0, 0.5);
+}
+
+.cl-video-modal__close {
+ position: absolute;
+ top: -48px;
+ right: 0;
+ z-index: 2;
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ background: rgba(0, 0, 0, 0.5);
+ color: #fff;
+ font-size: 1.5rem;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s;
+}
+
+.cl-video-modal__close:hover {
+ background: rgba(255, 255, 255, 0.15);
+ border-color: rgba(255, 255, 255, 0.4);
+}
+
+.cl-video-modal__player {
+ width: 100%;
+ height: 100%;
+ background: #000;
+}
+
+.cl-video-modal__player iframe,
+.cl-video-modal__player video {
+ width: 100%;
+ height: 100%;
+ border: none;
+ display: block;
+}
+
+@media (max-width: 640px) {
+ .cl-video-modal__content {
+ width: 95vw;
+ border-radius: var(--cl-radius-sm);
+ }
+
+ .cl-video-modal__close {
+ top: -44px;
+ right: 4px;
+ }
+}
+
/* ═══════════════════════════════════════════════════════════════════
3. PREMIUM STATS — icon with bg, label, animated counter
═══════════════════════════════════════════════════════════════════ */
@@ -909,7 +1128,7 @@
display: flex;
flex-direction: column;
padding: 1.5rem;
- background: var(--cl-card);
+ background: var(--cl-topic-card-bg, var(--cl-card));
border: 1px solid var(--cl-border);
border-radius: var(--cl-radius);
text-decoration: none;
@@ -983,56 +1202,67 @@
6. TOP CREATORS — pill badges
═══════════════════════════════════════════════════════════════════ */
.cl-creators {
- padding: 1.5rem 0 2rem;
+ padding: 2.5rem 0 3rem;
}
.cl-creators__list {
display: flex;
flex-wrap: wrap;
- gap: 0.6rem;
+ gap: 1rem;
}
.cl-creator-pill {
display: flex;
align-items: center;
- gap: 0.5rem;
- padding: 0.35rem 0.85rem 0.35rem 0.35rem;
+ gap: 0.75rem;
+ padding: 0.6rem 1.25rem 0.6rem 0.6rem;
background: var(--cl-card);
border: 1px solid var(--cl-border);
border-radius: 50px;
text-decoration: none;
- transition: all 0.2s ease;
+ transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
+ backdrop-filter: var(--cl-blur);
+ -webkit-backdrop-filter: var(--cl-blur);
}
.cl-creator-pill:hover {
border-color: var(--cl-border-hover);
background: var(--cl-accent-subtle);
+ transform: translateY(-3px);
+ box-shadow: 0 8px 24px var(--cl-shadow);
}
.cl-creator-pill__avatar {
- width: 32px;
- height: 32px;
+ width: 48px;
+ height: 48px;
border-radius: 50%;
object-fit: cover;
border: 2px solid var(--cl-border);
+ transition: border-color 0.3s ease;
+}
+
+.cl-creator-pill:hover .cl-creator-pill__avatar {
+ border-color: var(--cl-accent);
}
.cl-creator-pill__name {
- font-size: 0.82rem;
- font-weight: 600;
+ font-size: 0.95rem;
+ font-weight: 700;
color: var(--cl-text-strong);
white-space: nowrap;
}
.cl-creator-pill__count {
- font-size: 0.72rem;
+ font-size: 0.8rem;
color: var(--cl-accent);
white-space: nowrap;
- font-weight: 700;
+ font-weight: 800;
margin-left: auto;
- padding-left: 0.4rem;
- min-width: 1.2em;
+ padding: 0.15rem 0.6rem;
+ min-width: 1.5em;
text-align: center;
+ background: var(--cl-accent-subtle);
+ border-radius: 50px;
}
/* ═══════════════════════════════════════════════════════════════════
@@ -1435,4 +1665,8 @@
opacity: 1;
transform: none;
}
+
+ .cl-hero-play {
+ animation: none;
+ }
}
\ No newline at end of file
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 3d7bab8..48b583f 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -35,6 +35,7 @@ en:
hero_primary_button_url: "URL the primary button links to. Use a relative path like /latest or an absolute URL."
hero_secondary_button_label: "Text on the secondary (outlined) CTA button."
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_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_min_height: "Minimum height for the hero section in pixels. Set to 0 for auto height."
@@ -77,6 +78,7 @@ en:
topics_enabled: "━━ ROW 5: TRENDING ━━ — Show the Trending Discussions section: a horizontally scrollable row of topic cards showing the most active discussions. Each card displays category badge, title, reply count, and like count — all live data. Supports drag-to-scroll and native swipe."
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_min_height: "Minimum height for the trending section in pixels. Set to 0 for auto height."
diff --git a/config/settings.yml b/config/settings.yml
index bde0ae8..a9a4fa3 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -82,7 +82,7 @@ plugins:
type: string
navbar_bg_color:
default: ""
- type: string
+ type: color
navbar_border_style:
default: "none"
type: enum
@@ -127,12 +127,15 @@ plugins:
hero_secondary_button_url:
default: "/login"
type: string
+ hero_video_url:
+ default: ""
+ type: string
hero_bg_dark:
default: ""
- type: string
+ type: color
hero_bg_light:
default: ""
- type: string
+ type: color
hero_min_height:
default: 0
type: integer
@@ -158,7 +161,7 @@ plugins:
type: color
stat_icon_bg_color:
default: ""
- type: string
+ type: color
stat_icon_shape:
default: "circle"
type: enum
@@ -167,7 +170,7 @@ plugins:
- rounded
stat_counter_color:
default: ""
- type: string
+ type: color
stat_members_label:
default: "Members"
type: string
@@ -185,10 +188,10 @@ plugins:
type: string
stats_bg_dark:
default: ""
- type: string
+ type: color
stats_bg_light:
default: ""
- type: string
+ type: color
stats_min_height:
default: 0
type: integer
@@ -241,10 +244,10 @@ plugins:
type: string
about_bg_dark:
default: ""
- type: string
+ type: color
about_bg_light:
default: ""
- type: string
+ type: color
about_min_height:
default: 0
type: integer
@@ -271,12 +274,15 @@ plugins:
topics_count:
default: 5
type: integer
+ topics_card_bg_color:
+ default: ""
+ type: color
topics_bg_dark:
default: ""
- type: string
+ type: color
topics_bg_light:
default: ""
- type: string
+ type: color
topics_min_height:
default: 0
type: integer
@@ -308,10 +314,10 @@ plugins:
type: integer
contributors_bg_dark:
default: ""
- type: string
+ type: color
contributors_bg_light:
default: ""
- type: string
+ type: color
contributors_min_height:
default: 0
type: integer
@@ -343,13 +349,13 @@ plugins:
type: list
groups_card_bg_color:
default: ""
- type: string
+ type: color
groups_bg_dark:
default: ""
- type: string
+ type: color
groups_bg_light:
default: ""
- type: string
+ type: color
groups_min_height:
default: 0
type: integer
@@ -414,10 +420,10 @@ plugins:
type: string
app_cta_bg_dark:
default: ""
- type: string
+ type: color
app_cta_bg_light:
default: ""
- type: string
+ type: color
app_cta_min_height:
default: 0
type: integer
@@ -446,10 +452,10 @@ plugins:
type: string
footer_bg_dark:
default: ""
- type: string
+ type: color
footer_bg_light:
default: ""
- type: string
+ type: color
footer_border_style:
default: "solid"
type: enum
diff --git a/lib/community_landing/icons.rb b/lib/community_landing/icons.rb
index 678ff6d..7deea3a 100644
--- a/lib/community_landing/icons.rb
+++ b/lib/community_landing/icons.rb
@@ -12,6 +12,8 @@ module CommunityLanding
STAT_LIKES_SVG = ''
STAT_CHATS_SVG = ''
+ PLAY_SVG = ''
+
COMMENT_SVG = ''
HEART_SVG = ''
diff --git a/lib/community_landing/page_builder.rb b/lib/community_landing/page_builder.rb
index 0023c2b..24d63a9 100644
--- a/lib/community_landing/page_builder.rb
+++ b/lib/community_landing/page_builder.rb
@@ -29,6 +29,7 @@ module CommunityLanding
html << render_app_cta
html << render_footer_desc
html << render_footer
+ html << render_video_modal
html << "\n"
html << "