This commit is contained in:
2026-08-21 19:28:21 -07:00
parent 4af87f4f9c
commit acf97ae05d
15 changed files with 82 additions and 92 deletions

View File

@@ -1,7 +1,7 @@
import { prefersReducedMotion } from '../utils/dom.js';
import { smoothstep, diffuse, project, advect } from './fluid-solver.js';
const CELL_SIZE = 24;
const CELL_SIZE = 28;
const FONT_SIZE = 11;
const DT = 1 / 30;
const FADE_RATE = 0.6;
@@ -9,8 +9,8 @@ const MAX_ALPHA = 0.85;
const ADVECT_SCALE = 0.5;
const SPLASH_RADIUS_PX = 28;
const SPLASH_FORCE = 0.5;
const DIFFUSION_ITERATIONS = 3;
const PROJECT_ITERATIONS = 4;
const DIFFUSION_ITERATIONS = 2;
const PROJECT_ITERATIONS = 3;
const VELOCITY_DAMP = 0.1;
const DYE_DAMP = 0.1;
const TRAIL_LIFETIME_MS = 184;

View File

@@ -34,7 +34,7 @@ export function createMarqueeLoop(track, cssVar, { baseSpeed, boostedSpeed, minS
export function populateLoopedTrack(track, items, buildItem, itemWidthPx) {
track.innerHTML = '';
const minSets = Math.max(2, Math.ceil((window.innerWidth * 2.5) / (items.length * itemWidthPx)));
const minSets = Math.max(2, Math.ceil((window.innerWidth * 1.5) / (items.length * itemWidthPx)));
for (let i = 0; i < minSets; i++) {
items.forEach((item) => track.appendChild(buildItem(item)));
}

View File

@@ -10,6 +10,7 @@ function closeAllExcept(list, exceptEl) {
export function createFaqItem(item, list) {
const el = createElement('div', 'faq-item reveal');
el.setAttribute('role', 'listitem');
el.innerHTML = `
<button class="faq-question" aria-expanded="false">
<span>${item.q}</span>

View File

@@ -31,7 +31,7 @@ export function createScriptCard(script) {
let html = `
<div class="script-glow"></div>
<div class="script-image">
<img src="${script.image}" alt="${script.title}" crossorigin="anonymous">
<img src="${script.image}" alt="${script.title}" crossorigin="anonymous" width="300" height="168" loading="lazy" decoding="async">
</div>
<h3 class="script-title">
${script.title}

View File

@@ -5,7 +5,7 @@ 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="">
<img class="showcase-thumb" src="${youtubeThumbnailUrl(video.id)}" alt="" width="280" height="157" loading="lazy" decoding="async">
</button>
`;

View File

@@ -12,7 +12,7 @@ function initials(name) {
export function createTestimonialCard(testimonial) {
const card = createElement('div', 'testimonial-card');
const avatar = testimonial.avatar
? `<img src="${testimonial.avatar}" alt="">`
? `<img src="${testimonial.avatar}" alt="" width="34" height="34" loading="lazy" decoding="async">`
: initials(testimonial.name || '?');
card.innerHTML = `

View File

@@ -1,30 +1,26 @@
export const FAQ_ITEMS = [
{
q: 'Why was the website version discontinued?',
a: 'Hosting Vyla on a public VPS caused repeated Cloudflare DMCA reports, datacenter IP blocks, and hosting instability. Moving to Vyla Home allows the app to run locally on your own network with zero server dependencies or datacenter blocks.',
q: 'Why did Vyla transition from a web app to Vyla Home?',
a: 'Centralized public hosting caused cloud provider downtime, datacenter IP rate-limiting, and single-point-of-failure vulnerabilities. Moving execution locally to Vyla Home allows direct residential stream resolution without third-party server mediation.',
},
{
q: 'How do I install and launch Vyla Home?',
a: 'Download the latest setup executable (Vyla.Home.Setup.exe) from our official GitLab releases page. Run the installer, and the app will automatically configure all local microservices, player sandboxes, and runtimes.',
q: 'Why does Windows SmartScreen flag the installer?',
a: 'Windows SmartScreen displays an unverified publisher prompt on any open-source binary lacking an expensive commercial EV Code Signing Certificate. Vyla Home is 100% open-source on GitLab, allowing you to audit the build scripts or verify the SHA-256 release checksum manually.',
},
{
q: 'How do updates work?',
a: 'Vyla Home checks for updates directly from the official GitLab repository upon startup. When an update is available, it downloads the payload, verifies SHA-256 checksums, and applies patches automatically.',
q: 'How are my settings, bookmarks, and watch history protected?',
a: 'All user data is stored locally in an AES-256-GCM encrypted database keyed to your device hardware profile. No usage logs, browsing history, or playback metrics are ever transmitted to external servers.',
},
{
q: 'Where are my watch history, favorites, and settings stored?',
a: 'All data is stored locally on your machine in an encrypted vault. Your credentials and settings are secured using AES-256-GCM encryption tied to your unique hardware profile.',
q: 'How does the update mechanism ensure binary integrity?',
a: 'Vyla Home queries the official tagged GitLab release API over HTTPS. Update payloads are verified against published SHA-256 cryptographic hashes before application to prevent tampering.',
},
{
q: 'Do I need to install Node.js, Python, or other dependencies?',
a: 'No. The desktop setup bundles all required runtimes, backend microservices, and dependencies inside an isolated Electron package.',
q: 'Does Vyla Home require external runtime dependencies?',
a: 'No. The standalone installer packages the entire runtime environment, internal microservices, and isolated media decoders inside a self-contained desktop container.',
},
{
q: 'Where can I report bugs or suggest new features?',
a: 'Join our official Discord community where you can report issues, get technical support, submit feature suggestions, and receive update announcements.',
},
{
q: 'Is Vyla Home free to use?',
a: 'Yes, Vyla Home is completely free for personal use with no keys, subscriptions, or paywalls.',
q: 'Is Vyla Home open-source and free?',
a: 'Yes. Vyla Home is completely free and open-source under the MIT license with zero paid tiers, keys, or premium locks.',
},
];

View File

@@ -9,7 +9,7 @@ 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=''">`
? `<img src="${executor.image}" alt="" onerror="this.parentElement.innerHTML=''" width="28" height="28" loading="lazy" decoding="async">`
: '';
el.innerHTML = `
<span class="executor-avatar">${avatar}</span>

View File

@@ -5,8 +5,8 @@ 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}" ">`;
? `<a href="${partner.link}" target="_blank" rel="noopener noreferrer"><img src="${partner.logo}" alt="${partner.name}" width="128" height="128" loading="lazy" decoding="async"></a>`
: `<img src="${partner.logo}" alt="${partner.name}" width="128" height="128" loading="lazy" decoding="async">`;
item.innerHTML = inner;
return item;
}

View File

@@ -7,7 +7,7 @@ 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)));
const minSets = Math.max(1, Math.ceil((window.innerWidth * 2) / (reviews.length * AVG_TESTIMONIAL_WIDTH_PX)));
for (let i = 0; i < minSets; i++) {
reviews.forEach((review) => trackEl.appendChild(createTestimonialCard(review)));
}

View File

@@ -1,35 +1 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap');
@import 'base/tokens.css';
@import 'base/reset.css';
@import 'base/typography.css';
@import 'utilities/layout.css';
@import 'utilities/reveal.css';
@import 'layout/nav.css';
@import 'layout/footer.css';
@import 'components/buttons.css';
@import 'components/pills.css';
@import 'components/stats.css';
@import 'components/script-card.css';
@import 'components/marquee.css';
@import 'components/testimonial-card.css';
@import 'components/executors.css';
@import 'components/showcase.css';
@import 'components/modal.css';
@import 'components/partners.css';
@import 'components/faq.css';
@import 'sections/hero-atmosphere.css';
@import 'sections/hero-content.css';
@import 'sections/section-base.css';
@import 'sections/testimonials.css';
@import 'sections/executors.css';
@import 'sections/features.css';
@import 'sections/showcases.css';
@import 'sections/faq-section.css';
@import 'sections/cta-banner.css';
@import 'utilities/responsive.css';
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap');

View File

@@ -33,7 +33,6 @@
.hero-title .char {
display: inline-block;
opacity: 0;
filter: blur(10px);
animation: char-in 0.4s ease forwards;
}
@@ -54,7 +53,6 @@
@keyframes char-in {
to {
opacity: 1;
filter: blur(0);
}
}
@@ -62,7 +60,6 @@
.hero-title .char {
animation: none;
opacity: 1;
filter: none;
}
}

View File

@@ -1,16 +1,13 @@
.reveal {
opacity: 0;
filter: blur(12px);
transform: translate3d(0, 24px, 0);
transition: opacity var(--duration-slow) var(--ease-standard),
filter var(--duration-slow) var(--ease-standard),
transform var(--duration-slow) var(--ease-standard);
will-change: opacity, filter, transform;
will-change: opacity, transform;
}
.reveal.in-view {
opacity: 1;
filter: blur(0);
transform: translate3d(0, 0, 0);
}
@@ -45,7 +42,6 @@
@media (prefers-reduced-motion: reduce) {
.reveal {
opacity: 1;
filter: none;
transform: none;
transition: none;
}