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,39 @@
import { createElement, qs } from '../utils/dom.js';
function closeAllExcept(list, exceptEl) {
list.querySelectorAll('.faq-item.open').forEach((openEl) => {
if (openEl === exceptEl) return;
openEl.classList.remove('open');
qs('.faq-question', openEl).setAttribute('aria-expanded', 'false');
});
}
export function createFaqItem(item, list) {
const el = createElement('div', 'faq-item reveal');
el.innerHTML = `
<button class="faq-question" aria-expanded="false">
<span>${item.q}</span>
<svg class="chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M6 9l6 6 6-6" />
</svg>
</button>
<div class="faq-answer">
<div class="faq-answer-inner">${item.a}</div>
</div>
`;
const question = qs('.faq-question', el);
question.addEventListener('click', () => {
const isOpen = el.classList.contains('open');
closeAllExcept(list, el);
if (!isOpen) {
el.classList.add('open');
question.setAttribute('aria-expanded', 'true');
} else {
el.classList.remove('open');
question.setAttribute('aria-expanded', 'false');
}
});
return el;
}

View File

@@ -0,0 +1,62 @@
import { copyToClipboard, flashFeedback, flashClass } from '../utils/clipboard.js';
import { extractGlowColors } from '../utils/color-extract.js';
import { createElement, qs } from '../utils/dom.js';
import { COPY_FEEDBACK_DURATION_MS } from '../config/constants.js';
function buildLoaderSnippet(script) {
const rawLink = script.links?.[0]?.href || '';
return `loadstring(game:HttpGet("${rawLink}"))()`;
}
function attachGlow(card, img) {
extractGlowColors(img).then((colors) => {
if (!colors) return;
card.style.setProperty('--glow-a', colors.a);
card.style.setProperty('--glow-b', colors.b);
card.classList.add('glow-ready');
});
}
function attachCopyHandler(card, script) {
const copyIcon = qs('.script-copy-icon', card);
card.addEventListener('click', async () => {
const snippet = buildLoaderSnippet(script);
await copyToClipboard(snippet);
if (copyIcon) flashClass(copyIcon, 'copied', COPY_FEEDBACK_DURATION_MS);
});
}
export function createScriptCard(script) {
const card = createElement('div', 'script-card');
let html = `
<div class="script-glow"></div>
<div class="script-image">
<img src="${script.image}" alt="${script.title}" crossorigin="anonymous">
</div>
<h3 class="script-title">
${script.title}
<span class="script-copy-icon">
<svg class="copy-icon" width="20px" height="20px" viewBox="0 -0.5 25 25" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path fill-rule="evenodd" clip-rule="evenodd" d="M8.94605 4.99995L13.2541 4.99995C14.173 5.00498 15.0524 5.37487 15.6986 6.02825C16.3449 6.68163 16.7051 7.56497 16.7001 8.48395V12.716C16.7051 13.6349 16.3449 14.5183 15.6986 15.1717C15.0524 15.825 14.173 16.1949 13.2541 16.2H8.94605C8.02707 16.1949 7.14773 15.825 6.50148 15.1717C5.85522 14.5183 5.495 13.6349 5.50005 12.716L5.50005 8.48495C5.49473 7.5658 5.85484 6.6822 6.50112 6.0286C7.1474 5.375 8.0269 5.00498 8.94605 4.99995Z" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M10.1671 19H14.9371C17.4857 18.9709 19.5284 16.8816 19.5001 14.333V9.666" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>
<svg class="check-icon" width="20px" height="20px" viewBox="0 -0.5 25 25" fill="none"
xmlns="http://www.w3.org/2000/svg">
<g>
<path d="M5.5 12.5L10 17L19.5 7.5"
stroke="#ffffff"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round">
</path>
</g>
</svg>
</span>
</h3>`;
if (script.description) {
html += `<p class="script-desc">${script.description}</p>`;
}
card.innerHTML = html;
attachGlow(card, qs('.script-image img', card));
attachCopyHandler(card, script);
return card;
}

View File

@@ -0,0 +1,25 @@
import { createElement, qs } from '../utils/dom.js';
import { youtubeThumbnailUrl } from '../utils/youtube.js';
export function createShowcaseItem(video) {
const item = createElement('div', 'showcase-item');
item.innerHTML = `
<button class="showcase-card" type="button" aria-label="Play video">
<img class="showcase-thumb" src="${youtubeThumbnailUrl(video.id)}" alt="">
</button>
`;
const img = qs('img', item);
img.addEventListener('load', () => item.classList.add('loaded'));
img.addEventListener('error', () => {
item.style.display = 'none';
});
const cardBtn = qs('.showcase-card', item);
cardBtn.addEventListener('click', () => {
cardBtn.classList.add('clicked');
window.open(video.url, '_blank', 'noopener,noreferrer');
});
return item;
}

View File

@@ -0,0 +1,32 @@
import { createElement } from '../utils/dom.js';
function initials(name) {
return name
.split(' ')
.map((word) => word[0])
.join('')
.slice(0, 2)
.toUpperCase();
}
export function createTestimonialCard(testimonial) {
const card = createElement('div', 'testimonial-card');
const avatar = testimonial.avatar
? `<img src="${testimonial.avatar}" alt="">`
: initials(testimonial.name || '?');
card.innerHTML = `
<div class="testimonial-top">
<div class="testimonial-user">
<div class="testimonial-avatar">${avatar}</div>
<div class="testimonial-names">
<span class="testimonial-name">${testimonial.name}</span>
<span class="testimonial-handle">${testimonial.handle || ''}</span>
</div>
</div>
</div>
<p class="testimonial-text">${testimonial.text}</p>
`;
return card;
}