mirror of
https://gitlab.com/vyla-entertainment/home-frontend.git
synced 2026-11-14 06:57:23 -05:00
w UI update?
This commit is contained in:
327
index.html
327
index.html
@@ -55,7 +55,8 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap">
|
||||
<link rel="stylesheet" href="src/css/main.css">
|
||||
|
||||
<script src="src/javascript/fetch.js"></script>
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
@@ -108,303 +109,47 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="body-ambient-bg" aria-hidden="true">
|
||||
<video class="body-ambient-video" id="body-ambient-video" autoplay muted loop playsinline preload="auto">
|
||||
<source src="https://www.pexels.com/download/video/38243003/" type="video/mp4">
|
||||
</video>
|
||||
<pre>
|
||||
yea i don't feel like having a ui for this but trust me Vyla is AWESOME
|
||||
|
||||
download vyla using these links:
|
||||
|
||||
for windows: <a id="windows-download" href="">loading...</a>
|
||||
for mac: <a id="mac-download" href="">loading...</a>
|
||||
for android: <a id="android-download" href="">loading...</a>
|
||||
</pre>
|
||||
|
||||
<pre>screenshots! </pre>
|
||||
<div class="gallery">
|
||||
<img src="assets\images\features\1.png" alt="Feature 1">
|
||||
<img src="assets\images\features\2.png" alt="Feature 2">
|
||||
<img src="assets\images\features\3.png" alt="Feature 3">
|
||||
<img src="assets\images\features\4.png" alt="Feature 4">
|
||||
<img src="assets\images\features\5.png" alt="Feature 5">
|
||||
<img src="assets\images\features\6.png" alt="Feature 6">
|
||||
</div>
|
||||
|
||||
<header role="banner">
|
||||
<nav class="nav" role="navigation" aria-label="Main navigation">
|
||||
<div class="nav-container">
|
||||
<div class="nav-links">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener noreferrer"
|
||||
class="nav-discord-link" aria-label="Join our Discord community">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path
|
||||
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.929 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.894.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.028zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#features">Features</a>
|
||||
<a href="#testimonials">Testimonials</a>
|
||||
<a href="#faq">FAQ</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<footer class="footer">
|
||||
<pre>thanks for reading :-P</pre>
|
||||
|
||||
<main role="main">
|
||||
<section class="hero" aria-labelledby="hero-title">
|
||||
<div class="hero-atmosphere" aria-hidden="true">
|
||||
<video class="hero-atmosphere-video" autoplay muted loop playsinline preload="auto">
|
||||
<source src="https://www.pexels.com/download/video/38243003/" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
<div class="container hero-container">
|
||||
<div class="logo-image">
|
||||
<img src="assets/images/header_logo.png" alt="Vyla Home Official Logo" width="100" height="100"
|
||||
fetchpriority="high" loading="eager" decoding="async">
|
||||
</div>
|
||||
<h1 id="hero-title" class="hero-title">Welcome to Vyla Entertainment</h1>
|
||||
<p class="hero-description">Vyla runs directly on your local workstation. By decentralizing stream
|
||||
resolution to your residential connection, it eliminates centralized cloud failures, datacenter IP
|
||||
blocks, and third-party tracking.</p>
|
||||
<links>
|
||||
<pre></pre>
|
||||
<a href="https://gitlab.com/vyla-entertainment/home">gitlab</a>
|
||||
<a href="https://discord.gg/5eqRhKeywG">discord</a>
|
||||
<pre></pre>
|
||||
<a href="https://docs.vyla.cc">docs</a>
|
||||
<pre></pre>
|
||||
we have a nice api so you can create your own Vyla.
|
||||
</links>
|
||||
</footer>
|
||||
|
||||
<div class="hero-cta-row">
|
||||
<div class="download-expandable" id="hero-download-expandable" aria-expanded="false">
|
||||
<button class="hero-cta hero-cta-dark" id="hero-download-btn" type="button" aria-haspopup="true"
|
||||
aria-controls="hero-download-options">
|
||||
<span class="download-btn-text">Download Latest</span>
|
||||
<svg class="dropdown-arrow" width="17" height="17" viewBox="0 0 24 24" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path d="M7 10l5 5 5-5z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="download-options" id="hero-download-options" aria-hidden="true">
|
||||
<a href="#" class="download-option" id="hero-windows-download"
|
||||
aria-label="Download for Windows">
|
||||
<svg class="windows-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M0 3.449L9.75 2.1v9.451H0m10.949-9.602L24 0v11.4H10.949M0 12.6h9.75v9.451L0 20.699M10.949 12.6H24V24l-12.949-1.951" />
|
||||
</svg>
|
||||
<span>Windows</span>
|
||||
</a>
|
||||
<a href="#" class="download-option" id="hero-go-download" aria-label="Download for Android">
|
||||
<svg class="android-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M17.6 9.2l1.5-2.6c.1-.2.1-.4-.1-.5-.2-.1-.4-.1-.5.1L17 8.8c-.9-.4-1.9-.6-3-.6s-2.1.2-3 .6L9.5 6.2c-.1-.2-.3-.2-.5-.1-.2.1-.2.3-.1.5l1.5 2.6C8.1 10.2 6.4 12.1 6 14.5h12c-.4-2.4-2.1-4.3-4.4-5.3zM9.5 13.2c-.4 0-.7-.3-.7-.7s.3-.7.7-.7.7.3.7.7-.3.7-.7.7zm5 0c-.4 0-.7-.3-.7-.7s.3-.7.7-.7.7.3.7.7-.3.7-.7.7zM6 15h12v3.5c0 .8-.7 1.5-1.5 1.5h-9c-.8 0-1.5-.7-1.5-1.5V15zm2 5v2c0 .6-.4 1-1 1s-1-.4-1-1v-2h2zm10 0v2c0 .6-.4 1-1 1s-1-.4-1-1v-2h2z" />
|
||||
</svg>
|
||||
<span>Android</span>
|
||||
</a>
|
||||
<a href="#" class="download-option" id="hero-mac-download" aria-label="Download for macOS">
|
||||
<svg class="mac-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" />
|
||||
</svg>
|
||||
<span>macOS</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a class="hero-cta hero-cta-unsure" href="https://gitlab.com/vyla-entertainment/home/-/releases"
|
||||
target="_blank" rel="noopener noreferrer">View Releases</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="stats-grid" id="stats-grid" aria-label="Community Statistics">
|
||||
<div class="stat-item">
|
||||
<span class="stat-value" id="stat-members">1,250</span>
|
||||
<span class="stat-label">Discord Members</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-value" id="stat-online">180</span>
|
||||
<span class="stat-label">Online Now</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="features" class="section features-section" aria-labelledby="features-heading">
|
||||
<div class="container features-container">
|
||||
<header class="features-heading">
|
||||
<h2 id="features-heading" class="section-title-lg">Why choose Home?</h2>
|
||||
</header>
|
||||
<article class="feature-block">
|
||||
<figure class="feature-media">
|
||||
<img src="assets/images/features/1.png"
|
||||
alt="Direct residential network bypass illustration avoiding datacenter throttling"
|
||||
width="800" height="450" loading="lazy" decoding="async">
|
||||
</figure>
|
||||
<div class="feature-copy">
|
||||
<h3 class="section-title-lg feature-title">No Datacenter Throttling</h3>
|
||||
<p class="section-subtitle">Shared hosting nodes and VPS providers routinely block traffic
|
||||
originating from datacenters. We removed the datacenter entirely. Connect directly via your
|
||||
residential network.</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="feature-block feature-block-reverse">
|
||||
<figure class="feature-media">
|
||||
<img src="assets/images/features/2.png"
|
||||
alt="Decentralized peer network architecture visualization" width="800" height="450"
|
||||
loading="lazy" decoding="async">
|
||||
</figure>
|
||||
<div class="feature-copy">
|
||||
<h3 class="section-title-lg feature-title">No Single Point of Failure</h3>
|
||||
<p class="section-subtitle">When a central domain gets flagged or suspended, everyone goes dark.
|
||||
Running locally means your instance cannot be taken down by third parties.</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="feature-block">
|
||||
<figure class="feature-media">
|
||||
<img src="assets/images/features/3.png"
|
||||
alt="Encrypted local database vault with cryptographic hardware key" width="800"
|
||||
height="450" loading="lazy" decoding="async">
|
||||
</figure>
|
||||
<div class="feature-copy">
|
||||
<h3 class="section-title-lg feature-title">Encrypted Local Vault</h3>
|
||||
<p class="section-subtitle">Your bookmarks, watch history, settings, and tokens are stored in an
|
||||
encrypted vault keyed uniquely to your hardware profile. Total privacy.</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="feature-block feature-block-reverse">
|
||||
<figure class="feature-media">
|
||||
<img src="assets/images/features/4.png"
|
||||
alt="Automated checksum-verified updates pipeline overview" width="800" height="450"
|
||||
loading="lazy" decoding="async">
|
||||
</figure>
|
||||
<div class="feature-copy">
|
||||
<h3 class="section-title-lg feature-title">Automated OTA Updates</h3>
|
||||
<p class="section-subtitle">Vyla Home verifies releases directly from the repository upon
|
||||
startup. Update assets are downloaded, checksum-verified, and patched automatically.</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="feature-block">
|
||||
<figure class="feature-media">
|
||||
<img src="assets/images/features/5.png"
|
||||
alt="Rich media discovery system with high dynamic range and 4K media" width="800"
|
||||
height="450" loading="lazy" decoding="async">
|
||||
</figure>
|
||||
<div class="feature-copy">
|
||||
<h3 class="section-title-lg feature-title">Rich Discovery & Curated Media</h3>
|
||||
<p class="section-subtitle">Access curated collections, 4K releases, popular anime series,
|
||||
soundcloak music streaming, and detailed TMDB cast breakdowns seamlessly.</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="feature-block feature-block-reverse">
|
||||
<figure class="feature-media">
|
||||
<img src="assets/images/features/6.png"
|
||||
alt="Customizable UI controls, theme engine, and playback preferences" width="800"
|
||||
height="450" loading="lazy" decoding="async">
|
||||
</figure>
|
||||
<div class="feature-copy">
|
||||
<h3 class="section-title-lg feature-title">Customizable Interface</h3>
|
||||
<p class="section-subtitle">Toggle dynamic color adaptations, trailer background playback,
|
||||
custom color themes, text-only mode, and layout preferences with full persistent storage.
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="testimonials" class="section testimonials-section" aria-labelledby="testimonials-heading">
|
||||
<header class="testimonials-heading">
|
||||
<h2 id="testimonials-heading" class="section-title-lg span">Here's what people say about <span
|
||||
class="section-title-lg">Vyla Home</span></h2>
|
||||
</header>
|
||||
<div class="testimonials-marquee-wrapper">
|
||||
<div class="testimonials-track" id="marquee-row-1"></div>
|
||||
<div class="testimonials-track testimonials-track-reverse" id="marquee-row-2"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="faq" class="section faq-section" aria-labelledby="faq-heading">
|
||||
<div class="container">
|
||||
<header class="faq-heading">
|
||||
<h2 id="faq-heading" class="section-title-lg">Frequently Asked Questions</h2>
|
||||
<p class="section-subtitle">Still have questions? <a href="https://discord.gg/5eqRhKeywG"
|
||||
class="section-link" target="_blank" rel="noopener noreferrer">Ask in our Discord</a></p>
|
||||
</header>
|
||||
<div class="faq-list" id="faq-list" role="region" aria-label="FAQ items"></div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="cta-footer-wrap">
|
||||
<section class="cta-banner" aria-labelledby="cta-heading">
|
||||
<div class="cta-atmosphere" aria-hidden="true">
|
||||
<video class="cta-atmosphere-video" autoplay muted loop playsinline preload="auto">
|
||||
<source src="https://www.pexels.com/download/video/38243003/" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
<div class="container cta-banner-content">
|
||||
<h2 id="cta-heading" class="cta-banner-title">Try Vyla Home today</h2>
|
||||
<p class="cta-banner-subtitle">Your Entertainment. Zero Servers Attached.</p>
|
||||
<div class="download-expandable" id="cta-download-expandable" aria-expanded="false">
|
||||
<button class="hero-cta hero-cta-light" id="cta-download-btn" type="button" aria-haspopup="true"
|
||||
aria-controls="cta-download-options">
|
||||
<span class="download-btn-text">Download Now</span>
|
||||
<svg class="dropdown-arrow" width="17" height="17" viewBox="0 0 24 24" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path d="M7 10l5 5 5-5z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="download-options" id="cta-download-options" aria-hidden="true">
|
||||
<a href="#" class="download-option" id="cta-windows-download" aria-label="Download for Windows">
|
||||
<svg class="windows-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M0 3.449L9.75 2.1v9.451H0m10.949-9.602L24 0v11.4H10.949M0 12.6h9.75v9.451L0 20.699M10.949 12.6H24V24l-12.949-1.951" />
|
||||
</svg>
|
||||
<span>Windows</span>
|
||||
</a>
|
||||
<a href="#" class="download-option" id="cta-mac-download" aria-label="Download for macOS">
|
||||
<svg class="mac-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" />
|
||||
</svg>
|
||||
<span>macOS</span>
|
||||
</a>
|
||||
<a href="#" class="download-option" id="cta-go-download" aria-label="Download for Android">
|
||||
<svg class="android-icon" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
|
||||
aria-hidden="true">
|
||||
<path
|
||||
d="M17.6 9.2l1.5-2.6c.1-.2.1-.4-.1-.5-.2-.1-.4-.1-.5.1L17 8.8c-.9-.4-1.9-.6-3-.6s-2.1.2-3 .6L9.5 6.2c-.1-.2-.3-.2-.5-.1-.2.1-.2.3-.1.5l1.5 2.6C8.1 10.2 6.4 12.1 6 14.5h12c-.4-2.4-2.1-4.3-4.4-5.3zM9.5 13.2c-.4 0-.7-.3-.7-.7s.3-.7.7-.7.7.3.7.7-.3.7-.7.7zm5 0c-.4 0-.7-.3-.7-.7s.3-.7.7-.7.7.3.7.7-.3.7-.7.7zM6 15h12v3.5c0 .8-.7 1.5-1.5 1.5h-9c-.8 0-1.5-.7-1.5-1.5V15zm2 5v2c0 .6-.4 1-1 1s-1-.4-1-1v-2h2zm10 0v2c0 .6-.4 1-1 1s-1-.4-1-1v-2h2z" />
|
||||
</svg>
|
||||
<span>Android</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer" role="contentinfo">
|
||||
<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 noreferrer">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 noreferrer">DMCA</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Developers</h2>
|
||||
<a href="https://docs.vyla.cc" target="_blank" rel="noopener noreferrer">Documentation</a>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h2>Community</h2>
|
||||
<div class="social-links">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener noreferrer"
|
||||
aria-label="Official Discord Server">Discord</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-section footer-badge">
|
||||
<img src="https://app.greenweb.org/api/v3/greencheckimage/vyla.cc"
|
||||
alt="This website runs on green hosting verified by thegreenwebfoundation.org" width="200"
|
||||
height="95" loading="lazy" decoding="async">
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 Vyla Entertainment. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script type="module" src="src/js/main.js"></script>
|
||||
</body>
|
||||
|
||||
<link rel="stylesheet" href="/src/styling/grid.css">
|
||||
<link rel="stylesheet" href="/src/styling/footer.css">
|
||||
|
||||
</html>
|
||||
@@ -1,78 +0,0 @@
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family-base);
|
||||
background-color: var(--color-bg-base);
|
||||
color: var(--color-text-main);
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.body-ambient-bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: var(--z-ambient);
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.body-ambient-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
filter: blur(20px) brightness(1.2);
|
||||
opacity: 1;
|
||||
transition: opacity var(--transition-linear);
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: var(--space-7xl) var(--space-xl);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: var(--max-w-content);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.section-title-lg {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-regular);
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.section-title-lg span {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
color: var(--color-text-dim);
|
||||
margin-top: var(--space-xs);
|
||||
font-size: var(--font-size-lg);
|
||||
line-height: var(--line-height-base);
|
||||
}
|
||||
|
||||
.section-link {
|
||||
color: var(--color-accent);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.section-link:hover {
|
||||
color: var(--color-text-dim);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
.cta-banner {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding: var(--space-8xl) var(--space-xl);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cta-banner .download-expandable {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.cta-atmosphere {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: var(--z-base);
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
mask-image: linear-gradient(to bottom, transparent 0%, black 25%, black 75%, transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 25%, black 75%, transparent 100%);
|
||||
}
|
||||
|
||||
.cta-atmosphere-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.cta-banner-content {
|
||||
position: relative;
|
||||
z-index: var(--z-content);
|
||||
}
|
||||
|
||||
.cta-banner-title {
|
||||
font-size: clamp(2.1rem, 4vw, 3.2rem);
|
||||
font-weight: var(--font-weight-bold);
|
||||
letter-spacing: -0.035em;
|
||||
line-height: var(--line-height-mid);
|
||||
margin-bottom: var(--space-sm);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.cta-banner-subtitle {
|
||||
color: var(--color-text-dim);
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
.faq-heading {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-5xl);
|
||||
}
|
||||
|
||||
.faq-list {
|
||||
max-width: var(--max-w-faq);
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.faq-item {
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
border-top: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.faq-item:last-child {
|
||||
border-bottom: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--color-accent);
|
||||
padding: 1.4rem var(--space-2xs);
|
||||
font-family: inherit;
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
gap: var(--space-xl);
|
||||
transition: opacity var(--transition-normal);
|
||||
}
|
||||
|
||||
.faq-question:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.faq-chevron {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
flex-shrink: 0;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2.2;
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
transition: transform var(--transition-smooth);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.faq-item.active .faq-chevron {
|
||||
transform: rotate(180deg);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.faq-answer {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height var(--transition-smooth), padding var(--transition-smooth);
|
||||
padding: 0 var(--space-2xs);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--line-height-spacious);
|
||||
}
|
||||
|
||||
.faq-item.active .faq-answer {
|
||||
padding-bottom: 1.4rem;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
.features-heading {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-5xl);
|
||||
}
|
||||
|
||||
.feature-block {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
margin-bottom: var(--space-6xl);
|
||||
}
|
||||
|
||||
.feature-block-reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.feature-media {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.feature-media img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--radius-xl);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.feature-copy {
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.feature-block,
|
||||
.feature-block-reverse {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
.footer {
|
||||
position: relative;
|
||||
padding: var(--space-2xl) var(--space-xl) var(--space-xl);
|
||||
overflow: visible;
|
||||
background: transparent;
|
||||
z-index: var(--z-content);
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
max-width: var(--max-w-footer);
|
||||
margin: 0 auto var(--space-2xl);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: var(--space-3xl);
|
||||
font-weight: var(--font-weight-regular);
|
||||
position: relative;
|
||||
z-index: var(--z-footer);
|
||||
}
|
||||
|
||||
.footer-section h2 {
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--space-sm);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.footer-section p {
|
||||
color: var(--color-text-dim);
|
||||
font-size: 14px;
|
||||
line-height: var(--line-height-relaxed);
|
||||
}
|
||||
|
||||
.footer-section a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-text-dim);
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
transition: color var(--transition-normal);
|
||||
}
|
||||
|
||||
.footer-section a:hover {
|
||||
color: var(--color-text-main);
|
||||
}
|
||||
|
||||
.social-links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
text-align: center;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-dim);
|
||||
position: relative;
|
||||
z-index: var(--z-footer);
|
||||
padding-top: var(--space-xl);
|
||||
border-top: 1px solid var(--color-border-subtle);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
.glass-surface {
|
||||
position: relative !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
overflow: hidden !important;
|
||||
text-decoration: none !important;
|
||||
box-sizing: border-box !important;
|
||||
width: auto !important;
|
||||
max-width: max-content !important;
|
||||
transition: opacity var(--glass-transition-duration) ease-out;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.glass-surface.dark-mode:focus-visible {
|
||||
outline: 2px solid var(--glass-focus-blue-dark);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.glass-surface.light-mode:focus-visible {
|
||||
outline: 2px solid var(--glass-focus-blue-light);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.glass-surface__filter {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
z-index: -10;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.glass-surface__content {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: inherit;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: inherit;
|
||||
white-space: nowrap;
|
||||
border-radius: inherit;
|
||||
pointer-events: auto;
|
||||
box-shadow: none !important;
|
||||
padding: var(--glass-content-padding);
|
||||
}
|
||||
157
src/css/hero.css
157
src/css/hero.css
@@ -1,157 +0,0 @@
|
||||
.hero {
|
||||
position: relative;
|
||||
min-height: 85vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: var(--space-6xl);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.hero-atmosphere {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: var(--z-base);
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
mask-image: linear-gradient(to bottom, black 0%, black 50%, transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(to bottom, black 0%, black 50%, transparent 100%);
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.hero-atmosphere-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.hero-container {
|
||||
position: relative;
|
||||
z-index: var(--z-content);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
max-width: var(--max-w-hero);
|
||||
padding: var(--space-2xl) var(--space-xl);
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: clamp(2.2rem, 4vw, 3.5rem);
|
||||
font-weight: var(--font-weight-bold);
|
||||
letter-spacing: -0.035em;
|
||||
line-height: var(--line-height-tight);
|
||||
margin: var(--space-lg) 0 var(--space-md);
|
||||
color: var(--color-accent);
|
||||
white-space: pre-wrap;
|
||||
overflow: visible;
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.blur-char {
|
||||
display: inline-block;
|
||||
animation: blurIn 0.4s ease-out forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.blur-char.is-space {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes blurIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
filter: blur(8px);
|
||||
transform: translateY(5px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
filter: blur(0);
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.hero-description {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-dim);
|
||||
line-height: var(--line-height-relaxed);
|
||||
max-width: 680px;
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
|
||||
.hero-cta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
margin-bottom: var(--space-3xl);
|
||||
}
|
||||
|
||||
.download-expandable {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
padding: 0.85rem 1.75rem;
|
||||
font-size: var(--font-size-btn);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
border-radius: var(--radius-pill);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
max-width: 200px;
|
||||
color: var(--color-accent);
|
||||
background-color: transparent;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.download-options {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + var(--space-xs));
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
flex-direction: column;
|
||||
background: rgba(0, 0, 0, 0.65);
|
||||
backdrop-filter: blur(20px) saturate(2.5) brightness(1.2);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(2.5) brightness(1.2);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
z-index: var(--z-dropdown);
|
||||
width: max-content;
|
||||
min-width: 175px;
|
||||
}
|
||||
|
||||
.download-options.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.download-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
color: var(--color-text-card);
|
||||
text-decoration: none;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
transition: background var(--transition-fast), color var(--transition-fast);
|
||||
}
|
||||
|
||||
.download-option:hover {
|
||||
color: var(--color-accent);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
@import './variables.css';
|
||||
@import './base.css';
|
||||
@import './glass-surface.css';
|
||||
@import './nav.css';
|
||||
@import './hero.css';
|
||||
@import './stats.css';
|
||||
@import './features.css';
|
||||
@import './testimonials.css';
|
||||
@import './faq.css';
|
||||
@import './cta.css';
|
||||
@import './footer.css';
|
||||
@@ -1,56 +0,0 @@
|
||||
header[role="banner"] {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: var(--z-nav);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: var(--space-lg) var(--space-md);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.nav {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xl);
|
||||
border-radius: var(--radius-pill);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-lg);
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: var(--color-accent);
|
||||
text-decoration: none;
|
||||
font-size: var(--font-size-btn);
|
||||
font-weight: var(--font-weight-medium);
|
||||
opacity: 0.85;
|
||||
transition: opacity var(--transition-normal);
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.nav-discord-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-accent);
|
||||
opacity: 0.8;
|
||||
transition: opacity var(--transition-normal);
|
||||
margin-right: 0.35rem;
|
||||
}
|
||||
|
||||
.nav-discord-link:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
.content-wrapper {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.content-wrapper h2 {
|
||||
margin-top: 5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.content-wrapper h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
|
||||
.content-wrapper h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.content-wrapper p {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 200;
|
||||
color: var(--color-text-dim);
|
||||
}
|
||||
|
||||
.content-wrapper ul {
|
||||
list-style: disc;
|
||||
margin-left: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.content-wrapper li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.content-wrapper span {
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--space-4xl);
|
||||
padding: var(--space-4xl) var(--space-xl);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-black);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-dim);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stats-grid {
|
||||
gap: var(--space-xl);
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
.testimonials-section {
|
||||
padding: var(--space-7xl) 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.testimonials-heading {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-5xl);
|
||||
}
|
||||
|
||||
.testimonials-marquee-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-md);
|
||||
position: relative;
|
||||
width: 100%;
|
||||
mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
|
||||
-webkit-mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
|
||||
}
|
||||
|
||||
.testimonials-track {
|
||||
display: flex;
|
||||
gap: var(--space-md);
|
||||
width: max-content;
|
||||
animation: marquee-scroll 45s linear infinite;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.testimonials-track-reverse {
|
||||
animation: marquee-scroll-reverse 45s linear infinite;
|
||||
}
|
||||
|
||||
.testimonials-marquee-wrapper:hover .testimonials-track {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
@keyframes marquee-scroll {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes marquee-scroll-reverse {
|
||||
from {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.testimonial-card {
|
||||
border-radius: var(--radius-xl);
|
||||
padding: var(--space-xl);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-md);
|
||||
background-color: var(--color-card-bg);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
width: 320px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.testimonial-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.85rem;
|
||||
}
|
||||
|
||||
.testimonial-avatar {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: var(--radius-circle);
|
||||
object-fit: cover;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.testimonial-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.testimonial-name {
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-main);
|
||||
}
|
||||
|
||||
.testimonial-handle {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-dim);
|
||||
}
|
||||
|
||||
.testimonial-text {
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--line-height-base);
|
||||
color: var(--color-text-card);
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
:root {
|
||||
--color-bg-base: #000000;
|
||||
--color-text-main: #f5f5f7;
|
||||
--color-text-dim: #a1a1a6;
|
||||
--color-text-muted: #8b8b93;
|
||||
--color-text-card: #e4e4e7;
|
||||
--color-accent: #ffffff;
|
||||
--color-card-bg: rgba(5, 5, 5, 0.54);
|
||||
--color-border-subtle: rgba(255, 255, 255, 0.08);
|
||||
--color-border-hover: rgba(255, 255, 255, 0.15);
|
||||
--color-focus: #007aff;
|
||||
--color-focus-dark: #0a84ff;
|
||||
|
||||
--font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
--font-size-xs: 0.8rem;
|
||||
--font-size-sm: 0.85rem;
|
||||
--font-size-btn: 0.87rem;
|
||||
--font-size-base: 0.95rem;
|
||||
--font-size-md: 1rem;
|
||||
--font-size-lg: 1.05rem;
|
||||
--font-size-xl: 2.1rem;
|
||||
--font-size-2xl: 2.25rem;
|
||||
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
--font-weight-black: 800;
|
||||
|
||||
--line-height-tight: 1.12;
|
||||
--line-height-mid: 1.15;
|
||||
--line-height-base: 1.5;
|
||||
--line-height-relaxed: 1.6;
|
||||
--line-height-spacious: 1.65;
|
||||
|
||||
--space-2xs: 0.2rem;
|
||||
--space-xs: 0.5rem;
|
||||
--space-sm: 0.75rem;
|
||||
--space-md: 1rem;
|
||||
--space-lg: 1.25rem;
|
||||
--space-xl: 1.5rem;
|
||||
--space-2xl: 2rem;
|
||||
--space-3xl: 2.5rem;
|
||||
--space-4xl: 3rem;
|
||||
--space-5xl: 4rem;
|
||||
--space-6xl: 5rem;
|
||||
--space-7xl: 6rem;
|
||||
--space-8xl: 8rem;
|
||||
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 14px;
|
||||
--radius-lg: 18px;
|
||||
--radius-xl: 30px;
|
||||
--radius-pill: 9999px;
|
||||
--radius-circle: 50%;
|
||||
|
||||
--transition-fast: 0.15s ease;
|
||||
--transition-normal: 0.2s ease;
|
||||
--transition-smooth: 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--transition-linear: 0.1s linear;
|
||||
|
||||
--z-ambient: -2;
|
||||
--z-grid: -1;
|
||||
--z-base: 0;
|
||||
--z-content: 1;
|
||||
--z-footer: 2;
|
||||
--z-dropdown: 50;
|
||||
--z-nav: 1000;
|
||||
|
||||
--max-w-hero: 900px;
|
||||
--max-w-content: 1100px;
|
||||
--max-w-faq: 800px;
|
||||
--max-w-footer: 1400px;
|
||||
|
||||
--glass-focus-blue-dark: #0a84ff;
|
||||
--glass-focus-blue-light: #007aff;
|
||||
--glass-content-padding: 4px;
|
||||
--glass-transition-duration: 260ms;
|
||||
}
|
||||
88
src/javascript/fetch.js
Normal file
88
src/javascript/fetch.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const homeReleases = "https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/releases?per_page=100";
|
||||
const goReleases = "https://gitlab.com/api/v4/projects/vyla-entertainment%2Fgo/releases?per_page=100";
|
||||
|
||||
async function loadDownloads() {
|
||||
try {
|
||||
const [homeResponse, goResponse] = await Promise.all([
|
||||
fetch(homeReleases),
|
||||
fetch(goReleases)
|
||||
]);
|
||||
|
||||
if (!homeResponse.ok || !goResponse.ok) {
|
||||
throw new Error("Failed to fetch GitLab releases");
|
||||
}
|
||||
|
||||
const [homeReleasesData, goReleasesData] = await Promise.all([
|
||||
homeResponse.json(),
|
||||
goResponse.json()
|
||||
]);
|
||||
|
||||
let windowsUrl = null;
|
||||
let macUrl = null;
|
||||
let androidUrl = null;
|
||||
|
||||
for (const release of homeReleasesData) {
|
||||
const assets = release.assets?.links || [];
|
||||
|
||||
const windows = assets.find(asset =>
|
||||
/\.exe$/i.test(asset.name) ||
|
||||
/\.exe$/i.test(asset.url)
|
||||
);
|
||||
|
||||
const mac = assets.find(asset =>
|
||||
/\.dmg$/i.test(asset.name) ||
|
||||
/\.dmg$/i.test(asset.url)
|
||||
);
|
||||
|
||||
if (!windowsUrl && windows) {
|
||||
windowsUrl = windows.url;
|
||||
}
|
||||
|
||||
if (!macUrl && mac) {
|
||||
macUrl = mac.url;
|
||||
}
|
||||
|
||||
if (windowsUrl && macUrl) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const release of goReleasesData) {
|
||||
const assets = release.assets?.links || [];
|
||||
|
||||
const android = assets.find(asset =>
|
||||
/\.apk$/i.test(asset.name) ||
|
||||
/\.apk$/i.test(asset.url)
|
||||
);
|
||||
|
||||
if (android) {
|
||||
androidUrl = android.url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setDownload("windows-download", windowsUrl);
|
||||
setDownload("mac-download", macUrl);
|
||||
setDownload("android-download", androidUrl);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
setDownload("windows-download", null);
|
||||
setDownload("mac-download", null);
|
||||
setDownload("android-download", null);
|
||||
}
|
||||
}
|
||||
|
||||
function setDownload(id, url) {
|
||||
const link = document.getElementById(id);
|
||||
|
||||
if (url) {
|
||||
link.href = url;
|
||||
link.textContent = "click me";
|
||||
} else {
|
||||
link.removeAttribute("href");
|
||||
link.textContent = "Unavailable";
|
||||
}
|
||||
}
|
||||
|
||||
loadDownloads();
|
||||
@@ -1,23 +0,0 @@
|
||||
import { initAmbientSync } from './modules/ambient-sync.js';
|
||||
import { initReleases } from './modules/releases.js';
|
||||
import { initLiquidGlass } from './modules/glass-surface.js';
|
||||
import { initStats } from './modules/stats.js';
|
||||
import { initFAQ } from './modules/faq.js';
|
||||
import { initTestimonials } from './modules/testimonials.js';
|
||||
import { initTypewriter } from './modules/typewriter.js';
|
||||
|
||||
function initApp() {
|
||||
initAmbientSync();
|
||||
initReleases();
|
||||
initLiquidGlass();
|
||||
initStats();
|
||||
initFAQ();
|
||||
initTestimonials();
|
||||
initTypewriter();
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initApp, { once: true });
|
||||
} else {
|
||||
initApp();
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
export function initAmbientSync() {
|
||||
const heroVideo = document.querySelector('.hero-atmosphere-video');
|
||||
const ambientVideo = document.getElementById('body-ambient-video');
|
||||
|
||||
if (heroVideo && ambientVideo) {
|
||||
const syncVideos = () => {
|
||||
if (Math.abs(heroVideo.currentTime - ambientVideo.currentTime) > 0.25) {
|
||||
ambientVideo.currentTime = heroVideo.currentTime;
|
||||
}
|
||||
};
|
||||
heroVideo.addEventListener('timeupdate', syncVideos, { passive: true });
|
||||
|
||||
const matchPlayState = () => {
|
||||
if (!heroVideo.paused && ambientVideo.paused) ambientVideo.play().catch(() => { });
|
||||
if (heroVideo.paused && !ambientVideo.paused) ambientVideo.pause();
|
||||
};
|
||||
heroVideo.addEventListener('play', matchPlayState);
|
||||
heroVideo.addEventListener('pause', matchPlayState);
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
if (!ambientVideo) return;
|
||||
const scrollY = window.scrollY || window.pageYOffset;
|
||||
const fadeDistance = window.innerHeight * 1.5;
|
||||
const progress = Math.min(Math.max(scrollY / fadeDistance, 0), 1);
|
||||
const opacity = Math.cos(progress * (Math.PI / 2));
|
||||
ambientVideo.style.opacity = Math.max(opacity, 0).toFixed(3);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
handleScroll();
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
export async function initFAQ() {
|
||||
const faqContainer = document.getElementById('faq-list');
|
||||
if (!faqContainer) return;
|
||||
|
||||
const response = await fetch('/src/data/faq.json');
|
||||
const faqs = await response.json();
|
||||
|
||||
faqContainer.innerHTML = faqs.map((item, idx) => `
|
||||
<div class="faq-item">
|
||||
<button class="faq-question" type="button" aria-expanded="false" aria-controls="faq-ans-${idx}">
|
||||
<span>${item.q}</span>
|
||||
<svg class="faq-chevron" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="faq-answer" id="faq-ans-${idx}">
|
||||
<p>${item.a}</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
faqContainer.querySelectorAll('.faq-question').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const item = btn.closest('.faq-item');
|
||||
const answer = item.querySelector('.faq-answer');
|
||||
const isOpen = item.classList.contains('active');
|
||||
|
||||
faqContainer.querySelectorAll('.faq-item').forEach(other => {
|
||||
if (other !== item && other.classList.contains('active')) {
|
||||
other.classList.remove('active');
|
||||
other.querySelector('.faq-question').setAttribute('aria-expanded', 'false');
|
||||
other.querySelector('.faq-answer').style.maxHeight = null;
|
||||
}
|
||||
});
|
||||
|
||||
if (isOpen) {
|
||||
item.classList.remove('active');
|
||||
btn.setAttribute('aria-expanded', 'false');
|
||||
answer.style.maxHeight = null;
|
||||
} else {
|
||||
item.classList.add('active');
|
||||
btn.setAttribute('aria-expanded', 'true');
|
||||
answer.style.maxHeight = `${answer.scrollHeight}px`;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
class GlassSurface {
|
||||
constructor(element, config = {}) {
|
||||
this.el = element;
|
||||
this.config = Object.assign({
|
||||
borderWidth: 0.07,
|
||||
brightness: 50,
|
||||
opacity: 0,
|
||||
blur: 11,
|
||||
displace: 0,
|
||||
backgroundOpacity: 0,
|
||||
saturation: 1,
|
||||
distortionScale: -180,
|
||||
redOffset: 0,
|
||||
greenOffset: 0,
|
||||
blueOffset: 0,
|
||||
xChannel: 'R',
|
||||
yChannel: 'G',
|
||||
mixBlendMode: 'difference',
|
||||
className: '',
|
||||
style: {}
|
||||
}, config);
|
||||
|
||||
this.uniqueId = Math.random().toString(36).substring(2, 9).replace(/:/g, '-');
|
||||
this.filterId = `glass-filter-${this.uniqueId}`;
|
||||
this.redGradId = `red-grad-${this.uniqueId}`;
|
||||
this.blueGradId = `blue-grad-${this.uniqueId}`;
|
||||
this.isMounted = false;
|
||||
this.liquidGlassEnabled = true;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
supportsSVGFilters() {
|
||||
if (typeof window === 'undefined' || typeof document === 'undefined') return false;
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
if (ua.includes('firefox')) return false;
|
||||
const isIOS = /iphone|ipad|ipod/.test(ua);
|
||||
const isSafari = ua.includes('safari') && !ua.includes('chrome');
|
||||
const isWebkitOnly = ua.includes('webkit') && !ua.includes('chrome');
|
||||
if (isIOS || isSafari || isWebkitOnly) return false;
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.style.backdropFilter = `url(#${this.filterId})`;
|
||||
const supported = div.style.backdropFilter !== '';
|
||||
if (!supported) {
|
||||
div.style.cssText = `-webkit-backdrop-filter: url(#${this.filterId})`;
|
||||
return div.style.webkitBackdropFilter !== '' && div.style.webkitBackdropFilter !== undefined;
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
|
||||
supportsBackdropFilter() {
|
||||
return CSS.supports('backdrop-filter', 'blur(10px)');
|
||||
}
|
||||
|
||||
getIsDark() {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.isMounted = true;
|
||||
this.isDark = this.getIsDark();
|
||||
this.svgSupported = this.supportsSVGFilters();
|
||||
this.backdropSupported = this.supportsBackdropFilter();
|
||||
|
||||
this.darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
this.darkModeListener = (e) => {
|
||||
this.isDark = e.matches;
|
||||
this.updateStyles();
|
||||
};
|
||||
this.darkModeQuery.addEventListener('change', this.darkModeListener);
|
||||
|
||||
this.checkSettings();
|
||||
|
||||
this.el.classList.add('glass-surface');
|
||||
this.el.style.position = 'relative';
|
||||
this.el.style.transform = 'translateZ(0)';
|
||||
this.el.style.backfaceVisibility = 'hidden';
|
||||
this.el.style.perspective = '1000';
|
||||
this.el.style.contain = 'layout style paint';
|
||||
|
||||
this.applyBaseStyles();
|
||||
this.createContentWrapper();
|
||||
|
||||
if (this.liquidGlassEnabled) {
|
||||
this.applyGlassEffect();
|
||||
} else {
|
||||
this.applyFallback();
|
||||
}
|
||||
|
||||
this.setupResizeObserver();
|
||||
}
|
||||
|
||||
checkSettings() {
|
||||
try {
|
||||
const settings = localStorage.getItem('glassSettings');
|
||||
if (settings) {
|
||||
const parsed = JSON.parse(settings);
|
||||
this.liquidGlassEnabled = parsed.liquidGlassEnabled !== false;
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
applyBaseStyles() {
|
||||
const width = typeof this.config.width === 'number' ? `${this.config.width}px` : this.config.width;
|
||||
const height = typeof this.config.height === 'number' ? `${this.config.height}px` : this.config.height;
|
||||
|
||||
this.el.style.width = width || 'auto';
|
||||
this.el.style.height = height || 'auto';
|
||||
this.el.style.borderRadius = `${this.config.borderRadius}px`;
|
||||
this.el.style.setProperty('--glass-frost', this.config.backgroundOpacity);
|
||||
this.el.style.setProperty('--glass-saturation', this.config.saturation);
|
||||
|
||||
Object.assign(this.el.style, this.config.style);
|
||||
|
||||
if (this.config.className) {
|
||||
this.el.classList.add(...this.config.className.split(' '));
|
||||
}
|
||||
|
||||
this.el.classList.toggle('dark-mode', this.isDark);
|
||||
this.el.classList.toggle('light-mode', !this.isDark);
|
||||
}
|
||||
|
||||
createContentWrapper() {
|
||||
this.contentWrapper = document.createElement('div');
|
||||
this.contentWrapper.className = 'glass-surface__content';
|
||||
this.contentWrapper.style.cssText = `
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: inherit;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
`;
|
||||
|
||||
while (this.el.firstChild) {
|
||||
this.contentWrapper.appendChild(this.el.firstChild);
|
||||
}
|
||||
|
||||
this.el.appendChild(this.contentWrapper);
|
||||
}
|
||||
|
||||
applyGlassEffect() {
|
||||
const useBackdropOnly = this.config.useBackdropOnly === true;
|
||||
|
||||
if (useBackdropOnly || !this.svgSupported) {
|
||||
if (this.backdropSupported) {
|
||||
this.applyBackdropFallback();
|
||||
} else {
|
||||
this.applyBasicFallback();
|
||||
}
|
||||
} else if (this.svgSupported && this.backdropSupported) {
|
||||
this.applySVGFilter();
|
||||
} else if (this.backdropSupported) {
|
||||
this.applyBackdropFallback();
|
||||
} else {
|
||||
this.applyBasicFallback();
|
||||
}
|
||||
}
|
||||
|
||||
applySVGFilter() {
|
||||
this.el.style.willChange = 'backdrop-filter, transform';
|
||||
|
||||
const bgColor = this.isDark
|
||||
? `hsl(0 0% 0% / ${this.config.backgroundOpacity})`
|
||||
: `hsl(0 0% 100% / ${this.config.backgroundOpacity})`;
|
||||
|
||||
const shadow = this.isDark
|
||||
? '0 0 0 0.5px rgba(255, 255, 255, 0.1) inset, 0px 4px 16px rgba(0, 0, 0, 0.1), 0px 8px 24px rgba(0, 0, 0, 0.08)'
|
||||
: '0 0 0 0.5px rgba(255, 255, 255, 0.15) inset, 0px 4px 16px rgba(0, 0, 0, 0.06), 0px 8px 24px rgba(0, 0, 0, 0.04)';
|
||||
|
||||
this.el.style.background = bgColor;
|
||||
this.el.style.backdropFilter = `url(#${this.filterId}) saturate(${this.config.saturation})`;
|
||||
this.el.style.webkitBackdropFilter = `url(#${this.filterId}) saturate(${this.config.saturation})`;
|
||||
this.el.style.boxShadow = shadow;
|
||||
|
||||
this.createSVGFilter();
|
||||
}
|
||||
|
||||
createSVGFilter() {
|
||||
this.svgContainer = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
this.svgContainer.setAttribute("class", "glass-surface__filter");
|
||||
this.svgContainer.style.cssText = `
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
z-index: -10;
|
||||
pointer-events: none;
|
||||
transform: translateZ(0);
|
||||
`;
|
||||
|
||||
this.svgContainer.innerHTML = `
|
||||
<defs>
|
||||
<filter id="${this.filterId}" color-interpolation-filters="sRGB" x="0%" y="0%" width="100%" height="100%">
|
||||
<feImage ref="feImage" x="0" y="0" width="100%" height="100%" preserveAspectRatio="none" result="map" />
|
||||
<feDisplacementMap ref="dispRed" in="SourceGraphic" in2="map" id="redchannel" 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 ref="dispGreen" in="SourceGraphic" in2="map" id="greenchannel" 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 ref="dispBlue" in="SourceGraphic" in2="map" id="bluechannel" result="dispBlue" />
|
||||
<feColorMatrix in="dispBlue" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 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 ref="gaussianBlur" in="output" stdDeviation="0.7" />
|
||||
</filter>
|
||||
</defs>
|
||||
`;
|
||||
|
||||
this.el.appendChild(this.svgContainer);
|
||||
|
||||
this.feImage = this.svgContainer.querySelector('[ref="feImage"]');
|
||||
this.dispRed = this.svgContainer.querySelector('[ref="dispRed"]');
|
||||
this.dispGreen = this.svgContainer.querySelector('[ref="dispGreen"]');
|
||||
this.dispBlue = this.svgContainer.querySelector('[ref="dispBlue"]');
|
||||
this.gaussianBlur = this.svgContainer.querySelector('[ref="gaussianBlur"]');
|
||||
|
||||
this.updateFilterAttributes();
|
||||
requestAnimationFrame(() => this.updateDisplacementMap());
|
||||
}
|
||||
|
||||
updateFilterAttributes() {
|
||||
if (!this.dispRed || !this.dispGreen || !this.dispBlue || !this.gaussianBlur) return;
|
||||
|
||||
this.dispRed.setAttribute('scale', (this.config.distortionScale + this.config.redOffset).toString());
|
||||
this.dispRed.setAttribute('xChannelSelector', this.config.xChannel);
|
||||
this.dispRed.setAttribute('yChannelSelector', this.config.yChannel);
|
||||
|
||||
this.dispGreen.setAttribute('scale', (this.config.distortionScale + this.config.greenOffset).toString());
|
||||
this.dispGreen.setAttribute('xChannelSelector', this.config.xChannel);
|
||||
this.dispGreen.setAttribute('yChannelSelector', this.config.yChannel);
|
||||
|
||||
this.dispBlue.setAttribute('scale', (this.config.distortionScale + this.config.blueOffset).toString());
|
||||
this.dispBlue.setAttribute('xChannelSelector', this.config.xChannel);
|
||||
this.dispBlue.setAttribute('yChannelSelector', this.config.yChannel);
|
||||
|
||||
this.gaussianBlur.setAttribute('stdDeviation', this.config.displace.toString());
|
||||
}
|
||||
|
||||
applyBackdropFallback() {
|
||||
this.el.style.willChange = 'transform';
|
||||
|
||||
if (this.isDark) {
|
||||
this.el.style.background = 'rgba(0, 0, 0, 0.3)';
|
||||
this.el.style.backdropFilter = 'blur(12px) saturate(1.8) brightness(1.1)';
|
||||
this.el.style.webkitBackdropFilter = 'blur(12px) saturate(1.8) brightness(1.1)';
|
||||
this.el.style.boxShadow = '0 0 0 0.5px rgba(255, 255, 255, 0.1) inset';
|
||||
} else {
|
||||
this.el.style.background = 'rgba(255, 255, 255, 0.1)';
|
||||
this.el.style.backdropFilter = 'blur(12px) saturate(1.8) brightness(1.05)';
|
||||
this.el.style.webkitBackdropFilter = 'blur(12px) saturate(1.8) brightness(1.05)';
|
||||
this.el.style.boxShadow = '0 0 0 0.5px rgba(255, 255, 255, 0.15) inset, 0px 4px 16px rgba(0, 0, 0, 0.04)';
|
||||
}
|
||||
}
|
||||
|
||||
applyBasicFallback() {
|
||||
this.el.style.willChange = 'transform';
|
||||
|
||||
if (this.isDark) {
|
||||
this.el.style.background = 'rgba(0, 0, 0, 0.5)';
|
||||
this.el.style.boxShadow = '0 0 0 0.5px rgba(255, 255, 255, 0.1) inset';
|
||||
} else {
|
||||
this.el.style.background = 'rgba(255, 255, 255, 0.2)';
|
||||
this.el.style.boxShadow = '0 0 0 0.5px rgba(255, 255, 255, 0.15) inset';
|
||||
}
|
||||
}
|
||||
|
||||
applyFallback() {
|
||||
if (this.backdropSupported) {
|
||||
this.applyBackdropFallback();
|
||||
} else {
|
||||
this.applyBasicFallback();
|
||||
}
|
||||
}
|
||||
|
||||
updateStyles() {
|
||||
if (!this.isMounted) return;
|
||||
|
||||
this.el.classList.toggle('dark-mode', this.isDark);
|
||||
this.el.classList.toggle('light-mode', !this.isDark);
|
||||
|
||||
if (this.liquidGlassEnabled && this.svgSupported && this.backdropSupported) {
|
||||
const bgColor = this.isDark
|
||||
? `hsl(0 0% 0% / ${this.config.backgroundOpacity})`
|
||||
: `hsl(0 0% 100% / ${this.config.backgroundOpacity})`;
|
||||
|
||||
const shadow = this.isDark
|
||||
? '0 0 0 0.5px rgba(255, 255, 255, 0.1) inset, 0px 4px 16px rgba(0, 0, 0, 0.1), 0px 8px 24px rgba(0, 0, 0, 0.08)'
|
||||
: '0 0 0 0.5px rgba(255, 255, 255, 0.15) inset, 0px 4px 16px rgba(0, 0, 0, 0.06), 0px 8px 24px rgba(0, 0, 0, 0.04)';
|
||||
|
||||
this.el.style.background = bgColor;
|
||||
this.el.style.boxShadow = shadow;
|
||||
} else {
|
||||
this.applyFallback();
|
||||
}
|
||||
}
|
||||
|
||||
setupResizeObserver() {
|
||||
if (!this.svgSupported) return;
|
||||
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
if ('requestIdleCallback' in window) {
|
||||
window.requestIdleCallback(() => this.updateDisplacementMap(), { timeout: 100 });
|
||||
} else {
|
||||
setTimeout(() => this.updateDisplacementMap(), 0);
|
||||
}
|
||||
});
|
||||
this.resizeObserver.observe(this.el);
|
||||
}
|
||||
|
||||
updateDisplacementMap() {
|
||||
if (!this.svgSupported || !this.feImage) return;
|
||||
|
||||
const rect = this.el.getBoundingClientRect();
|
||||
const actualWidth = Math.max(Math.round(rect.width) || 400, 10);
|
||||
const actualHeight = Math.max(Math.round(rect.height) || 200, 10);
|
||||
const edgeSize = Math.min(actualWidth, actualHeight) * (this.config.borderWidth * 0.5);
|
||||
|
||||
const svgContent = `
|
||||
<svg viewBox="0 0 ${actualWidth} ${actualHeight}" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="${this.redGradId}" x1="100%" y1="0%" x2="0%" y2="0%">
|
||||
<stop offset="0%" stop-color="#0000"/>
|
||||
<stop offset="100%" stop-color="red"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="${this.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="${actualWidth}" height="${actualHeight}" fill="black"></rect>
|
||||
<rect x="0" y="0" width="${actualWidth}" height="${actualHeight}" rx="${this.config.borderRadius}" fill="url(#${this.redGradId})" />
|
||||
<rect x="0" y="0" width="${actualWidth}" height="${actualHeight}" rx="${this.config.borderRadius}" fill="url(#${this.blueGradId})" style="mix-blend-mode: ${this.config.mixBlendMode}" />
|
||||
<rect x="${edgeSize}" y="${edgeSize}" width="${actualWidth - edgeSize * 2}" height="${actualHeight - edgeSize * 2}" rx="${this.config.borderRadius}" fill="hsl(0 0% ${this.config.brightness}% / ${this.config.opacity})" style="filter:blur(${this.config.blur}px)" />
|
||||
</svg>
|
||||
`;
|
||||
|
||||
this.feImage.setAttribute('href', `data:image/svg+xml,${encodeURIComponent(svgContent)}`);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.isMounted = false;
|
||||
|
||||
if (this.darkModeQuery && this.darkModeListener) {
|
||||
this.darkModeQuery.removeEventListener('change', this.darkModeListener);
|
||||
}
|
||||
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
|
||||
if (this.svgContainer && this.svgContainer.parentNode) {
|
||||
this.svgContainer.remove();
|
||||
}
|
||||
|
||||
if (this.contentWrapper && this.contentWrapper.parentNode) {
|
||||
while (this.contentWrapper.firstChild) {
|
||||
this.el.appendChild(this.contentWrapper.firstChild);
|
||||
}
|
||||
this.contentWrapper.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function applyGlassSurface(el, config) {
|
||||
if (!el || el.dataset.hasGlass || typeof GlassSurface !== 'function') return null;
|
||||
el.dataset.hasGlass = "true";
|
||||
const instance = new GlassSurface(el, config);
|
||||
el._glassSurfaceInstance = instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
export function destroyGlassSurface(el) {
|
||||
if (!el || !el._glassSurfaceInstance) return;
|
||||
el._glassSurfaceInstance.destroy();
|
||||
el._glassSurfaceInstance = null;
|
||||
delete el.dataset.hasGlass;
|
||||
}
|
||||
|
||||
export function initLiquidGlass(configOverride = {}) {
|
||||
const config = {
|
||||
borderWidth: 0.07,
|
||||
brightness: 50,
|
||||
opacity: 0.93,
|
||||
blur: configOverride.reducedBlur ? 6 : 11,
|
||||
displace: 0,
|
||||
backgroundOpacity: 0,
|
||||
saturation: 1,
|
||||
distortionScale: -180,
|
||||
redOffset: 0,
|
||||
greenOffset: 10,
|
||||
blueOffset: 20,
|
||||
xChannel: 'R',
|
||||
yChannel: 'G',
|
||||
mixBlendMode: 'difference'
|
||||
};
|
||||
|
||||
document.querySelectorAll('.hero-cta, .nav-container').forEach(el => applyGlassSurface(el, config));
|
||||
|
||||
if (!window._cardPlayBtnGlassObserver) {
|
||||
window._cardPlayBtnGlassObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
applyGlassSurface(entry.target, config);
|
||||
window._cardPlayBtnGlassObserver.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { rootMargin: '200px' });
|
||||
}
|
||||
|
||||
if (!window._sharedDomObserver) {
|
||||
window._sharedDomObserver = new MutationObserver((mutations) => {
|
||||
for (const m of mutations) {
|
||||
m.addedNodes.forEach(node => {
|
||||
if (node.nodeType !== 1) return;
|
||||
if (node.matches?.('.card-play-btn') && !node.dataset.hasGlass) {
|
||||
window._cardPlayBtnGlassObserver.observe(node);
|
||||
}
|
||||
node.querySelectorAll?.('.card-play-btn:not([data-has-glass])').forEach(el => {
|
||||
window._cardPlayBtnGlassObserver.observe(el);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
window._sharedDomObserver.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
document.querySelectorAll('.card-play-btn:not([data-has-glass])').forEach(el => {
|
||||
window._cardPlayBtnGlassObserver.observe(el);
|
||||
});
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
async function setupExpandableDownload(expandableId, btnId, optionsId, windowsId, goId, macId) {
|
||||
const expandable = document.getElementById(expandableId);
|
||||
const downloadBtn = document.getElementById(btnId);
|
||||
const downloadOptions = document.getElementById(optionsId);
|
||||
const windowsDownload = document.getElementById(windowsId);
|
||||
const goDownload = document.getElementById(goId);
|
||||
const macDownload = document.getElementById(macId);
|
||||
|
||||
if (!expandable || !downloadBtn || !downloadOptions || !windowsDownload || !goDownload || !macDownload) return;
|
||||
|
||||
try {
|
||||
const [homeResponse, goResponse] = await Promise.all([
|
||||
fetch("https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/releases/permalink/latest"),
|
||||
fetch("https://gitlab.com/api/v4/projects/vyla-entertainment%2Fgo/releases/permalink/latest")
|
||||
]);
|
||||
|
||||
if (!homeResponse.ok || !goResponse.ok) throw new Error();
|
||||
|
||||
const homeRelease = await homeResponse.json();
|
||||
const goRelease = await goResponse.json();
|
||||
|
||||
const version = homeRelease.tag_name;
|
||||
const goVersion = goRelease.tag_name;
|
||||
const fileVersion = version.replace(/^v/, "");
|
||||
const goFileVersion = goVersion.replace(/^v/, "");
|
||||
|
||||
const windowsAsset = homeRelease.assets?.links?.find(link => link.name?.toLowerCase().endsWith(".exe"));
|
||||
const macAsset = homeRelease.assets?.links?.find(link => link.name?.toLowerCase().endsWith(".dmg"));
|
||||
const goAsset = goRelease.assets?.links?.find(link => link.name?.toLowerCase().endsWith(".apk"));
|
||||
|
||||
const btnText = downloadBtn.querySelector(".download-btn-text");
|
||||
if (btnText) btnText.textContent = `Download ${version}`;
|
||||
|
||||
windowsDownload.href = windowsAsset?.url || `https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/packages/generic/vyla-home/${version}/Vyla-Home_${fileVersion}_x64-setup.exe`;
|
||||
windowsDownload.querySelector("span").textContent = `Windows ${version}`;
|
||||
|
||||
goDownload.href = goAsset?.url || `https://gitlab.com/api/v4/projects/vyla-entertainment%2Fgo/packages/generic/vyla-go/${goVersion}/vyla-go-${goFileVersion}.apk`;
|
||||
goDownload.querySelector("span").textContent = `Android ${goVersion}`;
|
||||
|
||||
macDownload.href = macAsset?.url || `https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/packages/generic/vyla-home/${version}/Vyla-Home_${fileVersion}_aarch64.dmg`;
|
||||
macDownload.querySelector("span").textContent = `macOS ${version}`;
|
||||
|
||||
downloadBtn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const isExpanded = expandable.getAttribute("aria-expanded") === "true";
|
||||
expandable.setAttribute("aria-expanded", String(!isExpanded));
|
||||
downloadOptions.setAttribute("aria-hidden", String(isExpanded));
|
||||
downloadOptions.classList.toggle("show", !isExpanded);
|
||||
});
|
||||
} catch {
|
||||
const btnText = downloadBtn.querySelector(".download-btn-text");
|
||||
if (btnText) btnText.textContent = "Download Latest";
|
||||
windowsDownload.querySelector("span").textContent = "Windows (Latest)";
|
||||
goDownload.querySelector("span").textContent = "Android (Latest)";
|
||||
macDownload.querySelector("span").textContent = "macOS (Latest)";
|
||||
}
|
||||
}
|
||||
|
||||
export function initReleases() {
|
||||
setupExpandableDownload("hero-download-expandable", "hero-download-btn", "hero-download-options", "hero-windows-download", "hero-go-download", "hero-mac-download");
|
||||
setupExpandableDownload("cta-download-expandable", "cta-download-btn", "cta-download-options", "cta-windows-download", "cta-go-download", "cta-mac-download");
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
const expandables = document.querySelectorAll(".download-expandable");
|
||||
expandables.forEach(expandable => {
|
||||
const btn = expandable.querySelector("button");
|
||||
const options = expandable.querySelector(".download-options");
|
||||
if (btn && options && !expandable.contains(e.target)) {
|
||||
expandable.setAttribute("aria-expanded", "false");
|
||||
options.setAttribute("aria-hidden", "true");
|
||||
options.classList.remove("show");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
function animateValue(el, target, duration = 1400) {
|
||||
const start = 0;
|
||||
const startTime = performance.now();
|
||||
function update(now) {
|
||||
const elapsed = now - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
const ease = 1 - Math.pow(1 - progress, 3);
|
||||
const current = Math.floor(start + (target - start) * ease);
|
||||
el.textContent = current.toLocaleString();
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(update);
|
||||
} else {
|
||||
el.textContent = target.toLocaleString();
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
export async function initStats() {
|
||||
const statMembers = document.getElementById('stat-members');
|
||||
const statOnline = document.getElementById('stat-online');
|
||||
if (!statMembers || !statOnline) return;
|
||||
|
||||
let memberCount = 1250;
|
||||
let onlineCount = 180;
|
||||
|
||||
try {
|
||||
const res = await fetch('https://discord.com/api/v10/invites/5eqRhKeywG?with_counts=true');
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (data.approximate_member_count) memberCount = data.approximate_member_count;
|
||||
if (data.approximate_presence_count) onlineCount = data.approximate_presence_count;
|
||||
}
|
||||
} catch { }
|
||||
|
||||
const observer = new IntersectionObserver((entries, obs) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
animateValue(statMembers, memberCount);
|
||||
animateValue(statOnline, onlineCount);
|
||||
obs.disconnect();
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.3 });
|
||||
|
||||
observer.observe(statMembers.closest('.stats-grid') || statMembers);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
export async function initTestimonials() {
|
||||
const row1 = document.getElementById('marquee-row-1');
|
||||
const row2 = document.getElementById('marquee-row-2');
|
||||
if (!row1 || !row2) return;
|
||||
|
||||
try {
|
||||
const res = await fetch('src/data/reviews.json');
|
||||
if (!res.ok) throw new Error();
|
||||
const reviews = await res.json();
|
||||
|
||||
const makeCard = (r) => `
|
||||
<article class="testimonial-card">
|
||||
<div class="testimonial-user">
|
||||
<img class="testimonial-avatar" src="${r.avatarSmall || r.avatar}" alt="${r.name}" width="44" height="44" loading="lazy" decoding="async">
|
||||
<div class="testimonial-info">
|
||||
<span class="testimonial-name">${r.name}</span>
|
||||
<span class="testimonial-handle">${r.handle}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="testimonial-text">${r.text}</p>
|
||||
</article>
|
||||
`;
|
||||
|
||||
const half = Math.ceil(reviews.length / 2);
|
||||
const set1 = reviews.slice(0, half);
|
||||
const set2 = reviews.slice(half);
|
||||
|
||||
const buildLoop = (items) => {
|
||||
const batch = items.map(makeCard).join('');
|
||||
return batch.repeat(4);
|
||||
};
|
||||
|
||||
row1.innerHTML = buildLoop(set1.length ? set1 : reviews);
|
||||
row2.innerHTML = buildLoop(set2.length ? set2 : reviews);
|
||||
} catch { }
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
export function initTypewriter() {
|
||||
const heroTitle = document.getElementById('hero-title');
|
||||
if (!heroTitle) return;
|
||||
|
||||
const originalText = heroTitle.textContent;
|
||||
heroTitle.textContent = '';
|
||||
|
||||
let charIndex = 0;
|
||||
const typingSpeed = 50;
|
||||
|
||||
function typeChar() {
|
||||
if (charIndex < originalText.length) {
|
||||
const char = originalText.charAt(charIndex);
|
||||
const span = document.createElement('span');
|
||||
span.textContent = char;
|
||||
|
||||
if (char === ' ') {
|
||||
span.className = 'blur-char is-space';
|
||||
} else {
|
||||
span.className = 'blur-char';
|
||||
}
|
||||
|
||||
heroTitle.appendChild(span);
|
||||
charIndex++;
|
||||
setTimeout(typeChar, typingSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(typeChar, 300);
|
||||
}
|
||||
11
src/styling/footer.css
Normal file
11
src/styling/footer.css
Normal file
@@ -0,0 +1,11 @@
|
||||
footer {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
footer .links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
11
src/styling/grid.css
Normal file
11
src/styling/grid.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
width: 50vw;
|
||||
}
|
||||
|
||||
.gallery img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
Reference in New Issue
Block a user