updated site and images

This commit is contained in:
2026-08-22 20:18:50 -07:00
parent 1b4db70930
commit 2291e2d13f
9 changed files with 134 additions and 118 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -265,8 +265,8 @@
<section class="hero" aria-labelledby="hero-title">
<div class="hero-atmosphere" aria-hidden="true">
<video class="hero-atmosphere-video" autoplay loop muted playsinline webkit-playsinline preload="auto"
aria-hidden="true">
<source src="https://videos.pexels.com/video-files/35997189/15263283_3840_2160_25fps.mp4"
crossorigin="anonymous" aria-hidden="true">
<source src="https://videos.pexels.com/video-files/26754965/12000494_3840_2160_30fps.mp4"
type="video/mp4">
</video>
<div class="hero-atmosphere-sheen"></div>
@@ -370,10 +370,6 @@
</svg>
<span>Available for Windows 10 / 11 (64-bit)</span>
</div>
<div class="hero-screen-mockup">
<img src="assets/images/desktop-preview.png" alt="Vyla Home Desktop Media Player" width="1020"
height="574" fetchpriority="high" decoding="async">
</div>
</div>
<div class="partners-inline reveal" id="partners-grid-wrap">
@@ -552,8 +548,9 @@
<section class="cta-banner" aria-labelledby="cta-heading">
<div class="cta-banner-media" aria-hidden="true">
<video class="cta-banner-video hero-atmosphere-video"
src="https://videos.pexels.com/video-files/35997189/15263283_3840_2160_25fps.mp4" autoplay loop muted
playsinline webkit-playsinline preload="auto" aria-hidden="true"></video>
src="https://videos.pexels.com/video-files/26754965/12000494_3840_2160_30fps.mp4" autoplay loop muted
playsinline webkit-playsinline preload="auto" crossorigin="anonymous" aria-hidden="true">
</video>
<canvas id="cta-byte-overlay" aria-hidden="true"></canvas>
<div class="cta-banner-overlay"></div>
</div>

View File

@@ -13,14 +13,12 @@ const DIFFUSION_ITERATIONS = 2;
const PROJECT_ITERATIONS = 3;
const VELOCITY_DAMP = 0.1;
const DYE_DAMP = 0.1;
const TRAIL_LIFETIME_MS = 184;
const TRAIL_LIFETIME_MS = 2000;
const TRAIL_RADIUS_PX = 100;
const TRAIL_EASE = 2.5;
const TRAIL_STRENGTH = 3;
const TRAIL_FORCE_DECAY = 1.2;
const TRAIL_WOBBLE = 0.14;
const DRAG_THRESHOLD_PX = 12;
const MAX_TRAIL_POINTS = 24;
export function initFluidByteField(canvasId) {
const canvas = document.getElementById(canvasId);
@@ -57,11 +55,6 @@ export function initFluidByteField(canvasId) {
let accumulator = 0;
let running = true;
const trails = [];
let dragStartX = 0;
let dragStartY = 0;
let dragging = false;
function resize() {
const rect = container.getBoundingClientRect();
const dpr = Math.min(window.devicePixelRatio || 1, 2);
@@ -111,8 +104,16 @@ export function initFluidByteField(canvasId) {
function onPointerMove(e) {
const rect = container.getBoundingClientRect();
const px = e.clientX - rect.left;
const py = e.clientY - rect.top;
let px, py;
if (e.touches && e.touches.length > 0) {
px = e.touches[0].clientX - rect.left;
py = e.touches[0].clientY - rect.top;
} else {
px = e.clientX - rect.left;
py = e.clientY - rect.top;
}
if (cols > 0 && lastPointerX > -9000 && px >= 0 && py >= 0 && px <= rect.width && py <= rect.height) {
const elapsed = Math.min(0.05, Math.max(0.001, (e.timeStamp - lastPointerTime) / 1000));
const fx = (px - lastPointerX) / CELL_SIZE / elapsed * ADVECT_SCALE;
@@ -129,78 +130,7 @@ export function initFluidByteField(canvasId) {
lastPointerY = -9999;
}
function onPointerDown(e) {
const rect = container.getBoundingClientRect();
const px = e.clientX - rect.left;
const py = e.clientY - rect.top;
dragStartX = px;
dragStartY = py;
dragging = px >= 0 && py >= 0 && px <= rect.width && py <= rect.height;
}
function onPointerUp(e) {
if (!dragging) return;
dragging = false;
const rect = container.getBoundingClientRect();
const px = e.clientX - rect.left;
const py = e.clientY - rect.top;
if (Math.hypot(px - dragStartX, py - dragStartY) > DRAG_THRESHOLD_PX) return;
trails.push({ x: px, y: py, age: 0, seed: Math.random() * Math.PI * 2 });
if (trails.length > MAX_TRAIL_POINTS) trails.shift();
}
function applyTrails() {
if (!trails.length) return;
const lifetimeSec = TRAIL_LIFETIME_MS / 1000;
const cellsPerRadius = TRAIL_RADIUS_PX / CELL_SIZE;
for (let i = trails.length - 1; i >= 0; i--) {
const trail = trails[i];
trail.age += DT;
const progress = trail.age / lifetimeSec;
if (progress >= 1) {
trails.splice(i, 1);
continue;
}
const eased = 1 - Math.pow(1 - progress, TRAIL_EASE);
const ringRadius = eased * TRAIL_RADIUS_PX / CELL_SIZE;
const remaining = 1 - progress;
const intensity = 0.35 + 0.65 * eased;
const force = TRAIL_STRENGTH * Math.pow(remaining, TRAIL_FORCE_DECAY);
const dyeAmount = 0.2 * Math.pow(remaining, 2);
const cx = trail.x / CELL_SIZE;
const cy = trail.y / CELL_SIZE;
const seed = trail.seed;
const band = ringRadius * 1.3 + cellsPerRadius;
const xMin = Math.max(1, Math.floor(cx - band));
const xMax = Math.min(cols - 2, Math.ceil(cx + band));
const yMin = Math.max(1, Math.floor(cy - band));
const yMax = Math.min(rows - 2, Math.ceil(cy + band));
for (let y = yMin; y <= yMax; y++) {
for (let x = xMin; x <= xMax; x++) {
const dx = x - cx;
const dy = y - cy;
const dist = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const wobble = 0.5 * Math.sin(6 * angle + seed) + 0.25 * Math.sin(3 * angle + 0.05 * dist + 0.3 * seed) + 0.5;
const wobbleAmount = TRAIL_WOBBLE * (2 * wobble - 1) * intensity;
const targetRadius = ringRadius * Math.min(1.6, Math.max(0.6, 1 + wobbleAmount));
const ringDist = Math.abs(dist - targetRadius) / cellsPerRadius;
if (ringDist >= 1) continue;
const falloff = smoothstep(1 - ringDist);
const forceMul = Math.min(2.2, Math.max(0.4, 1 + wobbleAmount));
const invDist = dist > 0.001 ? 1 / dist : 0;
const idx = x + y * cols;
velX[idx] += dx * invDist * force * forceMul * falloff;
velY[idx] += dy * invDist * force * forceMul * falloff;
dye[idx] = Math.min(1.5, dye[idx] + dyeAmount * forceMul * falloff);
if (Math.random() < 0.03 * falloff) glyphs[idx] = Math.floor(Math.random() * 256);
}
}
}
}
function step() {
applyTrails();
const visc = 1.5 * DT;
velX0.set(velX);
diffuse(velX, velX0, visc, cols, rows, DT, DIFFUSION_ITERATIONS);
@@ -255,18 +185,18 @@ export function initFluidByteField(canvasId) {
videoLuminance = (r * 0.299 + g * 0.587 + b * 0.114) / 255;
}
const baseAlpha = 0.04 + videoLuminance * 0.22;
const baseAlpha = 0;
if (d <= 0.02) {
ctx.shadowBlur = 0;
ctx.globalAlpha = baseAlpha;
ctx.fillStyle = 'rgba(0, 0, 0, 0.45)';
ctx.fillStyle = 'rgba(255, 255, 255)';
ctx.fillText(hex, x * CELL_SIZE + half, y * CELL_SIZE + half);
} else {
const alpha = d * FADE_RATE;
const activeAlpha = Math.min(alpha + baseAlpha, MAX_ALPHA);
const alpha = d * 1.5;
const activeAlpha = Math.min(alpha + baseAlpha, 1.0);
ctx.globalAlpha = activeAlpha;
ctx.fillStyle = '#ffffff';
ctx.shadowColor = '#ffffff';
ctx.shadowBlur = 0;
ctx.fillText(hex, x * CELL_SIZE + half, y * CELL_SIZE + half);
}
}
@@ -304,8 +234,8 @@ export function initFluidByteField(canvasId) {
new ResizeObserver(resize).observe(container);
window.addEventListener('pointermove', onPointerMove, { passive: true });
window.addEventListener('pointerleave', onPointerLeave);
window.addEventListener('pointerdown', onPointerDown, { passive: true });
window.addEventListener('pointerup', onPointerUp, { passive: true });
window.addEventListener('touchmove', onPointerMove, { passive: true });
window.addEventListener('touchend', onPointerLeave);
document.addEventListener('visibilitychange', onVisibilityChange);
resize();

View File

@@ -6,6 +6,101 @@ import { DISCORD_LOADER_SCRIPT, COPY_FEEDBACK_DURATION_MS } from '../config/cons
const HERO_GREETING = 'Vyla Home';
function initVideoColorExtraction() {
const videos = document.querySelectorAll('.hero-atmosphere-video');
if (videos.length === 0) return;
console.log('Video color extraction initialized, found', videos.length, 'videos');
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
let animationFrameId;
let frameCount = 0;
let currentVideoColor = 'rgb(0, 0, 0)';
function extractColors() {
const video = videos[0];
if (video.readyState < 2) {
console.log('Video not ready, readyState:', video.readyState);
animationFrameId = requestAnimationFrame(extractColors);
return;
}
try {
canvas.width = 64;
canvas.height = 64;
ctx.drawImage(video, 0, 0, 64, 64);
const imageData = ctx.getImageData(0, 0, 64, 64);
const data = imageData.data;
let r = 0, g = 0, b = 0;
let count = 0;
for (let i = 0; i < data.length; i += 4) {
r += data[i];
g += data[i + 1];
b += data[i + 2];
count++;
}
r = Math.round(r / count);
g = Math.round(g / count);
b = Math.round(b / count);
currentVideoColor = `rgb(${r}, ${g}, ${b})`;
const dominantColorTransparent = `rgba(${r}, ${g}, ${b}, 0.5)`;
document.documentElement.style.setProperty('--video-dominant-color', currentVideoColor);
document.documentElement.style.setProperty('--video-dominant-color-transparent', dominantColorTransparent);
if (window.scrollY === 0) {
document.documentElement.style.setProperty('--color-bg', currentVideoColor);
}
frameCount++;
if (frameCount % 30 === 0) {
console.log('Frame', frameCount, '- Extracted color:', currentVideoColor);
console.log('CSS variable set:', getComputedStyle(document.documentElement).getPropertyValue('--video-dominant-color'));
}
} catch (e) {
console.error('Error extracting video colors:', e);
}
animationFrameId = requestAnimationFrame(extractColors);
}
function handleScroll() {
const scrollY = window.scrollY;
const heroHeight = window.innerHeight;
const scrollProgress = Math.min(scrollY / (heroHeight * 0.5), 1);
const videoColorRGB = currentVideoColor.match(/\d+/g);
if (videoColorRGB) {
const r = Math.round(parseInt(videoColorRGB[0]) * (1 - scrollProgress));
const g = Math.round(parseInt(videoColorRGB[1]) * (1 - scrollProgress));
const b = Math.round(parseInt(videoColorRGB[2]) * (1 - scrollProgress));
const fadedColor = `rgb(${r}, ${g}, ${b})`;
document.documentElement.style.setProperty('--color-bg', fadedColor);
}
}
animationFrameId = requestAnimationFrame(extractColors);
window.addEventListener('scroll', handleScroll, { passive: true });
handleScroll();
videos.forEach(video => {
video.addEventListener('pause', () => {
console.log('Video paused, stopping color extraction');
cancelAnimationFrame(animationFrameId);
});
video.addEventListener('play', () => {
console.log('Video playing, starting color extraction');
animationFrameId = requestAnimationFrame(extractColors);
});
});
}
function initHeroTitle() {
const heroTitle = qs('.hero-title');
if (heroTitle) typewriterReveal(heroTitle, HERO_GREETING);
@@ -48,8 +143,7 @@ export function initHeroSection() {
initDiscordCopyBar();
initHeroCopyButton();
initCtaCopyButton();
if (!isMobileViewport()) {
initFluidByteField('hero-byte-overlay');
initFluidByteField('cta-byte-overlay');
}
initVideoColorExtraction();
initFluidByteField('hero-byte-overlay');
initFluidByteField('cta-byte-overlay');
}

View File

@@ -15,6 +15,7 @@
z-index: -1;
overflow: hidden;
pointer-events: none;
background: var(--video-dominant-color, black);
-webkit-mask-image:
linear-gradient(to bottom, transparent, black 50%),
linear-gradient(to top, transparent, black 70%);

View File

@@ -15,14 +15,14 @@
overflow: hidden;
pointer-events: none;
-webkit-mask-image: linear-gradient(to bottom,
black 0%,
black 48%,
rgba(0, 0, 0, 0.5) 80%,
var(--video-dominant-color, black) 0%,
var(--video-dominant-color, black) 48%,
var(--video-dominant-color-transparent, rgba(0, 0, 0, 0.5)) 80%,
transparent 96%);
mask-image: linear-gradient(to bottom,
black 0%,
black 48%,
rgba(0, 0, 0, 0.5) 80%,
var(--video-dominant-color, black) 0%,
var(--video-dominant-color, black) 48%,
var(--video-dominant-color-transparent, rgba(0, 0, 0, 0.5)) 80%,
transparent 96%);
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: 100% 100%;
@@ -50,13 +50,6 @@
animation: hero-sheen 16s ease-in-out infinite alternate;
}
.hero-atmosphere-vignette {
position: absolute;
inset: 0;
background: linear-gradient(to bottom, transparent 15%, rgba(0, 0, 0, 0.15) 55%, rgba(0, 0, 0, 0.95) 100%),
radial-gradient(ellipse at center, transparent 40%, rgba(0, 0, 0, 0.2) 100%);
}
#hero-byte-overlay {
position: absolute;
inset: 0;
@@ -103,7 +96,6 @@
@media (orientation: landscape) and (max-height: 600px) {
.hero {
height: auto;
min-height: 100svh;
padding-top: 5rem;
height: 100vh;
}
}

View File

@@ -95,8 +95,8 @@
align-items: center;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(8px) saturate(140%);
-webkit-backdrop-filter: blur(8px) saturate(140%);
backdrop-filter: blur(15px) saturate(140%);
-webkit-backdrop-filter: blur(15px) saturate(140%);
border: none;
padding: 0.35rem 0.85rem;
border-radius: var(--radius-pill);
@@ -104,6 +104,8 @@
font-weight: 500;
color: var(--color-fg-dim);
margin-bottom: 1.25rem;
position: relative;
z-index: 10;
}
.platform-pill svg {

View File

@@ -16,7 +16,7 @@
@media (orientation: landscape) and (min-width: 600px) and (max-height: 500px) {
.container {
width: 95%;
width: 70%;
padding-inline: 0;
}