Released New Website

This commit is contained in:
2026-08-21 14:48:57 -07:00
parent e58db3d719
commit ade74f1280
86 changed files with 3683 additions and 276 deletions

View 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>';
}
}
}

View 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);
}

View 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');
}
}

View 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 = '';
}
}

View 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>';
}
}

View 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>';
}
}
}

View 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();
}

View 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>';
}
}
}