forked from vyla-entertainment/home-frontend
updated site ( crazy )
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
<meta name="msapplication-tooltip" content="Self-hosted desktop media center for streaming entertainment">
|
||||
<meta name="msapplication-starturl" content="/">
|
||||
<meta name="msapplication-window" content="width=device-width;height=device-height">
|
||||
<meta name="msapplication-navbutton-color" content="#1a1a2e">
|
||||
<meta name="msapplication-navbutton-color" content="#FFF">
|
||||
|
||||
<link rel="icon" href="/assets/images/favicon.ico" type="image/x-icon">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.ico">
|
||||
@@ -73,13 +73,13 @@
|
||||
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<meta name="theme-color" content="#1a1a2e">
|
||||
<meta name="theme-color" content="#FFF">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Vyla Home">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="Vyla Home">
|
||||
<meta name="msapplication-TileColor" content="#1a1a2e">
|
||||
<meta name="msapplication-TileColor" content="#FFF">
|
||||
<meta name="msapplication-config" content="/browserconfig.xml">
|
||||
|
||||
<link rel="apple-touch-icon" href="favicon.ico">
|
||||
@@ -302,90 +302,104 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<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/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>
|
||||
<div class="container cta-banner-content reveal">
|
||||
<h2 id="cta-heading" class="cta-banner-title">Try Vyla Home today</h2>
|
||||
<p class="cta-banner-subtitle">Your Entertainment. Zero Servers Attached.</p>
|
||||
<a class="hero-cta hero-cta-light" id="cta-copy-script-btn" href="#" target="_blank"
|
||||
rel="noopener noreferrer">Download Now</a>
|
||||
</div>
|
||||
<div class="cta-footer-wrap">
|
||||
<div id="cta-rays" class="cta-rays-container" aria-hidden="true"></div>
|
||||
|
||||
<script>
|
||||
async function setupLatestDownload() {
|
||||
const btn = document.getElementById("hero-copy-script-btn");
|
||||
<section class="cta-banner" aria-labelledby="cta-heading">
|
||||
<div class="container cta-banner-content reveal">
|
||||
<h2 id="cta-heading" class="cta-banner-title">Try Vyla Home today</h2>
|
||||
<p class="cta-banner-subtitle">Your Entertainment. Zero Servers Attached.</p>
|
||||
<a class="hero-cta hero-cta-light" id="cta-copy-script-btn" href="#" target="_blank"
|
||||
rel="noopener noreferrer">Download Now</a>
|
||||
</div>
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/releases/permalink/latest"
|
||||
);
|
||||
<script>
|
||||
async function setupLatestDownload() {
|
||||
const buttons = [
|
||||
document.getElementById("hero-copy-script-btn"),
|
||||
document.getElementById("cta-copy-script-btn")
|
||||
];
|
||||
|
||||
if (!response.ok) throw new Error("Failed to fetch latest release");
|
||||
try {
|
||||
const project = "vyla-entertainment%2Fhome";
|
||||
|
||||
const release = await response.json();
|
||||
const version = release.tag_name;
|
||||
const fileVersion = version.replace(/^v/, "");
|
||||
const response = await fetch(
|
||||
`https://gitlab.com/api/v4/projects/${project}/releases/permalink/latest`
|
||||
);
|
||||
|
||||
btn.href =
|
||||
`https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/packages/generic/vyla-home/${version}/Vyla-Home_${fileVersion}_x64-setup.exe`;
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch latest release: ${response.status}`);
|
||||
}
|
||||
|
||||
btn.textContent = `Download Release ${version}`;
|
||||
} catch (error) {
|
||||
btn.textContent = "Download Latest Release";
|
||||
const release = await response.json();
|
||||
|
||||
const asset = release.assets?.links?.find(link =>
|
||||
link.name?.toLowerCase().endsWith(".exe")
|
||||
);
|
||||
|
||||
if (!asset?.url) {
|
||||
throw new Error("No .exe asset found in latest release");
|
||||
}
|
||||
|
||||
buttons.forEach(button => {
|
||||
if (button) {
|
||||
button.href = asset.url;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
buttons.forEach(button => {
|
||||
if (button) {
|
||||
button.textContent = "Download Latest Release";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupLatestDownload();
|
||||
</script>
|
||||
</section>
|
||||
setupLatestDownload();
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>Vyla</h4>
|
||||
<p>Vyla is a passion project born from a love for film and television.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Backup Site:</h4>
|
||||
<p><a href="https://vyla.qzz.io" target="_blank" rel="noopener">https://vyla.qzz.io</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Quick Links</h4>
|
||||
<a href="/terms-of-service">Terms of Service</a>
|
||||
<a href="/privacy-policy">Privacy Policy</a>
|
||||
<a href="/about-us">About Us</a>
|
||||
<a href="https://docs.vyla.cc/misc/dmca" target="_blank" rel="noopener">DMCA</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Our API</h4>
|
||||
<a href="https://docs.vyla.cc" target="_blank" rel="noopener">Documentation</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Join our Discord</h4>
|
||||
<div class="social-links">
|
||||
<a href="/discord" target="_blank" rel="noopener">
|
||||
<i class="fab fa-discord"></i> Discord
|
||||
</a>
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h2>Vyla</h2>
|
||||
<p>Vyla is a passion project born from a love for film and television.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Backup Site:</h2>
|
||||
<p><a href="https://vyla.qzz.io" target="_blank" rel="noopener">https://vyla.qzz.io</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Quick Links</h2>
|
||||
<a href="/terms-of-service">Terms of Service</a>
|
||||
<a href="/privacy-policy">Privacy Policy</a>
|
||||
<a href="/about-us">About Us</a>
|
||||
<a href="https://docs.vyla.cc/misc/dmca" target="_blank" rel="noopener">DMCA</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Our API</h2>
|
||||
<a href="https://docs.vyla.cc" target="_blank" rel="noopener">Documentation</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Join our Discord</h2>
|
||||
<div class="social-links">
|
||||
<a href="/discord" target="_blank" rel="noopener">
|
||||
<i class="fab fa-discord"></i> Discord
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<img src="https://app.greenweb.org/api/v3/greencheckimage/vyla.cc?nocache=true"
|
||||
alt="This website runs on green hosting - verified by thegreenwebfoundation.org" width="200"
|
||||
height="95" loading="lazy" decoding="async">
|
||||
</div>
|
||||
<img src="https://app.greenweb.org/api/v3/greencheckimage/vyla.cc?nocache=true"
|
||||
alt="This website runs on green hosting - verified by thegreenwebfoundation.org" width="200px"
|
||||
height="95px">
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<a href="/fuckinggodihatethisprojectijustwantoenditall" class="secret-footer">
|
||||
© 2025 Vyla Entertainment. All rights reserved.
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
<div class="footer-bottom">
|
||||
<a href="/fuckinggodihatethisprojectijustwantoenditall" class="secret-footer">
|
||||
© 2025 Vyla Entertainment. All rights reserved.
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/src/scripts/main.js"></script>
|
||||
</body>
|
||||
|
||||
BIN
assets/images/thumbnail.png
Normal file
BIN
assets/images/thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 MiB |
196
index.html
196
index.html
@@ -29,7 +29,7 @@
|
||||
<meta name="msapplication-tooltip" content="Self-hosted desktop media center for streaming entertainment">
|
||||
<meta name="msapplication-starturl" content="/">
|
||||
<meta name="msapplication-window" content="width=device-width;height=device-height">
|
||||
<meta name="msapplication-navbutton-color" content="#1a1a2e">
|
||||
<meta name="msapplication-navbutton-color" content="#FFF">
|
||||
|
||||
<link rel="icon" href="assets/images/favicon.ico" type="image/x-icon">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon.ico">
|
||||
@@ -50,7 +50,7 @@
|
||||
<meta property="og:title" content="Vyla Home - Self-Hosted Desktop Media Center | Serverless Streaming">
|
||||
<meta property="og:description"
|
||||
content="Experience revolutionary self-hosted streaming with Vyla Home. Bypass datacenter blocking, ISP throttling, and cloud downtime. 4K movies, TV shows, anime with complete privacy and zero server dependencies.">
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png">
|
||||
<meta property="og:image" content="/assets/images/thumbnail.png">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
<meta property="og:image:alt" content="Vyla Home Desktop Media Center Interface">
|
||||
@@ -66,20 +66,20 @@
|
||||
<meta name="twitter:title" content="Vyla Home - Self-Hosted Desktop Media Center">
|
||||
<meta name="twitter:description"
|
||||
content="Experience revolutionary self-hosted streaming. Bypass datacenter blocking, ISP throttling, and cloud downtime. 4K movies, TV shows, anime with complete privacy.">
|
||||
<meta name="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png">
|
||||
<meta name="twitter:image" content="/assets/images/thumbnail.png">
|
||||
<meta name="twitter:image:alt" content="Vyla Home Desktop Media Center Interface">
|
||||
|
||||
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
||||
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<meta name="theme-color" content="#1a1a2e">
|
||||
<meta name="theme-color" content="#FFF">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Vyla Home">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="Vyla Home">
|
||||
<meta name="msapplication-TileColor" content="#1a1a2e">
|
||||
<meta name="msapplication-TileColor" content="#FFF">
|
||||
<meta name="msapplication-config" content="/browserconfig.xml">
|
||||
|
||||
<link rel="apple-touch-icon" href="favicon.ico">
|
||||
@@ -91,6 +91,7 @@
|
||||
<link rel="stylesheet" href="src/styles/base/typography.css">
|
||||
<link rel="stylesheet" href="src/styles/utilities/layout.css">
|
||||
<link rel="stylesheet" href="src/styles/utilities/reveal.css">
|
||||
<link rel="stylesheet" href="src/styles/components/glass-surface.css">
|
||||
<link rel="stylesheet" href="src/styles/layout/nav.css">
|
||||
<link rel="stylesheet" href="src/styles/sections/hero-atmosphere.css">
|
||||
<link rel="stylesheet" href="src/styles/sections/hero-content.css">
|
||||
@@ -266,14 +267,7 @@
|
||||
<main role="main">
|
||||
<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"
|
||||
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>
|
||||
<canvas id="hero-byte-overlay" aria-hidden="true"></canvas>
|
||||
<div class="hero-atmosphere-vignette"></div>
|
||||
<div id="hero-prism" class="hero-prism"></div>
|
||||
</div>
|
||||
<div class="container hero-container">
|
||||
|
||||
@@ -531,108 +525,104 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<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/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>
|
||||
<div class="container cta-banner-content reveal">
|
||||
<h2 id="cta-heading" class="cta-banner-title">Try Vyla Home today</h2>
|
||||
<p class="cta-banner-subtitle">Your Entertainment. Zero Servers Attached.</p>
|
||||
<a class="hero-cta hero-cta-light" id="cta-copy-script-btn" href="#" target="_blank"
|
||||
rel="noopener noreferrer">Download Now</a>
|
||||
</div>
|
||||
<div class="cta-footer-wrap">
|
||||
<div id="cta-rays" class="cta-rays-container" aria-hidden="true"></div>
|
||||
|
||||
<script>
|
||||
async function setupLatestDownload() {
|
||||
const buttons = [
|
||||
document.getElementById("hero-copy-script-btn"),
|
||||
document.getElementById("cta-copy-script-btn")
|
||||
];
|
||||
<section class="cta-banner" aria-labelledby="cta-heading">
|
||||
<div class="container cta-banner-content reveal">
|
||||
<h2 id="cta-heading" class="cta-banner-title">Try Vyla Home today</h2>
|
||||
<p class="cta-banner-subtitle">Your Entertainment. Zero Servers Attached.</p>
|
||||
<a class="hero-cta hero-cta-light" id="cta-copy-script-btn" href="#" target="_blank"
|
||||
rel="noopener noreferrer">Download Now</a>
|
||||
</div>
|
||||
|
||||
try {
|
||||
const project = "vyla-entertainment%2Fhome";
|
||||
<script>
|
||||
async function setupLatestDownload() {
|
||||
const buttons = [
|
||||
document.getElementById("hero-copy-script-btn"),
|
||||
document.getElementById("cta-copy-script-btn")
|
||||
];
|
||||
|
||||
const response = await fetch(
|
||||
`https://gitlab.com/api/v4/projects/${project}/releases/permalink/latest`
|
||||
);
|
||||
try {
|
||||
const project = "vyla-entertainment%2Fhome";
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch latest release: ${response.status}`);
|
||||
}
|
||||
const response = await fetch(
|
||||
`https://gitlab.com/api/v4/projects/${project}/releases/permalink/latest`
|
||||
);
|
||||
|
||||
const release = await response.json();
|
||||
|
||||
const asset = release.assets?.links?.find(link =>
|
||||
link.name?.toLowerCase().endsWith(".exe")
|
||||
);
|
||||
|
||||
if (!asset?.url) {
|
||||
throw new Error("No .exe asset found in latest release");
|
||||
}
|
||||
|
||||
buttons.forEach(button => {
|
||||
if (button) {
|
||||
button.href = asset.url;
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch latest release: ${response.status}`);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
buttons.forEach(button => {
|
||||
if (button) {
|
||||
button.textContent = "Download Latest Release";
|
||||
|
||||
const release = await response.json();
|
||||
|
||||
const asset = release.assets?.links?.find(link =>
|
||||
link.name?.toLowerCase().endsWith(".exe")
|
||||
);
|
||||
|
||||
if (!asset?.url) {
|
||||
throw new Error("No .exe asset found in latest release");
|
||||
}
|
||||
});
|
||||
|
||||
buttons.forEach(button => {
|
||||
if (button) {
|
||||
button.href = asset.url;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
buttons.forEach(button => {
|
||||
if (button) {
|
||||
button.textContent = "Download Latest Release";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupLatestDownload();
|
||||
</script>
|
||||
</section>
|
||||
setupLatestDownload();
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h2>Vyla</h2>
|
||||
<p>Vyla is a passion project born from a love for film and television.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Backup Site:</h2>
|
||||
<p><a href="https://vyla.qzz.io" target="_blank" rel="noopener">https://vyla.qzz.io</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Quick Links</h2>
|
||||
<a href="/terms-of-service">Terms of Service</a>
|
||||
<a href="/privacy-policy">Privacy Policy</a>
|
||||
<a href="/about-us">About Us</a>
|
||||
<a href="https://docs.vyla.cc/misc/dmca" target="_blank" rel="noopener">DMCA</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Our API</h2>
|
||||
<a href="https://docs.vyla.cc" target="_blank" rel="noopener">Documentation</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Join our Discord</h2>
|
||||
<div class="social-links">
|
||||
<a href="/discord" target="_blank" rel="noopener">
|
||||
<i class="fab fa-discord"></i> Discord
|
||||
</a>
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h2>Vyla</h2>
|
||||
<p>Vyla is a passion project born from a love for film and television.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Backup Site:</h2>
|
||||
<p><a href="https://vyla.qzz.io" target="_blank" rel="noopener">https://vyla.qzz.io</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Quick Links</h2>
|
||||
<a href="/terms-of-service">Terms of Service</a>
|
||||
<a href="/privacy-policy">Privacy Policy</a>
|
||||
<a href="/about-us">About Us</a>
|
||||
<a href="https://docs.vyla.cc/misc/dmca" target="_blank" rel="noopener">DMCA</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Our API</h2>
|
||||
<a href="https://docs.vyla.cc" target="_blank" rel="noopener">Documentation</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Join our Discord</h2>
|
||||
<div class="social-links">
|
||||
<a href="/discord" target="_blank" rel="noopener">
|
||||
<i class="fab fa-discord"></i> Discord
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<img src="https://app.greenweb.org/api/v3/greencheckimage/vyla.cc?nocache=true"
|
||||
alt="This website runs on green hosting - verified by thegreenwebfoundation.org" width="200"
|
||||
height="95" loading="lazy" decoding="async">
|
||||
</div>
|
||||
<img src="https://app.greenweb.org/api/v3/greencheckimage/vyla.cc?nocache=true"
|
||||
alt="This website runs on green hosting - verified by thegreenwebfoundation.org" width="200" height="95"
|
||||
loading="lazy" decoding="async">
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<a href="/fuckinggodihatethisprojectijustwantoenditall" class="secret-footer">
|
||||
© 2025 Vyla Entertainment. All rights reserved.
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
<div class="footer-bottom">
|
||||
<a href="/fuckinggodihatethisprojectijustwantoenditall" class="secret-footer">
|
||||
© 2025 Vyla Entertainment. All rights reserved.
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script type="module" src="src/scripts/main.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<meta name="msapplication-tooltip" content="Self-hosted desktop media center for streaming entertainment">
|
||||
<meta name="msapplication-starturl" content="/">
|
||||
<meta name="msapplication-window" content="width=device-width;height=device-height">
|
||||
<meta name="msapplication-navbutton-color" content="#1a1a2e">
|
||||
<meta name="msapplication-navbutton-color" content="#FFF">
|
||||
|
||||
<link rel="icon" href="/assets/images/favicon.ico" type="image/x-icon">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.ico">
|
||||
@@ -73,13 +73,13 @@
|
||||
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<meta name="theme-color" content="#1a1a2e">
|
||||
<meta name="theme-color" content="#FFF">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Vyla Home">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="Vyla Home">
|
||||
<meta name="msapplication-TileColor" content="#1a1a2e">
|
||||
<meta name="msapplication-TileColor" content="#FFF">
|
||||
<meta name="msapplication-config" content="/browserconfig.xml">
|
||||
|
||||
<link rel="apple-touch-icon" href="favicon.ico">
|
||||
@@ -313,90 +313,104 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<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/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>
|
||||
<div class="container cta-banner-content reveal">
|
||||
<h2 id="cta-heading" class="cta-banner-title">Try Vyla Home today</h2>
|
||||
<p class="cta-banner-subtitle">Your Entertainment. Zero Servers Attached.</p>
|
||||
<a class="hero-cta hero-cta-light" id="cta-copy-script-btn" href="#" target="_blank"
|
||||
rel="noopener noreferrer">Download Now</a>
|
||||
</div>
|
||||
<div class="cta-footer-wrap">
|
||||
<div id="cta-rays" class="cta-rays-container" aria-hidden="true"></div>
|
||||
|
||||
<script>
|
||||
async function setupLatestDownload() {
|
||||
const btn = document.getElementById("hero-copy-script-btn");
|
||||
<section class="cta-banner" aria-labelledby="cta-heading">
|
||||
<div class="container cta-banner-content reveal">
|
||||
<h2 id="cta-heading" class="cta-banner-title">Try Vyla Home today</h2>
|
||||
<p class="cta-banner-subtitle">Your Entertainment. Zero Servers Attached.</p>
|
||||
<a class="hero-cta hero-cta-light" id="cta-copy-script-btn" href="#" target="_blank"
|
||||
rel="noopener noreferrer">Download Now</a>
|
||||
</div>
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/releases/permalink/latest"
|
||||
);
|
||||
<script>
|
||||
async function setupLatestDownload() {
|
||||
const buttons = [
|
||||
document.getElementById("hero-copy-script-btn"),
|
||||
document.getElementById("cta-copy-script-btn")
|
||||
];
|
||||
|
||||
if (!response.ok) throw new Error("Failed to fetch latest release");
|
||||
try {
|
||||
const project = "vyla-entertainment%2Fhome";
|
||||
|
||||
const release = await response.json();
|
||||
const version = release.tag_name;
|
||||
const fileVersion = version.replace(/^v/, "");
|
||||
const response = await fetch(
|
||||
`https://gitlab.com/api/v4/projects/${project}/releases/permalink/latest`
|
||||
);
|
||||
|
||||
btn.href =
|
||||
`https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/packages/generic/vyla-home/${version}/Vyla-Home_${fileVersion}_x64-setup.exe`;
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch latest release: ${response.status}`);
|
||||
}
|
||||
|
||||
btn.textContent = `Download Release ${version}`;
|
||||
} catch (error) {
|
||||
btn.textContent = "Download Latest Release";
|
||||
const release = await response.json();
|
||||
|
||||
const asset = release.assets?.links?.find(link =>
|
||||
link.name?.toLowerCase().endsWith(".exe")
|
||||
);
|
||||
|
||||
if (!asset?.url) {
|
||||
throw new Error("No .exe asset found in latest release");
|
||||
}
|
||||
|
||||
buttons.forEach(button => {
|
||||
if (button) {
|
||||
button.href = asset.url;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
buttons.forEach(button => {
|
||||
if (button) {
|
||||
button.textContent = "Download Latest Release";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupLatestDownload();
|
||||
</script>
|
||||
</section>
|
||||
setupLatestDownload();
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>Vyla</h4>
|
||||
<p>Vyla is a passion project born from a love for film and television.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Backup Site:</h4>
|
||||
<p><a href="https://vyla.qzz.io" target="_blank" rel="noopener">https://vyla.qzz.io</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Quick Links</h4>
|
||||
<a href="/terms-of-service">Terms of Service</a>
|
||||
<a href="/privacy-policy">Privacy Policy</a>
|
||||
<a href="/about-us">About Us</a>
|
||||
<a href="https://docs.vyla.cc/misc/dmca" target="_blank" rel="noopener">DMCA</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Our API</h4>
|
||||
<a href="https://docs.vyla.cc" target="_blank" rel="noopener">Documentation</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Join our Discord</h4>
|
||||
<div class="social-links">
|
||||
<a href="/discord" target="_blank" rel="noopener">
|
||||
<i class="fab fa-discord"></i> Discord
|
||||
</a>
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h2>Vyla</h2>
|
||||
<p>Vyla is a passion project born from a love for film and television.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Backup Site:</h2>
|
||||
<p><a href="https://vyla.qzz.io" target="_blank" rel="noopener">https://vyla.qzz.io</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Quick Links</h2>
|
||||
<a href="/terms-of-service">Terms of Service</a>
|
||||
<a href="/privacy-policy">Privacy Policy</a>
|
||||
<a href="/about-us">About Us</a>
|
||||
<a href="https://docs.vyla.cc/misc/dmca" target="_blank" rel="noopener">DMCA</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Our API</h2>
|
||||
<a href="https://docs.vyla.cc" target="_blank" rel="noopener">Documentation</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Join our Discord</h2>
|
||||
<div class="social-links">
|
||||
<a href="/discord" target="_blank" rel="noopener">
|
||||
<i class="fab fa-discord"></i> Discord
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<img src="https://app.greenweb.org/api/v3/greencheckimage/vyla.cc?nocache=true"
|
||||
alt="This website runs on green hosting - verified by thegreenwebfoundation.org" width="200"
|
||||
height="95" loading="lazy" decoding="async">
|
||||
</div>
|
||||
<img src="https://app.greenweb.org/api/v3/greencheckimage/vyla.cc?nocache=true"
|
||||
alt="This website runs on green hosting - verified by thegreenwebfoundation.org" width="200px"
|
||||
height="95px">
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<a href="/fuckinggodihatethisprojectijustwantoenditall" class="secret-footer">
|
||||
© 2025 Vyla Entertainment. All rights reserved.
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
<div class="footer-bottom">
|
||||
<a href="/fuckinggodihatethisprojectijustwantoenditall" class="secret-footer">
|
||||
© 2025 Vyla Entertainment. All rights reserved.
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/src/scripts/main.js"></script>
|
||||
</body>
|
||||
|
||||
121
src/scripts/animations/glass-surface.js
Normal file
121
src/scripts/animations/glass-surface.js
Normal file
@@ -0,0 +1,121 @@
|
||||
let globalSvgDefs = null;
|
||||
|
||||
function ensureGlobalDefs() {
|
||||
if (!globalSvgDefs) {
|
||||
globalSvgDefs = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
globalSvgDefs.setAttribute('id', 'glass-surface-defs');
|
||||
globalSvgDefs.setAttribute('aria-hidden', 'true');
|
||||
globalSvgDefs.style.cssText = 'position:fixed;top:0;left:0;width:0;height:0;pointer-events:none;z-index:-9999;opacity:0;';
|
||||
document.body.appendChild(globalSvgDefs);
|
||||
}
|
||||
return globalSvgDefs;
|
||||
}
|
||||
|
||||
export function initGlassSurface(selector, options = {}) {
|
||||
const elements = typeof selector === 'string' ? document.querySelectorAll(selector) : [selector];
|
||||
const defsContainer = ensureGlobalDefs();
|
||||
|
||||
elements.forEach((el, index) => {
|
||||
if (!el || el.dataset.glassInitialized) return;
|
||||
el.dataset.glassInitialized = 'true';
|
||||
|
||||
const id = `glass-surf-${index}-${Math.random().toString(36).slice(2, 7)}`;
|
||||
const filterId = `filter-${id}`;
|
||||
const redGradId = `red-grad-${id}`;
|
||||
const blueGradId = `blue-grad-${id}`;
|
||||
|
||||
const config = {
|
||||
borderRadius: options.borderRadius !== undefined ? Number(options.borderRadius) : 50,
|
||||
borderWidth: options.borderWidth !== undefined ? Number(options.borderWidth) : 0,
|
||||
brightness: options.brightness !== undefined ? Number(options.brightness) : 100,
|
||||
opacity: options.opacity !== undefined ? Number(options.opacity) : 0.93,
|
||||
blur: options.blur !== undefined ? Number(options.blur) : 15,
|
||||
displace: options.displace !== undefined ? Number(options.displace) : 0.5,
|
||||
backgroundOpacity: options.backgroundOpacity !== undefined ? Number(options.backgroundOpacity) : 0,
|
||||
saturation: options.saturation !== undefined ? Number(options.saturation) : 0,
|
||||
distortionScale: options.distortionScale !== undefined ? Number(options.distortionScale) : -180,
|
||||
redOffset: options.redOffset !== undefined ? Number(options.redOffset) : -50,
|
||||
greenOffset: options.greenOffset !== undefined ? Number(options.greenOffset) : -50,
|
||||
blueOffset: options.blueOffset !== undefined ? Number(options.blueOffset) : 20,
|
||||
xChannel: options.xChannel || 'R',
|
||||
yChannel: options.yChannel || 'G',
|
||||
mixBlendMode: options.mixBlendMode || 'difference'
|
||||
};
|
||||
|
||||
const filterGroup = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
||||
filterGroup.innerHTML = `
|
||||
<filter id="${filterId}" color-interpolation-filters="sRGB" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feImage id="fe-map-${id}" x="0" y="0" width="100%" height="100%" preserveAspectRatio="none" result="map" />
|
||||
<feDisplacementMap id="disp-red-${id}" in="SourceGraphic" in2="map" scale="${config.distortionScale + config.redOffset}" xChannelSelector="${config.xChannel}" yChannelSelector="${config.yChannel}" result="dispRed" />
|
||||
<feColorMatrix in="dispRed" type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" result="red" />
|
||||
<feDisplacementMap id="disp-green-${id}" in="SourceGraphic" in2="map" scale="${config.distortionScale + config.greenOffset}" xChannelSelector="${config.xChannel}" yChannelSelector="${config.yChannel}" result="dispGreen" />
|
||||
<feColorMatrix in="dispGreen" type="matrix" values="0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0" result="green" />
|
||||
<feDisplacementMap id="disp-blue-${id}" in="SourceGraphic" in2="map" scale="${config.distortionScale + config.blueOffset}" xChannelSelector="${config.xChannel}" yChannelSelector="${config.yChannel}" result="dispBlue" />
|
||||
<feColorMatrix in="dispBlue" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0" result="blue" />
|
||||
<feBlend in="red" in2="green" mode="screen" result="rg" />
|
||||
<feBlend in="rg" in2="blue" mode="screen" result="output" />
|
||||
<feGaussianBlur id="blur-${id}" in="output" stdDeviation="${config.displace}" />
|
||||
</filter>
|
||||
`;
|
||||
defsContainer.appendChild(filterGroup);
|
||||
|
||||
const rawContent = el.innerHTML;
|
||||
el.innerHTML = '';
|
||||
|
||||
const backdropLayer = document.createElement('span');
|
||||
backdropLayer.className = 'glass-surface__backdrop';
|
||||
|
||||
const contentLayer = document.createElement('span');
|
||||
contentLayer.className = 'glass-surface__content';
|
||||
contentLayer.innerHTML = rawContent;
|
||||
|
||||
el.appendChild(backdropLayer);
|
||||
el.appendChild(contentLayer);
|
||||
el.classList.add('glass-surface');
|
||||
|
||||
el.style.borderRadius = `${config.borderRadius}px`;
|
||||
el.style.setProperty('--glass-radius', `${config.borderRadius}px`);
|
||||
el.style.setProperty('--glass-saturation', config.saturation.toString());
|
||||
|
||||
const feImage = defsContainer.querySelector(`#fe-map-${id}`);
|
||||
|
||||
const updateMap = () => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const width = Math.max(Math.round(rect.width), 40);
|
||||
const height = Math.max(Math.round(rect.height), 20);
|
||||
const edgeSize = Math.min(width, height) * (config.borderWidth * 0.5);
|
||||
|
||||
const svgContent = `
|
||||
<svg viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="${redGradId}" x1="100%" y1="0%" x2="0%" y2="0%">
|
||||
<stop offset="0%" stop-color="#0000"/>
|
||||
<stop offset="100%" stop-color="red"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="${blueGradId}" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#0000"/>
|
||||
<stop offset="100%" stop-color="blue"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" width="${width}" height="${height}" fill="black"></rect>
|
||||
<rect x="0" y="0" width="${width}" height="${height}" rx="${config.borderRadius}" fill="url(#${redGradId})" />
|
||||
<rect x="0" y="0" width="${width}" height="${height}" rx="${config.borderRadius}" fill="url(#${blueGradId})" style="mix-blend-mode: ${config.mixBlendMode}" />
|
||||
<rect x="${edgeSize}" y="${edgeSize}" width="${Math.max(0, width - edgeSize * 2)}" height="${Math.max(0, height - edgeSize * 2)}" rx="${config.borderRadius}" fill="hsl(0 0% ${config.brightness}% / ${config.opacity})" style="filter:blur(${config.blur}px)" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
if (feImage) {
|
||||
feImage.setAttribute('href', `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`);
|
||||
}
|
||||
|
||||
el.style.setProperty('--glass-filter-id', `url(#${filterId})`);
|
||||
el.style.setProperty('--glass-radius', `${config.borderRadius}px`);
|
||||
el.style.setProperty('--glass-saturation', config.saturation.toString());
|
||||
el.style.borderRadius = `${config.borderRadius}px`;
|
||||
};
|
||||
|
||||
updateMap();
|
||||
const ro = new ResizeObserver(() => requestAnimationFrame(updateMap));
|
||||
ro.observe(el);
|
||||
});
|
||||
}
|
||||
245
src/scripts/animations/light-rays.js
Normal file
245
src/scripts/animations/light-rays.js
Normal file
@@ -0,0 +1,245 @@
|
||||
import { Renderer, Program, Triangle, Mesh } from 'https://esm.sh/ogl@1.0.11';
|
||||
|
||||
const hexToRgb = hex => {
|
||||
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return m ? [parseInt(m[1], 16) / 255, parseInt(m[2], 16) / 255, parseInt(m[3], 16) / 255] : [1, 1, 1];
|
||||
};
|
||||
|
||||
const getAnchorAndDir = (origin, w, h) => {
|
||||
const outside = 0.05;
|
||||
switch (origin) {
|
||||
case 'top-left':
|
||||
return { anchor: [0, -outside * h], dir: [0.707, 0.707] };
|
||||
case 'top-right':
|
||||
return { anchor: [w, -outside * h], dir: [-0.707, 0.707] };
|
||||
case 'left':
|
||||
return { anchor: [-outside * w, 0.5 * h], dir: [1, 0] };
|
||||
case 'right':
|
||||
return { anchor: [(1 + outside) * w, 0.5 * h], dir: [-1, 0] };
|
||||
case 'bottom-left':
|
||||
return { anchor: [0, (1 + outside) * h], dir: [0.707, -0.707] };
|
||||
case 'bottom-center':
|
||||
return { anchor: [0.5 * w, (1 + outside) * h], dir: [0, -1] };
|
||||
case 'bottom-right':
|
||||
return { anchor: [w, (1 + outside) * h], dir: [-0.707, -0.707] };
|
||||
default:
|
||||
return { anchor: [0.5 * w, -outside * h], dir: [0, 1] };
|
||||
}
|
||||
};
|
||||
|
||||
export function initLightRays(container, options = {}) {
|
||||
if (!container) return () => { };
|
||||
|
||||
const raysOrigin = options.raysOrigin ?? 'bottom-center';
|
||||
const rayColor1 = options.rayColor1 ?? '#EAB308';
|
||||
const rayColor2 = options.rayColor2 ?? '#96c8ff';
|
||||
const blend = options.blend ?? 0.65;
|
||||
const raysSpeed = options.raysSpeed ?? 1.8;
|
||||
const lightSpread = options.lightSpread ?? 0.7;
|
||||
const rayLength = options.rayLength ?? 2.5;
|
||||
const pulsating = Boolean(options.pulsating);
|
||||
const fadeDistance = options.fadeDistance ?? 2.0;
|
||||
const saturation = options.saturation ?? 1.4;
|
||||
const followMouse = options.followMouse !== false;
|
||||
const mouseInfluence = options.mouseInfluence ?? 0.12;
|
||||
const noiseAmount = options.noiseAmount ?? 0.08;
|
||||
const distortion = options.distortion ?? 0.04;
|
||||
|
||||
const mouse = { x: 0.5, y: 0.5 };
|
||||
const smoothMouse = { x: 0.5, y: 0.5 };
|
||||
let animationId = null;
|
||||
|
||||
const renderer = new Renderer({
|
||||
dpr: Math.min(window.devicePixelRatio, 2),
|
||||
alpha: true
|
||||
});
|
||||
const gl = renderer.gl;
|
||||
Object.assign(gl.canvas.style, {
|
||||
position: 'absolute',
|
||||
inset: '0',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'block',
|
||||
pointerEvents: 'none'
|
||||
});
|
||||
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
container.appendChild(gl.canvas);
|
||||
|
||||
const vert = `
|
||||
attribute vec2 position;
|
||||
void main() {
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
const frag = `
|
||||
precision highp float;
|
||||
|
||||
uniform float iTime;
|
||||
uniform vec2 iResolution;
|
||||
|
||||
uniform vec2 rayPos;
|
||||
uniform vec2 rayDir;
|
||||
uniform vec3 rayColor1;
|
||||
uniform vec3 rayColor2;
|
||||
uniform float blend;
|
||||
uniform float raysSpeed;
|
||||
uniform float lightSpread;
|
||||
uniform float rayLength;
|
||||
uniform float pulsating;
|
||||
uniform float fadeDistance;
|
||||
uniform float saturation;
|
||||
uniform vec2 mousePos;
|
||||
uniform float mouseInfluence;
|
||||
uniform float noiseAmount;
|
||||
uniform float distortion;
|
||||
|
||||
float noise(vec2 st) {
|
||||
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
|
||||
}
|
||||
|
||||
float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord, float seedA, float seedB, float speed) {
|
||||
vec2 sourceToCoord = coord - raySource;
|
||||
vec2 dirNorm = normalize(sourceToCoord);
|
||||
float cosAngle = dot(dirNorm, rayRefDirection);
|
||||
|
||||
float distortedAngle = cosAngle + distortion * sin(iTime * 2.0 + length(sourceToCoord) * 0.005) * 0.2;
|
||||
float spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(lightSpread, 0.001));
|
||||
|
||||
float maxDim = max(iResolution.x, iResolution.y);
|
||||
float distance = length(sourceToCoord);
|
||||
float maxDistance = maxDim * rayLength;
|
||||
float lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0);
|
||||
|
||||
float fadeRadius = maxDim * fadeDistance;
|
||||
float fadeFalloff = clamp((fadeRadius - distance) / fadeRadius, 0.0, 1.0);
|
||||
float pulse = pulsating > 0.5 ? (0.85 + 0.15 * sin(iTime * speed * 3.0)) : 1.0;
|
||||
|
||||
float baseStrength = clamp(
|
||||
(0.45 + 0.15 * sin(distortedAngle * seedA + iTime * speed)) +
|
||||
(0.3 + 0.2 * cos(-distortedAngle * seedB + iTime * speed)),
|
||||
0.0, 1.0
|
||||
);
|
||||
|
||||
return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 coord = vec2(gl_FragCoord.x, iResolution.y - gl_FragCoord.y);
|
||||
|
||||
vec2 finalRayDir = rayDir;
|
||||
if (mouseInfluence > 0.0) {
|
||||
vec2 mouseScreenPos = mousePos * iResolution.xy;
|
||||
vec2 mouseDirection = normalize(mouseScreenPos - rayPos);
|
||||
finalRayDir = normalize(mix(rayDir, mouseDirection, mouseInfluence));
|
||||
}
|
||||
|
||||
vec4 rays1 = vec4(rayColor1, 1.0) * rayStrength(rayPos, finalRayDir, coord, 36.2214, 21.11349, 1.5 * raysSpeed);
|
||||
vec4 rays2 = vec4(rayColor2, 1.0) * rayStrength(rayPos, finalRayDir, coord, 22.3991, 18.0234, 1.1 * raysSpeed);
|
||||
|
||||
vec4 color = rays1 * (1.0 - blend) * 1.2 + rays2 * blend * 1.2;
|
||||
|
||||
if (noiseAmount > 0.0) {
|
||||
float n = noise(coord * 0.01 + iTime * 0.1);
|
||||
color.rgb *= (1.0 - noiseAmount + noiseAmount * n);
|
||||
}
|
||||
|
||||
float maxDim = max(iResolution.x, iResolution.y);
|
||||
float distFromSource = length(coord - rayPos) / maxDim;
|
||||
float glowFalloff = clamp(1.3 - distFromSource * 0.7, 0.3, 1.6);
|
||||
color.rgb *= glowFalloff;
|
||||
|
||||
if (saturation != 1.0) {
|
||||
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
||||
color.rgb = mix(vec3(gray), color.rgb, saturation);
|
||||
}
|
||||
|
||||
color.a = clamp(max(color.r, max(color.g, color.b)) * 1.4, 0.0, 1.0);
|
||||
gl_FragColor = color;
|
||||
}
|
||||
`;
|
||||
|
||||
const uniforms = {
|
||||
iTime: { value: 0 },
|
||||
iResolution: { value: [1, 1] },
|
||||
rayPos: { value: [0, 0] },
|
||||
rayDir: { value: [0, -1] },
|
||||
rayColor1: { value: hexToRgb(rayColor1) },
|
||||
rayColor2: { value: hexToRgb(rayColor2) },
|
||||
blend: { value: blend },
|
||||
raysSpeed: { value: raysSpeed },
|
||||
lightSpread: { value: lightSpread },
|
||||
rayLength: { value: rayLength },
|
||||
pulsating: { value: pulsating ? 1.0 : 0.0 },
|
||||
fadeDistance: { value: fadeDistance },
|
||||
saturation: { value: saturation },
|
||||
mousePos: { value: [0.5, 0.5] },
|
||||
mouseInfluence: { value: mouseInfluence },
|
||||
noiseAmount: { value: noiseAmount },
|
||||
distortion: { value: distortion }
|
||||
};
|
||||
|
||||
const geometry = new Triangle(gl);
|
||||
const program = new Program(gl, { vertex: vert, fragment: frag, uniforms });
|
||||
const mesh = new Mesh(gl, { geometry, program });
|
||||
|
||||
const updatePlacement = () => {
|
||||
if (!container || !renderer) return;
|
||||
renderer.dpr = Math.min(window.devicePixelRatio, 2);
|
||||
const wCSS = container.clientWidth || window.innerWidth || 1;
|
||||
const hCSS = container.clientHeight || window.innerHeight || 1;
|
||||
renderer.setSize(wCSS, hCSS);
|
||||
|
||||
const w = wCSS * renderer.dpr;
|
||||
const h = hCSS * renderer.dpr;
|
||||
uniforms.iResolution.value = [w, h];
|
||||
|
||||
const { anchor, dir } = getAnchorAndDir(raysOrigin, w, h);
|
||||
uniforms.rayPos.value = anchor;
|
||||
uniforms.rayDir.value = dir;
|
||||
};
|
||||
|
||||
const handleMouseMove = e => {
|
||||
const rect = container.getBoundingClientRect();
|
||||
mouse.x = (e.clientX - rect.left) / (rect.width || 1);
|
||||
mouse.y = (e.clientY - rect.top) / (rect.height || 1);
|
||||
};
|
||||
|
||||
if (followMouse) {
|
||||
window.addEventListener('mousemove', handleMouseMove, { passive: true });
|
||||
}
|
||||
|
||||
const loop = t => {
|
||||
uniforms.iTime.value = t * 0.001;
|
||||
|
||||
if (followMouse && mouseInfluence > 0.0) {
|
||||
smoothMouse.x += (mouse.x - smoothMouse.x) * 0.08;
|
||||
smoothMouse.y += (mouse.y - smoothMouse.y) * 0.08;
|
||||
uniforms.mousePos.value = [smoothMouse.x, smoothMouse.y];
|
||||
}
|
||||
|
||||
try {
|
||||
renderer.render({ scene: mesh });
|
||||
animationId = requestAnimationFrame(loop);
|
||||
} catch (e) { }
|
||||
};
|
||||
|
||||
const ro = new ResizeObserver(updatePlacement);
|
||||
ro.observe(container);
|
||||
window.addEventListener('resize', updatePlacement);
|
||||
updatePlacement();
|
||||
animationId = requestAnimationFrame(loop);
|
||||
|
||||
return () => {
|
||||
if (animationId) cancelAnimationFrame(animationId);
|
||||
ro.disconnect();
|
||||
window.removeEventListener('resize', updatePlacement);
|
||||
if (followMouse) window.removeEventListener('mousemove', handleMouseMove);
|
||||
if (renderer && renderer.gl && renderer.gl.canvas && renderer.gl.canvas.parentNode) {
|
||||
renderer.gl.canvas.parentNode.removeChild(renderer.gl.canvas);
|
||||
}
|
||||
};
|
||||
}
|
||||
361
src/scripts/animations/prism.js
Normal file
361
src/scripts/animations/prism.js
Normal file
@@ -0,0 +1,361 @@
|
||||
import { Renderer, Triangle, Program, Mesh } from 'https://esm.sh/ogl@1.0.11';
|
||||
|
||||
export function initPrism(container, options = {}) {
|
||||
if (!container) return () => { };
|
||||
|
||||
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;
|
||||
const GLOW = Math.max(0.0, options.glow ?? 1);
|
||||
const NOISE = Math.max(0.0, options.noise ?? 0.5);
|
||||
const offX = options.offset?.x ?? 0;
|
||||
const offY = options.offset?.y ?? 0;
|
||||
const SAT = options.transparent !== false ? 1.5 : 1;
|
||||
const SCALE = Math.max(0.001, options.scale ?? 3.6);
|
||||
const HUE = options.hueShift || 0;
|
||||
const CFREQ = Math.max(0.0, options.colorFrequency ?? 1);
|
||||
const BLOOM = Math.max(0.0, options.bloom ?? 1);
|
||||
const RSX = 1;
|
||||
const RSY = 1;
|
||||
const RSZ = 1;
|
||||
const TS = Math.max(0, options.timeScale ?? 0.5);
|
||||
const HOVSTR = Math.max(0, options.hoverStrength ?? 2);
|
||||
const INERT = Math.max(0, Math.min(1, options.inertia ?? 0.05));
|
||||
const animationType = options.animationType || 'rotate';
|
||||
const transparent = options.transparent !== false;
|
||||
const suspendWhenOffscreen = Boolean(options.suspendWhenOffscreen);
|
||||
|
||||
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
||||
const renderer = new Renderer({
|
||||
dpr,
|
||||
alpha: transparent,
|
||||
antialias: false
|
||||
});
|
||||
const gl = renderer.gl;
|
||||
gl.disable(gl.DEPTH_TEST);
|
||||
gl.disable(gl.CULL_FACE);
|
||||
gl.disable(gl.BLEND);
|
||||
|
||||
Object.assign(gl.canvas.style, {
|
||||
position: 'absolute',
|
||||
inset: '0',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'block'
|
||||
});
|
||||
container.appendChild(gl.canvas);
|
||||
|
||||
const vertex = `
|
||||
attribute vec2 position;
|
||||
void main() {
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
const fragment = `
|
||||
precision highp float;
|
||||
|
||||
uniform vec2 iResolution;
|
||||
uniform float iTime;
|
||||
|
||||
uniform float uHeight;
|
||||
uniform float uBaseHalf;
|
||||
uniform mat3 uRot;
|
||||
uniform int uUseBaseWobble;
|
||||
uniform float uGlow;
|
||||
uniform vec2 uOffsetPx;
|
||||
uniform float uNoise;
|
||||
uniform float uSaturation;
|
||||
uniform float uScale;
|
||||
uniform float uHueShift;
|
||||
uniform float uColorFreq;
|
||||
uniform float uBloom;
|
||||
uniform float uCenterShift;
|
||||
uniform float uInvBaseHalf;
|
||||
uniform float uInvHeight;
|
||||
uniform float uMinAxis;
|
||||
uniform float uPxScale;
|
||||
uniform float uTimeScale;
|
||||
|
||||
vec4 tanh4(vec4 x){
|
||||
vec4 e2x = exp(2.0*x);
|
||||
return (e2x - 1.0) / (e2x + 1.0);
|
||||
}
|
||||
|
||||
float rand(vec2 co){
|
||||
return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453123);
|
||||
}
|
||||
|
||||
float sdOctaAnisoInv(vec3 p){
|
||||
vec3 q = vec3(abs(p.x) * uInvBaseHalf, abs(p.y) * uInvHeight, abs(p.z) * uInvBaseHalf);
|
||||
float m = q.x + q.y + q.z - 1.0;
|
||||
return m * uMinAxis * 0.5773502691896258;
|
||||
}
|
||||
|
||||
float sdPyramidUpInv(vec3 p){
|
||||
float oct = sdOctaAnisoInv(p);
|
||||
float halfSpace = -p.y;
|
||||
return max(oct, halfSpace);
|
||||
}
|
||||
|
||||
mat3 hueRotation(float a){
|
||||
float c = cos(a), s = sin(a);
|
||||
mat3 W = mat3(
|
||||
0.299, 0.587, 0.114,
|
||||
0.299, 0.587, 0.114,
|
||||
0.299, 0.587, 0.114
|
||||
);
|
||||
mat3 U = mat3(
|
||||
0.701, -0.587, -0.114,
|
||||
-0.299, 0.413, -0.114,
|
||||
-0.300, -0.588, 0.886
|
||||
);
|
||||
mat3 V = mat3(
|
||||
0.168, -0.331, 0.500,
|
||||
0.328, 0.035, -0.500,
|
||||
-0.497, 0.296, 0.201
|
||||
);
|
||||
return W + U * c + V * s;
|
||||
}
|
||||
|
||||
void main(){
|
||||
vec2 f = (gl_FragCoord.xy - 0.5 * iResolution.xy - uOffsetPx) * uPxScale;
|
||||
|
||||
float z = 5.0;
|
||||
float d = 0.0;
|
||||
|
||||
vec3 p;
|
||||
vec4 o = vec4(0.0);
|
||||
|
||||
float centerShift = uCenterShift;
|
||||
float cf = uColorFreq;
|
||||
|
||||
mat2 wob = mat2(1.0);
|
||||
if (uUseBaseWobble == 1) {
|
||||
float t = iTime * uTimeScale;
|
||||
float c0 = cos(t + 0.0);
|
||||
float c1 = cos(t + 33.0);
|
||||
float c2 = cos(t + 11.0);
|
||||
wob = mat2(c0, c1, c2, c0);
|
||||
}
|
||||
|
||||
const int STEPS = 100;
|
||||
for (int i = 0; i < STEPS; i++) {
|
||||
p = vec3(f, z);
|
||||
p.xz = p.xz * wob;
|
||||
p = uRot * p;
|
||||
vec3 q = p;
|
||||
q.y += centerShift;
|
||||
d = 0.1 + 0.2 * abs(sdPyramidUpInv(q));
|
||||
z -= d;
|
||||
o += (sin((p.y + z) * cf + vec4(0.0, 1.0, 2.0, 3.0)) + 1.0) / d;
|
||||
}
|
||||
|
||||
o = tanh4(o * o * (uGlow * uBloom) / 1e5);
|
||||
|
||||
vec3 col = o.rgb;
|
||||
float n = rand(gl_FragCoord.xy + vec2(iTime));
|
||||
col += (n - 0.5) * uNoise;
|
||||
col = clamp(col, 0.0, 1.0);
|
||||
|
||||
float L = dot(col, vec3(0.2126, 0.7152, 0.0722));
|
||||
col = clamp(mix(vec3(L), col, uSaturation), 0.0, 1.0);
|
||||
|
||||
if(abs(uHueShift) > 0.0001){
|
||||
col = clamp(hueRotation(uHueShift) * col, 0.0, 1.0);
|
||||
}
|
||||
|
||||
gl_FragColor = vec4(col, o.a);
|
||||
}
|
||||
`;
|
||||
|
||||
const geometry = new Triangle(gl);
|
||||
const iResBuf = new Float32Array(2);
|
||||
const offsetPxBuf = new Float32Array(2);
|
||||
|
||||
const program = new Program(gl, {
|
||||
vertex,
|
||||
fragment,
|
||||
uniforms: {
|
||||
iResolution: { value: iResBuf },
|
||||
iTime: { value: 0 },
|
||||
uHeight: { value: H },
|
||||
uBaseHalf: { value: BASE_HALF },
|
||||
uUseBaseWobble: { value: 1 },
|
||||
uRot: { value: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) },
|
||||
uGlow: { value: GLOW },
|
||||
uOffsetPx: { value: offsetPxBuf },
|
||||
uNoise: { value: NOISE },
|
||||
uSaturation: { value: SAT },
|
||||
uScale: { value: SCALE },
|
||||
uHueShift: { value: HUE },
|
||||
uColorFreq: { value: CFREQ },
|
||||
uBloom: { value: BLOOM },
|
||||
uCenterShift: { value: H * 0.25 },
|
||||
uInvBaseHalf: { value: 1 / BASE_HALF },
|
||||
uInvHeight: { value: 1 / H },
|
||||
uMinAxis: { value: Math.min(BASE_HALF, H) },
|
||||
uPxScale: {
|
||||
value: 1 / ((gl.drawingBufferHeight || 1) * 0.1 * SCALE)
|
||||
},
|
||||
uTimeScale: { value: TS }
|
||||
}
|
||||
});
|
||||
const mesh = new Mesh(gl, { geometry, program });
|
||||
|
||||
const resize = () => {
|
||||
const w = container.clientWidth || 1;
|
||||
const h = container.clientHeight || 1;
|
||||
renderer.setSize(w, h);
|
||||
iResBuf[0] = gl.drawingBufferWidth;
|
||||
iResBuf[1] = gl.drawingBufferHeight;
|
||||
offsetPxBuf[0] = offX * dpr;
|
||||
offsetPxBuf[1] = offY * dpr;
|
||||
program.uniforms.uPxScale.value = 1 / ((gl.drawingBufferHeight || 1) * 0.1 * SCALE);
|
||||
};
|
||||
const ro = new ResizeObserver(resize);
|
||||
ro.observe(container);
|
||||
resize();
|
||||
|
||||
const rotBuf = new Float32Array(9);
|
||||
const setMat3FromEuler = (yawY, pitchX, rollZ, out) => {
|
||||
const cy = Math.cos(yawY), sy = Math.sin(yawY);
|
||||
const cx = Math.cos(pitchX), sx = Math.sin(pitchX);
|
||||
const cz = Math.cos(rollZ), sz = Math.sin(rollZ);
|
||||
out[0] = cy * cz + sy * sx * sz;
|
||||
out[1] = cx * sz;
|
||||
out[2] = -sy * cz + cy * sx * sz;
|
||||
out[3] = -cy * sz + sy * sx * cz;
|
||||
out[4] = cx * cz;
|
||||
out[5] = sy * sz + cy * sx * cz;
|
||||
out[6] = sy * cx;
|
||||
out[7] = -sx;
|
||||
out[8] = cy * cx;
|
||||
return out;
|
||||
};
|
||||
|
||||
const NOISE_IS_ZERO = NOISE < 1e-6;
|
||||
let raf = 0;
|
||||
const t0 = performance.now();
|
||||
const startRAF = () => {
|
||||
if (raf) return;
|
||||
raf = requestAnimationFrame(render);
|
||||
};
|
||||
const stopRAF = () => {
|
||||
if (!raf) return;
|
||||
cancelAnimationFrame(raf);
|
||||
raf = 0;
|
||||
};
|
||||
|
||||
const rnd = () => Math.random();
|
||||
const wX = (0.3 + rnd() * 0.6) * RSX;
|
||||
const wY = (0.2 + rnd() * 0.7) * RSY;
|
||||
const wZ = (0.1 + rnd() * 0.5) * RSZ;
|
||||
const phX = rnd() * Math.PI * 2;
|
||||
const phZ = rnd() * Math.PI * 2;
|
||||
|
||||
let yaw = 0, pitch = 0, roll = 0;
|
||||
let targetYaw = 0, targetPitch = 0;
|
||||
const lerp = (a, b, t) => a + (b - a) * t;
|
||||
|
||||
const pointer = { x: 0, y: 0, inside: true };
|
||||
const onMove = e => {
|
||||
const ww = Math.max(1, window.innerWidth);
|
||||
const wh = Math.max(1, window.innerHeight);
|
||||
const cx = ww * 0.5;
|
||||
const cy = wh * 0.5;
|
||||
pointer.x = Math.max(-1, Math.min(1, (e.clientX - cx) / cx));
|
||||
pointer.y = Math.max(-1, Math.min(1, (e.clientY - cy) / cy));
|
||||
pointer.inside = true;
|
||||
};
|
||||
const onLeave = () => { pointer.inside = false; };
|
||||
const onBlur = () => { pointer.inside = false; };
|
||||
|
||||
let onPointerMove = null;
|
||||
if (animationType === 'hover') {
|
||||
onPointerMove = e => {
|
||||
onMove(e);
|
||||
startRAF();
|
||||
};
|
||||
window.addEventListener('pointermove', onPointerMove, { passive: true });
|
||||
window.addEventListener('mouseleave', onLeave);
|
||||
window.addEventListener('blur', onBlur);
|
||||
program.uniforms.uUseBaseWobble.value = 0;
|
||||
} else if (animationType === '3drotate') {
|
||||
program.uniforms.uUseBaseWobble.value = 0;
|
||||
} else {
|
||||
program.uniforms.uUseBaseWobble.value = 1;
|
||||
}
|
||||
|
||||
const render = t => {
|
||||
const time = (t - t0) * 0.001;
|
||||
program.uniforms.iTime.value = time;
|
||||
|
||||
let continueRAF = true;
|
||||
|
||||
if (animationType === 'hover') {
|
||||
const maxPitch = 0.6 * HOVSTR;
|
||||
const maxYaw = 0.6 * HOVSTR;
|
||||
targetYaw = (pointer.inside ? -pointer.x : 0) * maxYaw;
|
||||
targetPitch = (pointer.inside ? pointer.y : 0) * maxPitch;
|
||||
yaw = lerp(yaw, targetYaw, INERT);
|
||||
pitch = lerp(pitch, targetPitch, INERT);
|
||||
roll = lerp(roll, 0, 0.1);
|
||||
program.uniforms.uRot.value = setMat3FromEuler(yaw, pitch, roll, rotBuf);
|
||||
|
||||
if (NOISE_IS_ZERO && Math.abs(yaw - targetYaw) < 1e-4 && Math.abs(pitch - targetPitch) < 1e-4 && Math.abs(roll) < 1e-4) {
|
||||
continueRAF = false;
|
||||
}
|
||||
} else if (animationType === '3drotate') {
|
||||
const tScaled = time * TS;
|
||||
yaw = tScaled * wY;
|
||||
pitch = Math.sin(tScaled * wX + phX) * 0.6;
|
||||
roll = Math.sin(tScaled * wZ + phZ) * 0.5;
|
||||
program.uniforms.uRot.value = setMat3FromEuler(yaw, pitch, roll, rotBuf);
|
||||
if (TS < 1e-6) continueRAF = false;
|
||||
} else {
|
||||
rotBuf[0] = 1; rotBuf[1] = 0; rotBuf[2] = 0;
|
||||
rotBuf[3] = 0; rotBuf[4] = 1; rotBuf[5] = 0;
|
||||
rotBuf[6] = 0; rotBuf[7] = 0; rotBuf[8] = 1;
|
||||
program.uniforms.uRot.value = rotBuf;
|
||||
if (TS < 1e-6) continueRAF = false;
|
||||
}
|
||||
|
||||
renderer.render({ scene: mesh });
|
||||
if (continueRAF) {
|
||||
raf = requestAnimationFrame(render);
|
||||
} else {
|
||||
raf = 0;
|
||||
}
|
||||
};
|
||||
|
||||
if (suspendWhenOffscreen) {
|
||||
const io = new IntersectionObserver(entries => {
|
||||
const vis = entries.some(e => e.isIntersecting);
|
||||
if (vis) startRAF();
|
||||
else stopRAF();
|
||||
});
|
||||
io.observe(container);
|
||||
startRAF();
|
||||
container.__prismIO = io;
|
||||
} else {
|
||||
startRAF();
|
||||
}
|
||||
|
||||
return () => {
|
||||
stopRAF();
|
||||
ro.disconnect();
|
||||
if (animationType === 'hover' && onPointerMove) {
|
||||
window.removeEventListener('pointermove', onPointerMove);
|
||||
window.removeEventListener('mouseleave', onLeave);
|
||||
window.removeEventListener('blur', onBlur);
|
||||
}
|
||||
if (suspendWhenOffscreen && container.__prismIO) {
|
||||
container.__prismIO.disconnect();
|
||||
delete container.__prismIO;
|
||||
}
|
||||
if (gl.canvas.parentElement === container) {
|
||||
container.removeChild(gl.canvas);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { observeReveals } from './animations/reveal-observer.js';
|
||||
import { initGlassSurface } from './animations/glass-surface.js';
|
||||
import { initHeroSection } from './sections/hero.js';
|
||||
import { initTestimonialsSection } from './sections/testimonials.js';
|
||||
import { initPartnersSection } from './sections/partners.js';
|
||||
@@ -8,6 +9,21 @@ import { initStatsSection } from './sections/stats.js';
|
||||
function initApp() {
|
||||
observeReveals();
|
||||
|
||||
initGlassSurface('.nav-links, .discord-copy-bar, .platform-pill, .hero-cta-light', {
|
||||
borderRadius: 50,
|
||||
borderWidth: 0,
|
||||
backgroundOpacity: 0,
|
||||
saturation: 1,
|
||||
brightness: 0,
|
||||
opacity: 1,
|
||||
blur: 15,
|
||||
displace: 0.5,
|
||||
distortionScale: -180,
|
||||
redOffset: -50,
|
||||
greenOffset: -50,
|
||||
blueOffset: 20
|
||||
});
|
||||
|
||||
initHeroSection();
|
||||
initStatsSection();
|
||||
initTestimonialsSection();
|
||||
|
||||
@@ -1,95 +1,48 @@
|
||||
import { qs, isMobileViewport } from '../utils/dom.js';
|
||||
import { qs } 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 { initPrism } from '../animations/prism.js';
|
||||
import { initLightRays } from '../animations/light-rays.js';
|
||||
import { DISCORD_LOADER_SCRIPT, COPY_FEEDBACK_DURATION_MS } from '../config/constants.js';
|
||||
|
||||
const HERO_GREETING = 'Vyla Home';
|
||||
const HERO_GREETING = 'The Local-First Desktop Media Center';
|
||||
|
||||
function initVideoColorExtraction() {
|
||||
const videos = document.querySelectorAll('.hero-atmosphere-video');
|
||||
if (videos.length === 0) return;
|
||||
function initHeroPrism() {
|
||||
const prismContainer = document.getElementById('hero-prism');
|
||||
if (!prismContainer) return;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
let animationFrameId;
|
||||
let frameCount = 0;
|
||||
let currentVideoColor = 'rgb(0, 0, 0)';
|
||||
initPrism(prismContainer, {
|
||||
animationType: 'rotate',
|
||||
timeScale: 0.5,
|
||||
height: 3.5,
|
||||
baseWidth: 5.5,
|
||||
scale: 3.6,
|
||||
hueShift: 0,
|
||||
colorFrequency: 1,
|
||||
noise: 0.5,
|
||||
glow: 1,
|
||||
transparent: true
|
||||
});
|
||||
}
|
||||
|
||||
function extractColors() {
|
||||
const video = videos[0];
|
||||
if (video.readyState < 2) {
|
||||
animationFrameId = requestAnimationFrame(extractColors);
|
||||
return;
|
||||
}
|
||||
function initCtaRays() {
|
||||
const raysContainer = document.getElementById('cta-rays');
|
||||
if (!raysContainer) 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) {
|
||||
}
|
||||
} catch (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', () => {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
});
|
||||
|
||||
video.addEventListener('play', () => {
|
||||
animationFrameId = requestAnimationFrame(extractColors);
|
||||
});
|
||||
initLightRays(raysContainer, {
|
||||
raysOrigin: 'bottom-center',
|
||||
rayColor1: '#EAB308',
|
||||
rayColor2: '#96c8ff',
|
||||
blend: 0.65,
|
||||
raysSpeed: 1.8,
|
||||
lightSpread: 0.7,
|
||||
rayLength: 2.5,
|
||||
followMouse: true,
|
||||
mouseInfluence: 0.12,
|
||||
noiseAmount: 0.08,
|
||||
distortion: 0.04,
|
||||
fadeDistance: 2.0,
|
||||
saturation: 1.4
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,7 +53,6 @@ function initHeroTitle() {
|
||||
|
||||
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;
|
||||
|
||||
@@ -131,11 +83,10 @@ function initCtaCopyButton() {
|
||||
}
|
||||
|
||||
export function initHeroSection() {
|
||||
initHeroPrism();
|
||||
initCtaRays();
|
||||
initHeroTitle();
|
||||
initDiscordCopyBar();
|
||||
initHeroCopyButton();
|
||||
initCtaCopyButton();
|
||||
initVideoColorExtraction();
|
||||
initFluidByteField('hero-byte-overlay');
|
||||
initFluidByteField('cta-byte-overlay');
|
||||
}
|
||||
@@ -27,8 +27,11 @@
|
||||
.hero-cta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1rem;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
@@ -70,12 +73,19 @@
|
||||
}
|
||||
|
||||
.hero-cta-light {
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
color: #0f0f0f;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
color: var(--color-fg);
|
||||
border-radius: 50px;
|
||||
padding: 0.75rem 1.75rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
text-decoration: none;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.hero-cta-light:hover {
|
||||
background: #fff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
|
||||
52
src/styles/components/glass-surface.css
Normal file
52
src/styles/components/glass-surface.css
Normal file
@@ -0,0 +1,52 @@
|
||||
.glass-surface {
|
||||
position: relative !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
border-radius: var(--glass-radius, 50px) !important;
|
||||
overflow: visible !important;
|
||||
text-decoration: none !important;
|
||||
box-sizing: border-box !important;
|
||||
width: auto !important;
|
||||
max-width: max-content !important;
|
||||
}
|
||||
|
||||
.glass-surface__backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit !important;
|
||||
background: transparent;
|
||||
backdrop-filter: var(--glass-filter-id, none) saturate(var(--glass-saturation, 0));
|
||||
-webkit-backdrop-filter: var(--glass-filter-id, none) saturate(var(--glass-saturation, 0));
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.glass-surface__content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: inherit;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
color: inherit;
|
||||
white-space: nowrap;
|
||||
border-radius: inherit;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.glass-surface:hover .glass-surface__backdrop {
|
||||
border-color: rgba(255, 255, 255, 0.45);
|
||||
box-shadow:
|
||||
inset 0 1px 2px 0 rgba(255, 255, 255, 0.6),
|
||||
inset 0 -1px 1px 0 rgba(255, 255, 255, 0.15),
|
||||
0 6px 20px 0 rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
@@ -1,31 +1,47 @@
|
||||
.discord-copy-bar {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(8px) saturate(140%);
|
||||
-webkit-backdrop-filter: blur(8px) saturate(140%);
|
||||
border: none;
|
||||
border-radius: var(--radius-pill);
|
||||
padding: 0.5rem 0.5rem 0.5rem 0.9rem;
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-fg);
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 0.65rem 1.25rem;
|
||||
border-radius: 50px;
|
||||
cursor: pointer;
|
||||
letter-spacing: -0.01em;
|
||||
margin-top: 1.2rem;
|
||||
transition: border-color var(--duration-fast) ease, background var(--duration-fast) ease;
|
||||
margin-top: 1.5rem;
|
||||
max-width: 90vw;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.copy-discord-label,
|
||||
.discord-copy-actions {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.copy-discord-label {
|
||||
position: relative;
|
||||
max-width: 350px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-fg-dim);
|
||||
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--color-fg);
|
||||
-webkit-mask-image: linear-gradient(to left, transparent, black 15%);
|
||||
mask-image: linear-gradient(to left, transparent, black 15%);
|
||||
}
|
||||
|
||||
.discord-copy-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.discord-copy-icon .check-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.discord-copy-icon.copied .copy-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.discord-copy-icon.copied .check-icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.discord-copy-actions {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
.footer {
|
||||
position: relative;
|
||||
padding: 48px 24px 24px;
|
||||
overflow: hidden
|
||||
padding: 32px 24px 24px;
|
||||
overflow: visible;
|
||||
background: transparent;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
@@ -10,20 +12,23 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 40px;
|
||||
font-weight: 400
|
||||
font-weight: 400;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.footer-section h2 {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 7px
|
||||
margin-bottom: 7px;
|
||||
color: var(--color-fg);
|
||||
}
|
||||
|
||||
.footer-section p {
|
||||
color: var(--color-muted);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
font-weight: 400
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.footer-section a {
|
||||
@@ -34,17 +39,17 @@
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
transition: color .2s;
|
||||
font-weight: 400
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.footer-section a:hover {
|
||||
color: var(--color-muted)
|
||||
color: var(--color-fg);
|
||||
}
|
||||
|
||||
.social-links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-weight: 400
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
@@ -52,10 +57,12 @@
|
||||
font-weight: 400;
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-muted);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.footer-bottom p {
|
||||
color: var(--color-muted);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 400
|
||||
font-weight: 400;
|
||||
}
|
||||
@@ -26,13 +26,8 @@
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: clamp(1.25rem, 3vw, 2.5rem);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(8px) saturate(140%);
|
||||
-webkit-backdrop-filter: blur(8px) saturate(140%);
|
||||
padding: 1rem 1rem;
|
||||
gap: 1.25rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,64 +1,45 @@
|
||||
.cta-banner {
|
||||
.cta-footer-wrap {
|
||||
position: relative;
|
||||
padding: clamp(4rem, 10vw, 7rem) 0;
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
background: #000000;
|
||||
width: 100%;
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.cta-rays-container {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.cta-title-warp {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
height: clamp(60px, 10vw, 110px);
|
||||
margin: 0 auto 0.5rem;
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.cta-banner {
|
||||
position: relative;
|
||||
padding: clamp(5rem, 10vw, 8rem) 0 3rem;
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.cta-banner-media {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
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%);
|
||||
-webkit-mask-composite: source-in;
|
||||
mask-composite: intersect;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-webkit-mask-size: 100% 100%;
|
||||
mask-image:
|
||||
linear-gradient(to bottom, transparent, black 50%),
|
||||
linear-gradient(to top, transparent, black 65%);
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 100% 100%;
|
||||
}
|
||||
|
||||
.cta-banner-video {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.cta-banner-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
#cta-byte-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#cta-byte-overlay {
|
||||
display: none;
|
||||
}
|
||||
background: transparent;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.cta-banner-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
position: relative;
|
||||
padding: clamp(6rem, 14vh, 8.5rem) 0 clamp(3rem, 6vh, 5rem);
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hero-atmosphere {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
-webkit-mask-image: linear-gradient(to bottom,
|
||||
var(--video-dominant-color, black) 0%,
|
||||
var(--video-dominant-color, black) 48%,
|
||||
@@ -24,78 +23,13 @@
|
||||
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%;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: 100% 100%;
|
||||
}
|
||||
|
||||
.hero-atmosphere-video {
|
||||
position: absolute;
|
||||
inset: -6%;
|
||||
width: 112%;
|
||||
height: 112%;
|
||||
object-fit: cover;
|
||||
object-position: center top;
|
||||
transform: translateZ(0) scale(1.04);
|
||||
}
|
||||
|
||||
.hero-atmosphere-sheen {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(ellipse at 50% 4%, rgba(255, 255, 255, 0.18), transparent 46%),
|
||||
linear-gradient(116deg, transparent 18%, rgba(255, 255, 255, 0.06) 37%, transparent 51%);
|
||||
mix-blend-mode: soft-light;
|
||||
opacity: 0.8;
|
||||
animation: hero-sheen 16s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
#hero-byte-overlay {
|
||||
.hero-prism {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.9;
|
||||
mix-blend-mode: screen;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes hero-sheen {
|
||||
0% {
|
||||
transform: translate3d(-4%, 0, 0) scale(1.04);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate3d(5%, 1%, 0) scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#hero-byte-overlay {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hero-atmosphere-sheen {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.hero-atmosphere-video,
|
||||
#hero-byte-overlay {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.hero {
|
||||
padding-top: 8rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: landscape) and (max-height: 600px) {
|
||||
.hero {
|
||||
height: auto;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,32 @@
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.hero-title-warp {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
height: clamp(80px, 14vw, 150px);
|
||||
margin: 0.5rem auto 1rem;
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.warp-text {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.warp-text canvas {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 2.3rem;
|
||||
letter-spacing: -.03em;
|
||||
@@ -92,24 +118,31 @@
|
||||
|
||||
.platform-pill {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
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);
|
||||
padding: 0.45rem 1.1rem;
|
||||
border-radius: 50px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-fg-dim);
|
||||
color: #ffffff;
|
||||
margin-bottom: 1.25rem;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
max-width: max-content;
|
||||
}
|
||||
|
||||
.platform-pill svg {
|
||||
color: var(--color-fg);
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
color: #ffffff;
|
||||
fill: currentColor;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.platform-pill span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hero-screen-mockup {
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
transform: translate3d(0, 24px, 0);
|
||||
transition: opacity var(--duration-slow) var(--ease-standard),
|
||||
transform var(--duration-slow) var(--ease-standard);
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
.reveal.in-view {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
transform: none;
|
||||
will-change: auto;
|
||||
}
|
||||
|
||||
.reveal-stagger .reveal:nth-child(1) {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<meta name="msapplication-tooltip" content="Self-hosted desktop media center for streaming entertainment">
|
||||
<meta name="msapplication-starturl" content="/">
|
||||
<meta name="msapplication-window" content="width=device-width;height=device-height">
|
||||
<meta name="msapplication-navbutton-color" content="#1a1a2e">
|
||||
<meta name="msapplication-navbutton-color" content="#FFF">
|
||||
|
||||
<link rel="icon" href="/assets/images/favicon.ico" type="image/x-icon">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon.ico">
|
||||
@@ -73,13 +73,13 @@
|
||||
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<meta name="theme-color" content="#1a1a2e">
|
||||
<meta name="theme-color" content="#FFF">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Vyla Home">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="Vyla Home">
|
||||
<meta name="msapplication-TileColor" content="#1a1a2e">
|
||||
<meta name="msapplication-TileColor" content="#FFF">
|
||||
<meta name="msapplication-config" content="/browserconfig.xml">
|
||||
|
||||
<link rel="apple-touch-icon" href="favicon.ico">
|
||||
@@ -300,90 +300,104 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<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/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>
|
||||
<div class="container cta-banner-content reveal">
|
||||
<h2 id="cta-heading" class="cta-banner-title">Try Vyla Home today</h2>
|
||||
<p class="cta-banner-subtitle">Your Entertainment. Zero Servers Attached.</p>
|
||||
<a class="hero-cta hero-cta-light" id="cta-copy-script-btn" href="#" target="_blank"
|
||||
rel="noopener noreferrer">Download Now</a>
|
||||
</div>
|
||||
<div class="cta-footer-wrap">
|
||||
<div id="cta-rays" class="cta-rays-container" aria-hidden="true"></div>
|
||||
|
||||
<script>
|
||||
async function setupLatestDownload() {
|
||||
const btn = document.getElementById("hero-copy-script-btn");
|
||||
<section class="cta-banner" aria-labelledby="cta-heading">
|
||||
<div class="container cta-banner-content reveal">
|
||||
<h2 id="cta-heading" class="cta-banner-title">Try Vyla Home today</h2>
|
||||
<p class="cta-banner-subtitle">Your Entertainment. Zero Servers Attached.</p>
|
||||
<a class="hero-cta hero-cta-light" id="cta-copy-script-btn" href="#" target="_blank"
|
||||
rel="noopener noreferrer">Download Now</a>
|
||||
</div>
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/releases/permalink/latest"
|
||||
);
|
||||
<script>
|
||||
async function setupLatestDownload() {
|
||||
const buttons = [
|
||||
document.getElementById("hero-copy-script-btn"),
|
||||
document.getElementById("cta-copy-script-btn")
|
||||
];
|
||||
|
||||
if (!response.ok) throw new Error("Failed to fetch latest release");
|
||||
try {
|
||||
const project = "vyla-entertainment%2Fhome";
|
||||
|
||||
const release = await response.json();
|
||||
const version = release.tag_name;
|
||||
const fileVersion = version.replace(/^v/, "");
|
||||
const response = await fetch(
|
||||
`https://gitlab.com/api/v4/projects/${project}/releases/permalink/latest`
|
||||
);
|
||||
|
||||
btn.href =
|
||||
`https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/packages/generic/vyla-home/${version}/Vyla-Home_${fileVersion}_x64-setup.exe`;
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch latest release: ${response.status}`);
|
||||
}
|
||||
|
||||
btn.textContent = `Download Release ${version}`;
|
||||
} catch (error) {
|
||||
btn.textContent = "Download Latest Release";
|
||||
const release = await response.json();
|
||||
|
||||
const asset = release.assets?.links?.find(link =>
|
||||
link.name?.toLowerCase().endsWith(".exe")
|
||||
);
|
||||
|
||||
if (!asset?.url) {
|
||||
throw new Error("No .exe asset found in latest release");
|
||||
}
|
||||
|
||||
buttons.forEach(button => {
|
||||
if (button) {
|
||||
button.href = asset.url;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
buttons.forEach(button => {
|
||||
if (button) {
|
||||
button.textContent = "Download Latest Release";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupLatestDownload();
|
||||
</script>
|
||||
</section>
|
||||
setupLatestDownload();
|
||||
</script>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>Vyla</h4>
|
||||
<p>Vyla is a passion project born from a love for film and television.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Backup Site:</h4>
|
||||
<p><a href="https://vyla.qzz.io" target="_blank" rel="noopener">https://vyla.qzz.io</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Quick Links</h4>
|
||||
<a href="/terms-of-service">Terms of Service</a>
|
||||
<a href="/privacy-policy">Privacy Policy</a>
|
||||
<a href="/about-us">About Us</a>
|
||||
<a href="https://docs.vyla.cc/misc/dmca" target="_blank" rel="noopener">DMCA</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Our API</h4>
|
||||
<a href="https://docs.vyla.cc" target="_blank" rel="noopener">Documentation</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>Join our Discord</h4>
|
||||
<div class="social-links">
|
||||
<a href="/discord" target="_blank" rel="noopener">
|
||||
<i class="fab fa-discord"></i> Discord
|
||||
</a>
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h2>Vyla</h2>
|
||||
<p>Vyla is a passion project born from a love for film and television.</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Backup Site:</h2>
|
||||
<p><a href="https://vyla.qzz.io" target="_blank" rel="noopener">https://vyla.qzz.io</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Quick Links</h2>
|
||||
<a href="/terms-of-service">Terms of Service</a>
|
||||
<a href="/privacy-policy">Privacy Policy</a>
|
||||
<a href="/about-us">About Us</a>
|
||||
<a href="https://docs.vyla.cc/misc/dmca" target="_blank" rel="noopener">DMCA</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Our API</h2>
|
||||
<a href="https://docs.vyla.cc" target="_blank" rel="noopener">Documentation</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Join our Discord</h2>
|
||||
<div class="social-links">
|
||||
<a href="/discord" target="_blank" rel="noopener">
|
||||
<i class="fab fa-discord"></i> Discord
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<img src="https://app.greenweb.org/api/v3/greencheckimage/vyla.cc?nocache=true"
|
||||
alt="This website runs on green hosting - verified by thegreenwebfoundation.org" width="200"
|
||||
height="95" loading="lazy" decoding="async">
|
||||
</div>
|
||||
<img src="https://app.greenweb.org/api/v3/greencheckimage/vyla.cc?nocache=true"
|
||||
alt="This website runs on green hosting - verified by thegreenwebfoundation.org" width="200px"
|
||||
height="95px">
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<a href="/fuckinggodihatethisprojectijustwantoenditall" class="secret-footer">
|
||||
© 2025 Vyla Entertainment. All rights reserved.
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
<div class="footer-bottom">
|
||||
<a href="/fuckinggodihatethisprojectijustwantoenditall" class="secret-footer">
|
||||
© 2025 Vyla Entertainment. All rights reserved.
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/src/scripts/main.js"></script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user