forked from vyla-entertainment/home-frontend
Released New Website
This commit is contained in:
35
src/scripts/sections/executors.js
Normal file
35
src/scripts/sections/executors.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { getExecutors } from '../services/executors-service.js';
|
||||
import { createElement } from '../utils/dom.js';
|
||||
import { createMarqueeLoop, populateLoopedTrack } from '../animations/marquee-loop.js';
|
||||
|
||||
const EXECUTOR_ITEM_WIDTH_PX = 180;
|
||||
const BASE_SPEED = 0.35;
|
||||
const BOOSTED_SPEED = 2.2;
|
||||
|
||||
function createExecutorItem(executor) {
|
||||
const el = createElement('div', 'executor-item');
|
||||
const avatar = executor.image
|
||||
? `<img src="${executor.image}" alt="" " onerror="this.parentElement.innerHTML=''">`
|
||||
: '';
|
||||
el.innerHTML = `
|
||||
<span class="executor-avatar">${avatar}</span>
|
||||
<span class="executor-name">${executor.name}</span>
|
||||
`;
|
||||
return el;
|
||||
}
|
||||
|
||||
export async function initExecutorsSection() {
|
||||
const track = document.getElementById('executors-track');
|
||||
if (!track) return;
|
||||
|
||||
try {
|
||||
const executors = await getExecutors();
|
||||
populateLoopedTrack(track, executors, createExecutorItem, EXECUTOR_ITEM_WIDTH_PX);
|
||||
createMarqueeLoop(track, '--marquee-x', { baseSpeed: BASE_SPEED, boostedSpeed: BOOSTED_SPEED });
|
||||
} catch (error) {
|
||||
const wrap = document.querySelector('.executors-marquee-wrap');
|
||||
if (wrap) {
|
||||
wrap.innerHTML = '<p style="color: var(--color-muted); text-align:center; width:100%">executors unavailable.</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/scripts/sections/faq.js
Normal file
15
src/scripts/sections/faq.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { FAQ_ITEMS } from '../config/faq-data.js';
|
||||
import { createFaqItem } from '../components/faq-item.js';
|
||||
import { observeReveals } from '../animations/reveal-observer.js';
|
||||
|
||||
export function initFaqSection() {
|
||||
const list = document.getElementById('faq-list');
|
||||
if (!list) return;
|
||||
|
||||
list.innerHTML = '';
|
||||
FAQ_ITEMS.forEach((item) => {
|
||||
list.appendChild(createFaqItem(item, list));
|
||||
});
|
||||
|
||||
observeReveals(list);
|
||||
}
|
||||
55
src/scripts/sections/hero.js
Normal file
55
src/scripts/sections/hero.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { qs, isMobileViewport } from '../utils/dom.js';
|
||||
import { copyToClipboard, flashFeedback, flashClass } from '../utils/clipboard.js';
|
||||
import { typewriterReveal } from '../animations/typewriter.js';
|
||||
import { initFluidByteField } from '../animations/fluid-byte-field.js';
|
||||
import { DISCORD_LOADER_SCRIPT, COPY_FEEDBACK_DURATION_MS } from '../config/constants.js';
|
||||
|
||||
const HERO_GREETING = 'Soluna Development';
|
||||
|
||||
function initHeroTitle() {
|
||||
const heroTitle = qs('.hero-title');
|
||||
if (heroTitle) typewriterReveal(heroTitle, HERO_GREETING);
|
||||
}
|
||||
|
||||
function initDiscordCopyBar() {
|
||||
const copyBtn = document.getElementById('copy-discord-btn');
|
||||
const copyLabel = document.getElementById('copy-discord-label');
|
||||
const copyIcon = document.getElementById('copy-discord-icon');
|
||||
if (!copyBtn) return;
|
||||
|
||||
copyBtn.addEventListener('click', async () => {
|
||||
await copyToClipboard(DISCORD_LOADER_SCRIPT);
|
||||
if (copyIcon) flashClass(copyIcon, 'copied', COPY_FEEDBACK_DURATION_MS);
|
||||
});
|
||||
}
|
||||
|
||||
function initHeroCopyButton() {
|
||||
const heroCopyScriptBtn = document.getElementById('hero-copy-script-btn');
|
||||
if (!heroCopyScriptBtn) return;
|
||||
|
||||
heroCopyScriptBtn.addEventListener('click', async () => {
|
||||
await copyToClipboard(DISCORD_LOADER_SCRIPT);
|
||||
flashFeedback(heroCopyScriptBtn, 'Copied!', COPY_FEEDBACK_DURATION_MS);
|
||||
});
|
||||
}
|
||||
|
||||
function initCtaCopyButton() {
|
||||
const ctaCopyScriptBtn = document.getElementById('cta-copy-script-btn');
|
||||
if (!ctaCopyScriptBtn) return;
|
||||
|
||||
ctaCopyScriptBtn.addEventListener('click', async () => {
|
||||
await copyToClipboard(DISCORD_LOADER_SCRIPT);
|
||||
flashFeedback(ctaCopyScriptBtn, 'Copied!', COPY_FEEDBACK_DURATION_MS);
|
||||
});
|
||||
}
|
||||
|
||||
export function initHeroSection() {
|
||||
initHeroTitle();
|
||||
initDiscordCopyBar();
|
||||
initHeroCopyButton();
|
||||
initCtaCopyButton();
|
||||
if (!isMobileViewport()) {
|
||||
initFluidByteField('hero-byte-overlay');
|
||||
initFluidByteField('cta-byte-overlay');
|
||||
}
|
||||
}
|
||||
26
src/scripts/sections/partners.js
Normal file
26
src/scripts/sections/partners.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { getPartners } from '../services/partners-service.js';
|
||||
import { createElement } from '../utils/dom.js';
|
||||
import { observeReveals } from '../animations/reveal-observer.js';
|
||||
|
||||
function createPartnerItem(partner) {
|
||||
const item = createElement('div', 'partner-item reveal');
|
||||
const inner = partner.link
|
||||
? `<a href="${partner.link}" target="_blank" rel="noopener noreferrer"><img src="${partner.logo}" alt="${partner.name}" "></a>`
|
||||
: `<img src="${partner.logo}" alt="${partner.name}" ">`;
|
||||
item.innerHTML = inner;
|
||||
return item;
|
||||
}
|
||||
|
||||
export async function initPartnersSection() {
|
||||
const grid = document.getElementById('partners-grid');
|
||||
if (!grid) return;
|
||||
|
||||
try {
|
||||
const partners = await getPartners();
|
||||
grid.innerHTML = '';
|
||||
partners.forEach((partner) => grid.appendChild(createPartnerItem(partner)));
|
||||
observeReveals(grid);
|
||||
} catch (error) {
|
||||
grid.innerHTML = '';
|
||||
}
|
||||
}
|
||||
40
src/scripts/sections/scripts.js
Normal file
40
src/scripts/sections/scripts.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { getScripts } from '../services/scripts-service.js';
|
||||
import { createScriptCard } from '../components/script-card.js';
|
||||
import { debounce, isMobileViewport } from '../utils/dom.js';
|
||||
|
||||
let snapObserver = null;
|
||||
|
||||
function setupSnapGlow(grid) {
|
||||
if (snapObserver) snapObserver.disconnect();
|
||||
if (!isMobileViewport()) return;
|
||||
|
||||
snapObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
entry.target.classList.toggle('in-snap', entry.intersectionRatio >= 0.7);
|
||||
});
|
||||
},
|
||||
{ root: grid, threshold: [0, 0.7, 1] }
|
||||
);
|
||||
|
||||
grid.querySelectorAll('.script-card').forEach((card) => snapObserver.observe(card));
|
||||
}
|
||||
|
||||
export async function initScriptsSection() {
|
||||
const grid = document.getElementById('scripts-grid');
|
||||
if (!grid) return;
|
||||
|
||||
try {
|
||||
const scripts = await getScripts();
|
||||
grid.innerHTML = '';
|
||||
scripts.forEach((script) => {
|
||||
const card = createScriptCard(script);
|
||||
grid.appendChild(card);
|
||||
});
|
||||
setupSnapGlow(grid);
|
||||
|
||||
window.addEventListener('resize', debounce(() => setupSnapGlow(grid), 200));
|
||||
} catch (error) {
|
||||
grid.innerHTML = '<p style="color: var(--color-muted)">scripts unavailable.</p>';
|
||||
}
|
||||
}
|
||||
23
src/scripts/sections/showcases.js
Normal file
23
src/scripts/sections/showcases.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { getShowcaseVideos } from '../services/showcases-service.js';
|
||||
import { createShowcaseItem } from '../components/showcase-item.js';
|
||||
import { createMarqueeLoop, populateLoopedTrack } from '../animations/marquee-loop.js';
|
||||
|
||||
const SHOWCASE_ITEM_WIDTH_PX = 280;
|
||||
const BASE_SPEED = 0.4;
|
||||
const BOOSTED_SPEED = 2.4;
|
||||
|
||||
export async function initShowcasesSection() {
|
||||
const track = document.getElementById('showcase-track');
|
||||
if (!track) return;
|
||||
|
||||
try {
|
||||
const videos = await getShowcaseVideos();
|
||||
populateLoopedTrack(track, videos, createShowcaseItem, SHOWCASE_ITEM_WIDTH_PX);
|
||||
createMarqueeLoop(track, '--showcase-x', { baseSpeed: BASE_SPEED, boostedSpeed: BOOSTED_SPEED });
|
||||
} catch (error) {
|
||||
const viewport = document.querySelector('.showcase-viewport');
|
||||
if (viewport) {
|
||||
viewport.innerHTML = '<p style="color: var(--color-muted); text-align:center; width:100%">showcases unavailable.</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/scripts/sections/stats.js
Normal file
52
src/scripts/sections/stats.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { getDiscordStats } from '../services/discord-service.js';
|
||||
import { getScripts } from '../services/scripts-service.js';
|
||||
import { animateCountOnVisible } from '../animations/counter.js';
|
||||
|
||||
async function initDiscordStats() {
|
||||
const membersEl = document.getElementById('stat-members');
|
||||
const onlineEl = document.getElementById('stat-online');
|
||||
if (!membersEl || !onlineEl) return;
|
||||
|
||||
try {
|
||||
const { memberCount, onlineCount } = await getDiscordStats();
|
||||
const statsGrid = document.getElementById('stats-grid');
|
||||
const onlineItem = onlineEl.closest('.stat-item');
|
||||
if (onlineItem) onlineItem.classList.add('stat-online');
|
||||
|
||||
if (statsGrid) {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
animateCountOnVisible(membersEl, memberCount);
|
||||
animateCountOnVisible(onlineEl, onlineCount);
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.5 }
|
||||
);
|
||||
observer.observe(statsGrid);
|
||||
}
|
||||
} catch (error) {
|
||||
membersEl.textContent = '-';
|
||||
onlineEl.textContent = '-';
|
||||
}
|
||||
}
|
||||
|
||||
async function initScriptCountStat() {
|
||||
const scriptsEl = document.getElementById('stat-scripts');
|
||||
if (!scriptsEl) return;
|
||||
|
||||
try {
|
||||
const scripts = await getScripts();
|
||||
animateCountOnVisible(scriptsEl, scripts.length);
|
||||
} catch (error) {
|
||||
scriptsEl.textContent = '-';
|
||||
}
|
||||
}
|
||||
|
||||
export function initStatsSection() {
|
||||
initDiscordStats();
|
||||
initScriptCountStat();
|
||||
}
|
||||
38
src/scripts/sections/testimonials.js
Normal file
38
src/scripts/sections/testimonials.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { getReviews } from '../services/reviews-service.js';
|
||||
import { createTestimonialCard } from '../components/testimonial-card.js';
|
||||
|
||||
const AVG_TESTIMONIAL_WIDTH_PX = 260;
|
||||
|
||||
function fillMarqueeTrack(trackEl, reviews) {
|
||||
trackEl.innerHTML = '';
|
||||
if (!reviews.length) return;
|
||||
|
||||
const minSets = Math.max(1, Math.ceil((window.innerWidth * 3) / (reviews.length * AVG_TESTIMONIAL_WIDTH_PX)));
|
||||
for (let i = 0; i < minSets; i++) {
|
||||
reviews.forEach((review) => trackEl.appendChild(createTestimonialCard(review)));
|
||||
}
|
||||
|
||||
const firstPass = Array.from(trackEl.children).map((card) => card.cloneNode(true));
|
||||
firstPass.forEach((card) => trackEl.appendChild(card));
|
||||
}
|
||||
|
||||
export async function initTestimonialsSection() {
|
||||
const track1 = document.getElementById('marquee-track-1');
|
||||
const track2 = document.getElementById('marquee-track-2');
|
||||
if (!track1 || !track2) return;
|
||||
|
||||
try {
|
||||
const reviews = await getReviews();
|
||||
const mid = Math.ceil(reviews.length / 2);
|
||||
const rowA = reviews.slice(0, mid);
|
||||
const rowB = reviews.slice(mid);
|
||||
|
||||
fillMarqueeTrack(track1, rowA.length ? rowA : reviews);
|
||||
fillMarqueeTrack(track2, rowB.length ? rowB : reviews);
|
||||
} catch (error) {
|
||||
const wrap = document.querySelector('.marquee-wrap');
|
||||
if (wrap) {
|
||||
wrap.innerHTML = '<p style="color: var(--color-muted); text-align:center; width:100%">reviews unavailable.</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user