updated UI

This commit is contained in:
2026-09-10 20:15:03 -07:00
parent 806b32e30e
commit a323019d32
17 changed files with 305 additions and 98 deletions

View File

@@ -84,8 +84,11 @@
<link rel="apple-touch-icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap">
<!-- Critical CSS - loaded immediately -->
<link rel="stylesheet" href="src/styles/base/tokens.css">
<link rel="stylesheet" href="src/styles/base/reset.css">
<link rel="stylesheet" href="src/styles/base/typography.css">
@@ -96,6 +99,7 @@
<link rel="stylesheet" href="src/styles/sections/hero-atmosphere.css">
<link rel="stylesheet" href="src/styles/sections/hero-content.css">
<!-- Non-critical CSS - deferred loading -->
<link rel="stylesheet" href="src/styles/layout/footer.css" media="print" onload="this.media='all'">
<link rel="stylesheet" href="src/styles/components/buttons.css" media="print" onload="this.media='all'">
<link rel="stylesheet" href="src/styles/components/pills.css" media="print" onload="this.media='all'">
@@ -267,12 +271,14 @@
<main role="main">
<section class="hero" aria-labelledby="hero-title">
<div class="hero-atmosphere" aria-hidden="true">
<div id="hero-prism" class="hero-prism"></div>
<video class="hero-atmosphere-video" autoplay muted loop playsinline preload="auto">
<source src="https://www.pexels.com/download/video/39333459/" type="video/mp4">
</video>
</div>
<div class="container hero-container">
<div class="logo-image">
<img src="assets/images/header_logo.png" alt="Vyla Home Logo" width="100" height="100"
fetchpriority="high"
fetchpriority="high" loading="eager" decoding="async"
srcset="assets/images/header_logo.png 100w, assets/images/header_logo.png 200w" sizes="100px">
</div>
<h1 id="hero-title" class="hero-title">The Local-First Desktop Media Center</h1>
@@ -479,7 +485,7 @@
<figure class="feature-media">
<img src="assets/images/features/1.png"
alt="Diagram showing datacenter IP throttling bypass with residential network connection"
width="800" height="450" loading="lazy" decoding="async"
width="800" height="450" loading="lazy" decoding="async" fetchpriority="low"
srcset="assets/images/features/1.png 800w, assets/images/features/1.png 400w"
sizes="(max-width: 768px) 400px, 800px">
</figure>
@@ -494,7 +500,7 @@
<figure class="feature-media">
<img src="assets/images/features/2.png"
alt="Diagram showing decentralized architecture eliminating single point of failure"
width="800" height="450" loading="lazy" decoding="async"
width="800" height="450" loading="lazy" decoding="async" fetchpriority="low"
srcset="assets/images/features/2.png 800w, assets/images/features/2.png 400w"
sizes="(max-width: 768px) 400px, 800px">
</figure>
@@ -508,7 +514,7 @@
<figure class="feature-media">
<img src="assets/images/features/3.png"
alt="Illustration of encrypted local vault with hardware profile key" width="800"
height="450" loading="lazy" decoding="async"
height="450" loading="lazy" decoding="async" fetchpriority="low"
srcset="assets/images/features/3.png 800w, assets/images/features/3.png 400w"
sizes="(max-width: 768px) 400px, 800px">
</figure>
@@ -522,7 +528,7 @@
<figure class="feature-media">
<img src="assets/images/features/4.png"
alt="Interface showing automated over-the-air update process" width="800" height="450"
loading="lazy" decoding="async"
loading="lazy" decoding="async" fetchpriority="low"
srcset="assets/images/features/4.png 800w, assets/images/features/4.png 400w"
sizes="(max-width: 768px) 400px, 800px">
</figure>
@@ -536,7 +542,7 @@
<figure class="feature-media">
<img src="assets/images/features/5.png"
alt="Interface showing rich media discovery with curated collections and 4K content"
width="800" height="450" loading="lazy" decoding="async"
width="800" height="450" loading="lazy" decoding="async" fetchpriority="low"
srcset="assets/images/features/5.png 800w, assets/images/features/5.png 400w"
sizes="(max-width: 768px) 400px, 800px">
</figure>
@@ -550,7 +556,7 @@
<figure class="feature-media">
<img src="assets/images/features/6.png"
alt="Interface showing customizable theme and layout options" width="800" height="450"
loading="lazy" decoding="async"
loading="lazy" decoding="async" fetchpriority="low"
srcset="assets/images/features/6.png 800w, assets/images/features/6.png 400w"
sizes="(max-width: 768px) 400px, 800px">
</figure>
@@ -750,7 +756,7 @@
</div>
<img src="https://app.greenweb.org/api/v3/greencheckimage/vyla.cc"
alt="This website runs on green hosting - verified by thegreenwebfoundation.org" width="200"
height="95" loading="lazy" decoding="async">
height="95" loading="lazy" decoding="async" fetchpriority="low">
</div>
<div class="footer-bottom">
<a href="/fuckinggodihatethisprojectijustwantoenditall" class="secret-footer">

View File

@@ -153,7 +153,15 @@ class GlassSurface {
}
applyGlassEffect() {
if (this.svgSupported && this.backdropSupported) {
const useBackdropOnly = this.config.useBackdropOnly === true;
if (useBackdropOnly || !this.svgSupported) {
if (this.backdropSupported) {
this.applyBackdropFallback();
} else {
this.applyBasicFallback();
}
} else if (this.svgSupported && this.backdropSupported) {
this.applySVGFilter();
} else if (this.backdropSupported) {
this.applyBackdropFallback();
@@ -394,7 +402,7 @@ function destroyGlassSurface(el) {
delete el.dataset.hasGlass;
}
function initLiquidGlass() {
function initLiquidGlass(configOverride = {}) {
const config = {
width: 200,
height: 25,
@@ -402,7 +410,7 @@ function initLiquidGlass() {
borderWidth: 0.07,
brightness: 50,
opacity: 0.93,
blur: 11,
blur: configOverride.reducedBlur ? 6 : 11,
displace: 0,
backgroundOpacity: 0,
saturation: 1,
@@ -418,7 +426,6 @@ function initLiquidGlass() {
const eagerSelectors = [
'.nav-links',
'.discord-copy-bar',
'.platform-pill',
'.hero-cta-unsure',
'#navIslandMain',
'#navbarSearchToggle',

View File

@@ -3,6 +3,8 @@ import { Renderer, Triangle, Program, Mesh } from 'https://esm.sh/ogl@1.0.11';
export function initPrism(container, options = {}) {
if (!container) return () => { };
const stepMultiplier = options.stepMultiplier ?? 1.0;
const H = Math.max(0.001, options.height ?? 3.5);
const BW = Math.max(0.001, options.baseWidth ?? 5.5);
const BASE_HALF = BW * 0.5;
@@ -76,6 +78,7 @@ export function initPrism(container, options = {}) {
uniform float uMinAxis;
uniform float uPxScale;
uniform float uTimeScale;
uniform float uStepMultiplier;
vec4 tanh4(vec4 x){
vec4 e2x = exp(2.0*x);
@@ -139,8 +142,12 @@ export function initPrism(container, options = {}) {
wob = mat2(c0, c1, c2, c0);
}
const int STEPS = 100;
for (int i = 0; i < STEPS; i++) {
const int MAX_STEPS = 100;
float stepMultiplier = uStepMultiplier;
int actualSteps = int(floor(float(MAX_STEPS) * stepMultiplier));
for (int i = 0; i < MAX_STEPS; i++) {
if (i >= actualSteps) break;
p = vec3(f, z);
p.xz = p.xz * wob;
p = uRot * p;
@@ -198,7 +205,8 @@ export function initPrism(container, options = {}) {
uPxScale: {
value: 1 / ((gl.drawingBufferHeight || 1) * 0.1 * SCALE)
},
uTimeScale: { value: TS }
uTimeScale: { value: TS },
uStepMultiplier: { value: stepMultiplier }
}
});
const mesh = new Mesh(gl, { geometry, program });

View File

@@ -5,15 +5,21 @@ import { initTestimonialsSection } from './sections/testimonials.js';
import { initPartnersSection } from './sections/partners.js';
import { initFaqSection } from './sections/faq.js';
import { initStatsSection } from './sections/stats.js';
import { getDeviceCapability, getAnimationConfig } from './utils/device-capability.js';
function initApp() {
const deviceCapability = getDeviceCapability();
const animationConfig = getAnimationConfig();
observeReveals();
initLiquidGlass();
if (animationConfig.glassSurface.enabled) {
initLiquidGlass(animationConfig.glassSurface);
}
initHeroSection();
initHeroSection(animationConfig);
initStatsSection();
initTestimonialsSection();
initTestimonialsSection(animationConfig);
initPartnersSection();
initFaqSection();
}

View File

@@ -1,17 +1,20 @@
import { getPartners } from '../services/partners-service.js';
import { createElement } from '../utils/dom.js';
import { observeReveals } from '../animations/reveal-observer.js';
import { shouldReduceEffects } from '../utils/device-capability.js';
function createPartnerItem(partner) {
const item = createElement('div', 'partner-item reveal');
const item = createElement('div', 'partner-item');
let imgHTML;
if (partner.logo) {
if (partner.logoSmall) {
imgHTML = `<img src="${partner.logoSmall}" alt="${partner.name}" width="128" height="128" loading="lazy" decoding="async"
fetchpriority="low"
srcset="${partner.logoSmall} 64w, ${partner.logo} 128w" sizes="64px">`;
} else {
imgHTML = `<img src="${partner.logo}" alt="${partner.name}" width="128" height="128" loading="lazy" decoding="async">`;
imgHTML = `<img src="${partner.logo}" alt="${partner.name}" width="128" height="128" loading="lazy" decoding="async"
fetchpriority="low">`;
}
} else {
imgHTML = '';
@@ -32,7 +35,12 @@ export async function initPartnersSection() {
const partners = await getPartners();
grid.innerHTML = '';
partners.forEach((partner) => grid.appendChild(createPartnerItem(partner)));
observeReveals(grid);
if (shouldReduceEffects()) {
grid.classList.add('in-view');
} else {
observeReveals(grid);
}
} catch (error) {
grid.innerHTML = '';
}

View File

@@ -1,12 +1,19 @@
import { getDiscordStats } from '../services/discord-service.js';
import { getScripts } from '../services/scripts-service.js';
import { animateCountOnVisible } from '../animations/counter.js';
import { shouldReduceEffects } from '../utils/device-capability.js';
async function initDiscordStats() {
const membersEl = document.getElementById('stat-members');
const onlineEl = document.getElementById('stat-online');
if (!membersEl || !onlineEl) return;
if (shouldReduceEffects()) {
membersEl.textContent = '1K+';
onlineEl.textContent = '100+';
return;
}
try {
const { memberCount, onlineCount } = await getDiscordStats();
const statsGrid = document.getElementById('stats-grid');
@@ -38,6 +45,11 @@ async function initScriptCountStat() {
const scriptsEl = document.getElementById('stat-scripts');
if (!scriptsEl) return;
if (shouldReduceEffects()) {
scriptsEl.textContent = '50+';
return;
}
try {
const scripts = await getScripts();
animateCountOnVisible(scriptsEl, scripts.length);
@@ -47,6 +59,15 @@ async function initScriptCountStat() {
}
export function initStatsSection() {
initDiscordStats();
initScriptCountStat();
if ('requestIdleCallback' in window) {
window.requestIdleCallback(() => {
initDiscordStats();
initScriptCountStat();
}, { timeout: 2000 });
} else {
setTimeout(() => {
initDiscordStats();
initScriptCountStat();
}, 1000);
}
}

View File

@@ -1,5 +1,6 @@
import { getReviews } from '../services/reviews-service.js';
import { createTestimonialCard } from '../components/testimonial-card.js';
import { createMarqueeLoop } from '../animations/marquee-loop.js';
const AVG_TESTIMONIAL_WIDTH_PX = 260;
@@ -16,7 +17,7 @@ function fillMarqueeTrack(trackEl, reviews) {
firstPass.forEach((card) => trackEl.appendChild(card));
}
export async function initTestimonialsSection() {
export async function initTestimonialsSection(animationConfig) {
const track1 = document.getElementById('marquee-track-1');
const track2 = document.getElementById('marquee-track-2');
if (!track1 || !track2) return;
@@ -29,6 +30,23 @@ export async function initTestimonialsSection() {
fillMarqueeTrack(track1, rowA.length ? rowA : reviews);
fillMarqueeTrack(track2, rowB.length ? rowB : reviews);
if (animationConfig.marquee.enabled) {
const baseSpeed = animationConfig.marquee.reducedSpeed ? 0.3 : 0.6;
const boostedSpeed = animationConfig.marquee.reducedSpeed ? 0.6 : 1.2;
createMarqueeLoop(track1, '--marquee-x-1', {
baseSpeed,
boostedSpeed,
minSetsFor: 'screen'
});
createMarqueeLoop(track2, '--marquee-x-2', {
baseSpeed: -baseSpeed,
boostedSpeed: -boostedSpeed,
minSetsFor: 'screen'
});
}
} catch (error) {
const wrap = document.querySelector('.marquee-wrap');
if (wrap) {

View File

@@ -0,0 +1,142 @@
export class DeviceCapability {
constructor() {
this.lowEndDevice = this.detectLowEndDevice();
this.reducedMotion = this.detectReducedMotion();
this.lowMemory = this.detectLowMemory();
this.slowConnection = this.detectSlowConnection();
this.mobileViewport = this.detectMobileViewport();
this.shouldReduceEffects = this.calculateShouldReduceEffects();
}
detectLowEndDevice() {
const ua = navigator.userAgent.toLowerCase();
const mobileUA = /android|iphone|ipod|ipad|blackberry|iemobile|opera mini/i.test(ua);
const cores = navigator.hardwareConcurrency || 4;
const lowCores = cores <= 2;
const memory = (navigator.deviceMemory || 8);
const lowMemory = memory <= 4;
return mobileUA && (lowCores || lowMemory);
}
detectReducedMotion() {
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
detectLowMemory() {
const memory = navigator.deviceMemory;
if (memory && memory <= 4) return true;
const ua = navigator.userAgent.toLowerCase();
const lowEndUA = /android.*gt-[a-z0-9]+/i.test(ua) ||
/iphone.*os [0-9]+_[0-9]+/i.test(ua);
return lowEndUA;
}
detectSlowConnection() {
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
if (!connection) return false;
const slowTypes = ['2g', '3g'];
const effectiveType = connection.effectiveType;
const saveData = connection.saveData;
return slowTypes.includes(effectiveType) || saveData;
}
detectMobileViewport() {
return window.innerWidth < 768;
}
calculateShouldReduceEffects() {
return this.reducedMotion ||
this.lowEndDevice ||
this.lowMemory ||
this.slowConnection;
}
getAnimationConfig() {
if (this.shouldReduceEffects) {
return {
lightRays: {
enabled: false,
raysSpeed: 0.5,
followMouse: false
},
glassSurface: {
enabled: true,
useBackdropOnly: true,
reducedBlur: true
},
marquee: {
enabled: true,
reducedSpeed: true
},
fluid: {
enabled: false
}
};
}
if (this.mobileViewport) {
return {
lightRays: {
enabled: true,
raysSpeed: 1.0,
followMouse: false
},
glassSurface: {
enabled: true,
useBackdropOnly: false,
reducedBlur: false
},
marquee: {
enabled: true,
reducedSpeed: false
},
fluid: {
enabled: false
}
};
}
return {
lightRays: {
enabled: true,
raysSpeed: 1.8,
followMouse: true
},
glassSurface: {
enabled: true,
useBackdropOnly: false,
reducedBlur: false
},
marquee: {
enabled: true,
reducedSpeed: false
},
fluid: {
enabled: true
}
};
}
}
let deviceCapabilityInstance = null;
export function getDeviceCapability() {
if (!deviceCapabilityInstance) {
deviceCapabilityInstance = new DeviceCapability();
}
return deviceCapabilityInstance;
}
export function shouldReduceEffects() {
return getDeviceCapability().shouldReduceEffects;
}
export function getAnimationConfig() {
return getDeviceCapability().getAnimationConfig();
}

View File

@@ -38,8 +38,9 @@
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.7rem 1.35rem;
padding-left: 1rem;
height: 50px !important;
width: 160px;
border-radius: var(--radius-pill) !important;
font-family: var(--font-body);
font-size: 0.8125rem;
@@ -109,11 +110,7 @@
display: inline-block;
background: #000;
border-radius: var(--radius-pill);
overflow: hidden;
}
.download-expandable[aria-expanded="true"] {
border-radius: 10px;
z-index: 100;
}
.download-expandable .hero-cta {
@@ -142,16 +139,23 @@
}
.download-options {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease, opacity 0.3s ease;
position: absolute;
top: calc(100% + 8px);
left: 0;
right: 0;
min-width: 100%;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s ease;
background: #000;
border-radius: 10px;
}
.download-options.show {
max-height: 200px;
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.download-option {

View File

@@ -36,42 +36,24 @@
transform: translateZ(0);
backface-visibility: hidden;
will-change: transform;
contain: layout style paint;
}
.marquee-left .marquee-track {
animation: marquee-scroll-left 60s linear infinite;
transform: translateX(var(--marquee-x-1, 0));
}
.marquee-right .marquee-track {
animation: marquee-scroll-right 60s linear infinite;
}
@keyframes marquee-scroll-left {
from {
transform: translate3d(0, 0, 0);
}
to {
transform: translate3d(-50%, 0, 0);
}
}
@keyframes marquee-scroll-right {
from {
transform: translate3d(-50%, 0, 0);
}
to {
transform: translate3d(0, 0, 0);
}
transform: translateX(var(--marquee-x-2, 0));
}
@media (prefers-reduced-motion: reduce) {
.marquee-track,
.executors-marquee-track,
.showcase-track {
animation: none !important;
.marquee-track {
transform: none !important;
}
}
/* Performance optimization: GPU acceleration */
.marquee-row {
will-change: transform;
}

View File

@@ -4,6 +4,8 @@
align-items: center;
gap: 0.85rem;
padding-bottom: 3rem;
position: relative;
z-index: 5;
}
.partners-label {
@@ -46,4 +48,18 @@
.partner-item a {
display: flex;
align-items: center;
}
@media (max-width: 768px) {
.partners-inline {
padding-bottom: 2rem;
}
.partners-grid {
gap: 1rem;
}
.partner-item img {
height: 24px;
}
}

View File

@@ -101,19 +101,27 @@
.platform-pill {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: rgba(255, 255, 255, 0.05);
border-radius: var(--radius-pill);
font-size: 0.8125rem;
padding: 0.45rem 1.1rem;
border-radius: 50px;
font-size: 0.72rem;
font-weight: 600;
color: var(--color-fg-dim);
margin-bottom: 1.25rem;
}
.platform-pill svg {
width: 13px;
height: 13px;
color: var(--color-fg);
fill: currentColor;
flex-shrink: 0;
}
.platform-pill span {
white-space: nowrap;
}
.platform-divider {
color: var(--color-fg-dim);
opacity: 0.5;

View File

@@ -3,7 +3,7 @@
padding: 32px 24px 24px;
overflow: visible;
background: transparent;
z-index: 2;
z-index: 1;
}
.footer-content {

View File

@@ -1 +1 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap');
/* Font loading is handled in HTML with preconnect for better performance */

View File

@@ -70,6 +70,7 @@
.cta-banner-content .download-options {
background: #fff;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
}
.cta-banner-content .download-option {

View File

@@ -32,4 +32,12 @@
inset: 0;
width: 100%;
height: 100%;
}
.hero-atmosphere-video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}

View File

@@ -67,6 +67,7 @@
margin: 1rem auto 0;
line-height: 1.5;
width: 50%;
z-index: 10;
}
.hero-description a {
@@ -116,35 +117,6 @@
position: relative;
}
.platform-pill {
display: inline-flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.45rem 1.1rem;
border-radius: 50px;
font-size: 0.75rem;
font-weight: 500;
color: #ffffff;
margin-bottom: 1.25rem;
white-space: nowrap;
width: auto;
max-width: max-content;
}
.platform-pill svg {
width: 13px;
height: 13px;
color: #ffffff;
fill: currentColor;
flex-shrink: 0;
}
.platform-pill span {
white-space: nowrap;
}
.hero-screen-mockup {
position: relative;
width: 100%;