feat(website): adopt expressive material design
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import AppleIcon from "~icons/simple-icons/apple";
|
||||
import GooglePlayIcon from "~icons/simple-icons/googleplay";
|
||||
import LinuxIcon from "~icons/simple-icons/linux";
|
||||
import LinuxIcon from "~icons/devicon-plain/linux";
|
||||
import AmazonIcon from "~icons/cib/amazon";
|
||||
import ChevronDownIcon from "~icons/heroicons/chevron-down-solid";
|
||||
import WindowsIcon from "./WindowsIcon.svelte";
|
||||
@@ -133,80 +133,82 @@
|
||||
.download-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.store-buttons,
|
||||
.desktop-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.store-buttons {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.desktop-buttons {
|
||||
gap: 0.625rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.store-button,
|
||||
.desktop-button {
|
||||
display: inline-flex;
|
||||
min-height: 2.875rem;
|
||||
align-items: center;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
transition: background-color 150ms ease, color 150ms ease;
|
||||
justify-content: center;
|
||||
gap: 0.625rem;
|
||||
border-radius: var(--radius-pill);
|
||||
padding-inline: 1rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
transition:
|
||||
border-radius var(--motion-expressive) var(--ease-standard),
|
||||
color var(--motion-fast) var(--ease-standard),
|
||||
background-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.store-button {
|
||||
gap: 0.625rem;
|
||||
padding: 0.75rem 1.25rem;
|
||||
color: #111827;
|
||||
background: #fff;
|
||||
font-weight: 600;
|
||||
color: var(--color-on-primary);
|
||||
background: var(--color-text);
|
||||
}
|
||||
|
||||
.store-button:hover {
|
||||
background: #f3f4f6;
|
||||
.store-button:hover,
|
||||
.store-button:focus-visible {
|
||||
border-radius: var(--radius-md);
|
||||
background: #fff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.store-button :global(svg) {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
.desktop-button {
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid var(--color-border);
|
||||
background: color-mix(in srgb, var(--color-surface) 80%, transparent);
|
||||
color: var(--color-text-muted);
|
||||
background: var(--color-surface-highest);
|
||||
}
|
||||
|
||||
.desktop-button:hover,
|
||||
.desktop-button:focus-visible,
|
||||
.linux-button.active {
|
||||
color: var(--color-text);
|
||||
background: var(--color-surface-hover);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.desktop-button:hover,
|
||||
.desktop-button:focus-visible {
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.desktop-button :global(svg) {
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
opacity: 0.7;
|
||||
width: 0.9375rem;
|
||||
height: 0.9375rem;
|
||||
}
|
||||
|
||||
.linux-control {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.linux-button {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.chevron {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
transition: transform 300ms ease;
|
||||
transition: transform var(--motion-normal) var(--ease-standard);
|
||||
}
|
||||
|
||||
.chevron.open {
|
||||
@@ -216,55 +218,73 @@
|
||||
.chevron :global(svg) {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.linux-menu {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
bottom: calc(100% + 0.5rem);
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
width: 14rem;
|
||||
margin-bottom: 0.5rem;
|
||||
width: min(16rem, calc(100vw - 2rem));
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 1rem;
|
||||
background: var(--color-surface);
|
||||
box-shadow:
|
||||
0 20px 25px -5px rgb(0 0 0 / 0.1),
|
||||
0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 0.375rem;
|
||||
background: var(--color-surface-highest);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 150ms ease, visibility 150ms ease;
|
||||
transform: translateY(0.5rem);
|
||||
transition:
|
||||
opacity var(--motion-normal) var(--ease-standard),
|
||||
transform var(--motion-normal) var(--ease-standard),
|
||||
visibility var(--motion-normal) var(--ease-standard);
|
||||
}
|
||||
|
||||
.linux-menu.open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.linux-separator {
|
||||
border-top: 1px solid var(--color-border);
|
||||
height: var(--group-gap);
|
||||
margin-block: 0.375rem;
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
.linux-arch-label {
|
||||
padding: 0.625rem 1rem 0.25rem;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.025em;
|
||||
padding: 0.625rem 0.75rem 0.375rem;
|
||||
color: var(--color-text-subtle);
|
||||
font-family: var(--font-utility);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.linux-menu-item {
|
||||
display: block;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.625rem 0.75rem;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25rem;
|
||||
transition: background-color 150ms ease;
|
||||
transition:
|
||||
color var(--motion-fast) var(--ease-standard),
|
||||
background-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.linux-menu-item:hover {
|
||||
background: var(--color-surface-hover);
|
||||
.linux-menu-item:hover,
|
||||
.linux-menu-item:focus-visible {
|
||||
color: var(--color-text);
|
||||
background: rgb(237 237 237 / 0.12);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@media (max-width: 460px) {
|
||||
.store-button,
|
||||
.desktop-button {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
|
||||
<div class="faq-list">
|
||||
{#each faqs as faq, i}
|
||||
<ScrollReveal delay={i * 50}>
|
||||
<div id={faq.id} class="glass-card faq-card">
|
||||
<ScrollReveal delay={i * 50} class="faq-row">
|
||||
<div id={faq.id} class="flat-card faq-card">
|
||||
<button
|
||||
type="button"
|
||||
class="faq-toggle"
|
||||
@@ -72,118 +72,154 @@
|
||||
|
||||
<style>
|
||||
.faq-section {
|
||||
max-width: 64rem;
|
||||
width: min(100%, var(--page-width));
|
||||
margin-inline: auto;
|
||||
padding: 4rem 1.5rem;
|
||||
padding: clamp(4rem, 9vw, 8rem) var(--page-gutter);
|
||||
}
|
||||
|
||||
.section-label {
|
||||
margin-bottom: 0.75rem;
|
||||
color: var(--color-accent);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.025em;
|
||||
line-height: 1.25rem;
|
||||
text-transform: uppercase;
|
||||
width: fit-content;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: var(--radius-full);
|
||||
padding: 0.5rem 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
background: var(--color-surface);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
max-width: 12ch;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 2.25rem;
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(2.5rem, 7vw, 4.75rem);
|
||||
font-weight: 700;
|
||||
line-height: 2.5rem;
|
||||
letter-spacing: -0.045em;
|
||||
line-height: 1;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.section-description {
|
||||
max-width: 32rem;
|
||||
margin-bottom: 2.5rem;
|
||||
max-width: 34rem;
|
||||
margin-bottom: clamp(2.5rem, 6vw, 4.5rem);
|
||||
color: var(--color-text-muted);
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
font-size: clamp(1rem, 2vw, 1.125rem);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.faq-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.625rem;
|
||||
gap: var(--group-gap);
|
||||
}
|
||||
|
||||
.faq-card {
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-xs);
|
||||
background: var(--color-surface);
|
||||
transition: background-color var(--motion-normal) var(--ease-standard);
|
||||
}
|
||||
|
||||
.faq-card:has(.faq-toggle[aria-expanded="true"]) {
|
||||
background: var(--color-surface-high);
|
||||
}
|
||||
|
||||
:global(.faq-row:first-child) .faq-card {
|
||||
border-radius: var(--radius-lg) var(--radius-lg) var(--radius-xs) var(--radius-xs);
|
||||
}
|
||||
|
||||
:global(.faq-row:last-child) .faq-card {
|
||||
border-radius: var(--radius-xs) var(--radius-xs) var(--radius-lg) var(--radius-lg);
|
||||
}
|
||||
|
||||
.faq-toggle {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 4.5rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.5rem;
|
||||
padding: 0.75rem 1rem 0.75rem clamp(1rem, 4vw, 1.5rem);
|
||||
border-radius: inherit;
|
||||
text-align: left;
|
||||
transition: background-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.faq-toggle:not([aria-expanded="true"]):hover {
|
||||
background: rgb(237 237 237 / 0.08);
|
||||
}
|
||||
|
||||
.faq-toggle:focus-visible {
|
||||
background: rgb(237 237 237 / 0.14);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.faq-question {
|
||||
font-weight: 500;
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(0.9375rem, 2vw, 1.0625rem);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.faq-icon {
|
||||
display: flex;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
flex-shrink: 0;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
color: var(--color-accent);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-full);
|
||||
color: var(--color-text);
|
||||
background: rgb(237 237 237 / 0.1);
|
||||
transition:
|
||||
border-radius var(--motion-normal) var(--ease-standard),
|
||||
background-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.faq-toggle[aria-expanded="true"] .faq-icon {
|
||||
border-radius: var(--radius-md);
|
||||
background: rgb(237 237 237 / 0.16);
|
||||
}
|
||||
|
||||
.faq-icon :global(svg) {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
.faq-answer {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
transition: grid-template-rows 200ms ease;
|
||||
transition: grid-template-rows var(--motion-normal) var(--ease-standard);
|
||||
}
|
||||
|
||||
.faq-answer.open {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
.faq-answer > div {
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.faq-answer-content {
|
||||
padding: 0 1.5rem 1rem;
|
||||
max-width: 50rem;
|
||||
padding: 0 clamp(1rem, 4vw, 1.5rem) 1.5rem;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.625;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.faq-answer-content :global(a) {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.faq-answer-content :global(a:hover) {
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--color-text-subtle);
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.faq-section {
|
||||
padding-block: 6rem;
|
||||
}
|
||||
|
||||
.section-description {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.faq-section {
|
||||
padding-block: 8rem;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
.faq-answer-content :global(a:hover),
|
||||
.faq-answer-content :global(a:focus-visible) {
|
||||
text-decoration-color: var(--color-text);
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,48 +3,33 @@
|
||||
</script>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="footer-inner">
|
||||
<!-- Gradient rule -->
|
||||
<div class="gradient-rule"></div>
|
||||
|
||||
<div class="footer-content">
|
||||
<!-- Logo + name -->
|
||||
<div class="footer-brand">
|
||||
<span class="footer-logo"><Logo /></span>
|
||||
<span>Plezy</span>
|
||||
</div>
|
||||
|
||||
<!-- Links -->
|
||||
<nav class="footer-nav">
|
||||
<nav class="footer-nav" aria-label="Footer navigation">
|
||||
<a href="https://github.com/edde746/plezy" target="_blank" rel="noopener noreferrer">GitHub</a>
|
||||
<a href="/privacy">Privacy Policy</a>
|
||||
<a href="/privacy">Privacy</a>
|
||||
<a href="#home">Back to top</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
.site-footer {
|
||||
padding: 2.5rem 1.5rem;
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
max-width: 64rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.gradient-rule {
|
||||
height: 1px;
|
||||
margin-bottom: 2rem;
|
||||
background: linear-gradient(to right, transparent, var(--color-border), transparent);
|
||||
padding: clamp(3rem, 7vw, 5rem) var(--page-gutter);
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: flex;
|
||||
width: min(100%, var(--page-width));
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 2rem;
|
||||
gap: 1.5rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
@@ -52,42 +37,43 @@
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 500;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.footer-logo :global(svg) {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.footer-nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
gap: 0.25rem;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.footer-nav a {
|
||||
transition: color 150ms ease;
|
||||
border-radius: var(--radius-full);
|
||||
padding: 0.625rem 0.875rem;
|
||||
transition:
|
||||
color var(--motion-fast) var(--ease-standard),
|
||||
background-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.footer-nav a:hover {
|
||||
.footer-nav a:hover,
|
||||
.footer-nav a:focus-visible {
|
||||
color: var(--color-text);
|
||||
background: rgb(237 237 237 / 0.12);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.site-footer {
|
||||
padding-block: 4rem;
|
||||
}
|
||||
|
||||
.gradient-rule {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,65 +6,74 @@
|
||||
</script>
|
||||
|
||||
<section id="home" class="hero-section">
|
||||
<!-- Giant background logo watermark -->
|
||||
<div class="hero-watermark" aria-hidden="true">
|
||||
<Logo gradient={false} />
|
||||
</div>
|
||||
|
||||
<!-- Ambient glow -->
|
||||
<div class="ambient-glow"></div>
|
||||
|
||||
<!-- Content: two-column on desktop -->
|
||||
<div class="hero-content">
|
||||
<!-- Left: text + buttons -->
|
||||
<div class="hero-shell">
|
||||
<div class="hero-grid">
|
||||
<div class="hero-copy">
|
||||
<div class="brand-row fade-in" style="animation-delay: 0ms;">
|
||||
<div class="brand-row fade-in">
|
||||
<span class="brand-logo"><Logo /></span>
|
||||
<span class="brand-name">Plezy</span>
|
||||
<span>Plezy</span>
|
||||
</div>
|
||||
|
||||
<h1
|
||||
class="hero-title fade-in"
|
||||
style="animation-delay: 100ms;"
|
||||
>
|
||||
<span class="title-line">A beautiful client for</span>
|
||||
<span class="title-line gradient-text">Plex & Jellyfin</span>
|
||||
<h1 class="hero-title fade-in" style="animation-delay: 60ms;">
|
||||
A beautiful client for <span>Plex & Jellyfin</span>
|
||||
</h1>
|
||||
|
||||
<p
|
||||
class="hero-description fade-in"
|
||||
style="animation-delay: 200ms;"
|
||||
>
|
||||
Stream your media library with HDR, Dolby Vision, offline downloads, and a gorgeous experience across all your
|
||||
devices.
|
||||
<p class="hero-description fade-in" style="animation-delay: 120ms;">
|
||||
Stream your media library with HDR, Dolby Vision, offline downloads, and an experience built for every
|
||||
screen.
|
||||
</p>
|
||||
|
||||
<div class="fade-in" style="animation-delay: 300ms;">
|
||||
<div id="download" class="download-area fade-in" style="animation-delay: 180ms;">
|
||||
<DownloadButtons />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: stacked phone mockup -->
|
||||
<div class="phone-stage fade-in" style="animation-delay: 400ms;">
|
||||
<div class="phone-stack">
|
||||
<!-- Shadow phone behind -->
|
||||
<div class="shadow-phone">
|
||||
<div class="hero-visual fade-in" style="animation-delay: 240ms;">
|
||||
<div class="shape-field" aria-hidden="true">
|
||||
<svg
|
||||
class="hero-shape arch-shape"
|
||||
viewBox="0 0 1 1"
|
||||
preserveAspectRatio="none"
|
||||
data-material-shape="Arch"
|
||||
>
|
||||
<path
|
||||
d="M0.146447 0.146447C0.236929 0.055964 0.361929 0 0.5 0C0.776142 0 1 0.223858 1 0.5C1 0.619526 1 0.739052 1 0.858579C1 0.936684 0.936683 1 0.858579 1C0.619526 1 0.380474 1 0.141421 1C0.063317 1 0 0.936683 0 0.858579C0 0.739052 0 0.619526 0 0.5C0 0.361929 0.055964 0.236929 0.146447 0.146447Z"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
class="hero-shape cookie-shape"
|
||||
viewBox="0 0 1 1"
|
||||
data-material-shape="Cookie6Sided"
|
||||
>
|
||||
<path
|
||||
d="M0.716525 0.8729C0.699648 0.882624 0.683956 0.894405 0.669829 0.908026C0.669329 0.908507 0.66883 0.908989 0.66833 0.90947C0.574432 1 0.425694 0.999869 0.331956 0.909174C0.303749 0.881883 0.269272 0.861938 0.231554 0.851089C0.230887 0.850897 0.23022 0.850705 0.229553 0.850514C0.104204 0.81446 0.029948 0.685584 0.061623 0.559056C0.071154 0.520984 0.071189 0.481153 0.061725 0.443063C0.061558 0.44239 0.06139 0.441717 0.061223 0.441043C0.029771 0.31446 0.104254 0.185715 0.229667 0.149882C0.267404 0.1391 0.301917 0.119215 0.330171 0.091974C0.330671 0.091493 0.33117 0.091011 0.331669 0.09053C0.425568 0 0.574306 0.000131 0.668044 0.090826C0.696251 0.118116 0.730728 0.138062 0.768446 0.148911C0.769113 0.149103 0.76978 0.149294 0.770446 0.149486C0.895796 0.18554 0.970052 0.314416 0.938377 0.440944C0.928846 0.479016 0.928811 0.518847 0.938275 0.556937C0.938442 0.55761 0.93861 0.558283 0.938777 0.558957C0.970229 0.685539 0.895746 0.814285 0.770333 0.850118C0.751464 0.855509 0.733402 0.863176 0.716525 0.8729Z"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
class="hero-shape diamond-shape"
|
||||
viewBox="0 0 1 1"
|
||||
data-material-shape="Diamond"
|
||||
>
|
||||
<path
|
||||
d="M0.5 1C0.472713 1 0.445426 0.992561 0.421304 0.977684C0.398028 0.963329 0.371729 0.929255 0.319131 0.861106C0.252 0.774128 0.184869 0.68715 0.117738 0.600171C0.073881 0.543348 0.073881 0.46409 0.117738 0.407267C0.184869 0.320289 0.252 0.233311 0.319131 0.146333C0.371729 0.078184 0.398028 0.044109 0.421304 0.029754C0.469549 0 0.530451 0 0.578696 0.029754C0.601972 0.044109 0.628271 0.078184 0.680869 0.146333C0.748 0.233311 0.815131 0.320289 0.882262 0.407267C0.926119 0.46409 0.926119 0.543348 0.882262 0.600171C0.815131 0.68715 0.748 0.774128 0.680869 0.861106C0.628271 0.929255 0.601972 0.963329 0.578696 0.977684C0.554573 0.992561 0.527287 1 0.5 1Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="phone secondary-phone">
|
||||
<enhanced:img
|
||||
src={libraryImage}
|
||||
alt=""
|
||||
alt="Plezy library screen on phone"
|
||||
class="phone-image"
|
||||
loading="lazy"
|
||||
sizes="(min-width: 768px) 300px, (min-width: 640px) 280px, 224px"
|
||||
loading="eager"
|
||||
sizes="(min-width: 1024px) 250px, (min-width: 640px) 34vw, 42vw"
|
||||
/>
|
||||
</div>
|
||||
<!-- Main phone -->
|
||||
<div class="main-phone">
|
||||
<div class="phone primary-phone">
|
||||
<enhanced:img
|
||||
src={homeImage}
|
||||
alt="Plezy home screen"
|
||||
alt="Plezy home screen on phone"
|
||||
class="phone-image"
|
||||
fetchpriority="high"
|
||||
sizes="(min-width: 768px) 300px, (min-width: 640px) 280px, 224px"
|
||||
sizes="(min-width: 1024px) 280px, (min-width: 640px) 38vw, 48vw"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,196 +83,159 @@
|
||||
|
||||
<style>
|
||||
.hero-section {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 100dvh;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
overflow-x: clip;
|
||||
padding-inline: var(--page-gutter);
|
||||
}
|
||||
|
||||
.hero-watermark {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: -5rem;
|
||||
width: 37.5rem;
|
||||
transform: translateY(-50%);
|
||||
opacity: 0.04;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.hero-watermark :global(svg) {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.ambient-glow {
|
||||
position: absolute;
|
||||
top: 33.3333%;
|
||||
right: 25%;
|
||||
width: 31.25rem;
|
||||
height: 31.25rem;
|
||||
border-radius: 9999px;
|
||||
background: color-mix(in srgb, var(--color-accent) 10%, transparent);
|
||||
filter: blur(120px);
|
||||
animation: pulse-glow 3s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
max-width: 80rem;
|
||||
.hero-shell {
|
||||
width: min(100%, var(--page-width));
|
||||
margin-inline: auto;
|
||||
padding: 4rem 1.5rem;
|
||||
gap: 3rem;
|
||||
padding-block: clamp(3rem, 8vw, 7rem) clamp(5rem, 10vw, 9rem);
|
||||
}
|
||||
|
||||
.hero-grid {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
gap: clamp(4rem, 9vw, 8rem);
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
max-width: 36rem;
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fade-in-up 0.6s ease-out both;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.brand-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.25rem;
|
||||
margin-bottom: clamp(2rem, 5vw, 3rem);
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.brand-logo :global(svg) {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.brand-name {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 2.25rem;
|
||||
line-height: 1.05;
|
||||
max-width: 10ch;
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(3rem, 10vw, 5.75rem);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.025em;
|
||||
letter-spacing: -0.055em;
|
||||
line-height: 0.98;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.title-line {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
color: transparent;
|
||||
background: linear-gradient(to right, var(--color-accent-dim), var(--color-accent));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
.hero-title span {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.hero-description {
|
||||
max-width: 28rem;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
max-width: 31rem;
|
||||
margin-top: 1.5rem;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 1rem;
|
||||
line-height: 1.625;
|
||||
font-size: clamp(1rem, 2vw, 1.125rem);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.phone-stage {
|
||||
.download-area {
|
||||
margin-top: 2rem;
|
||||
scroll-margin-top: 2rem;
|
||||
}
|
||||
|
||||
.hero-visual {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
display: flex;
|
||||
width: min(100%, 35rem);
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: clamp(0.5rem, 2vw, 1.25rem);
|
||||
margin-inline: auto;
|
||||
padding: 0.5rem 0 2.5rem;
|
||||
}
|
||||
|
||||
.phone-stack {
|
||||
position: relative;
|
||||
width: 14rem;
|
||||
}
|
||||
|
||||
.shadow-phone {
|
||||
.shape-field {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
left: -2rem;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border: 1px solid color-mix(in srgb, var(--color-border) 30%, transparent);
|
||||
border-radius: 2.5rem;
|
||||
opacity: 0.4;
|
||||
filter: blur(1px);
|
||||
transform: rotate(-4deg);
|
||||
z-index: 0;
|
||||
inset: -1.5rem -1.5rem -1rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.main-phone {
|
||||
.hero-shape {
|
||||
position: absolute;
|
||||
display: block;
|
||||
fill: currentColor;
|
||||
opacity: 0.82;
|
||||
}
|
||||
|
||||
.arch-shape {
|
||||
top: -4%;
|
||||
right: -12%;
|
||||
width: 78%;
|
||||
height: 96%;
|
||||
color: var(--color-surface-high);
|
||||
transform: rotate(4deg);
|
||||
}
|
||||
|
||||
.cookie-shape {
|
||||
top: 6%;
|
||||
left: -10%;
|
||||
width: clamp(11rem, 32vw, 17rem);
|
||||
color: color-mix(in srgb, var(--color-brand) 10%, var(--color-surface-high));
|
||||
transform: rotate(-10deg);
|
||||
}
|
||||
|
||||
.diamond-shape {
|
||||
bottom: -8%;
|
||||
left: -10%;
|
||||
width: clamp(10rem, 28vw, 15rem);
|
||||
aspect-ratio: 1;
|
||||
color: var(--color-surface-highest);
|
||||
transform: rotate(-18deg);
|
||||
}
|
||||
|
||||
|
||||
.phone {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
border: 2px solid color-mix(in srgb, var(--color-border) 60%, transparent);
|
||||
border-radius: 2.5rem;
|
||||
box-shadow: 0 0 80px -20px color-mix(in srgb, var(--color-accent) 20%, transparent);
|
||||
border-radius: clamp(1.75rem, 4vw, 2.75rem);
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.secondary-phone {
|
||||
width: 44%;
|
||||
margin-top: clamp(3rem, 8vw, 6rem);
|
||||
transform: rotate(-2.5deg);
|
||||
}
|
||||
|
||||
.primary-phone {
|
||||
width: 50%;
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
|
||||
.phone :global(picture),
|
||||
.phone-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.hero-content {
|
||||
padding-block: 6rem;
|
||||
.fade-in {
|
||||
animation: fade-in-up var(--motion-expressive) var(--ease-standard) both;
|
||||
}
|
||||
|
||||
.brand-row {
|
||||
margin-bottom: 2rem;
|
||||
@media (min-width: 960px) {
|
||||
.hero-grid {
|
||||
grid-template-columns: minmax(28rem, 1fr) minmax(28rem, 0.9fr);
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.hero-description {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 2.5rem;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.phone-stack {
|
||||
width: 17.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.hero-watermark {
|
||||
width: 50rem;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 3.75rem;
|
||||
}
|
||||
|
||||
.phone-stack {
|
||||
width: 18.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.hero-watermark {
|
||||
width: 62.5rem;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
padding-inline: 3rem;
|
||||
gap: 6rem;
|
||||
}
|
||||
|
||||
.phone-stage {
|
||||
justify-content: flex-end;
|
||||
.hero-visual {
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import ScrollReveal from "./ScrollReveal.svelte";
|
||||
import AppleIcon from "~icons/simple-icons/apple";
|
||||
import GooglePlayIcon from "~icons/simple-icons/googleplay";
|
||||
import RedditIcon from "~icons/fa6-brands/reddit-alien";
|
||||
import ArrowUpIcon from "~icons/heroicons/arrow-up-solid";
|
||||
import StarIcon from "~icons/heroicons/star-solid";
|
||||
import ChevronLeftIcon from "~icons/heroicons/chevron-left-solid";
|
||||
import ChevronRightIcon from "~icons/heroicons/chevron-right-solid";
|
||||
|
||||
type SourceIcon = typeof AppleIcon | typeof GooglePlayIcon | typeof RedditIcon;
|
||||
|
||||
@@ -78,8 +79,8 @@
|
||||
upvotes: 50,
|
||||
},
|
||||
{
|
||||
text: "If you're having troubles with the default plex app just ditch it and switch to this. Plezy is faster, and easier to use. Worth the $5",
|
||||
name: "boyknight53",
|
||||
text: "Design and theme are much better. Love having the buffer size setting. Clean and intuitive. Single developer app that is consistently updated, thank you for your hard work.",
|
||||
name: "Corrykid",
|
||||
source: "App Store",
|
||||
icon: AppleIcon,
|
||||
},
|
||||
@@ -96,10 +97,10 @@
|
||||
icon: GooglePlayIcon,
|
||||
},
|
||||
{
|
||||
text: "College student here this is a godsend. Cannot recommend enough. For mediaheads on the go this is the best $4 you could ever spend.",
|
||||
name: "djmeowmeow",
|
||||
source: "App Store",
|
||||
icon: AppleIcon,
|
||||
text: "Perfect. I'm so excited for the development of this app. It's exactly what Plex should have been a decade ago. The client-side MPV player with full codec support fixes playback issues and removes the need for server-side lossy transcoding",
|
||||
name: "John Capasso",
|
||||
source: "Play Store",
|
||||
icon: GooglePlayIcon,
|
||||
},
|
||||
{
|
||||
text: "Much better than the official app! The sticking point for me is that downloads actually work reliably. The interface is great and snappy.",
|
||||
@@ -108,10 +109,10 @@
|
||||
icon: GooglePlayIcon,
|
||||
},
|
||||
{
|
||||
text: "This has to be my new favorite plex client. Starts up and connects quickly. Quality is amazing. Well worth the $5.",
|
||||
name: "nasukaren",
|
||||
source: "App Store",
|
||||
icon: AppleIcon,
|
||||
text: "Just brilliant. Using on all my Android TV boxes. UI and UX are great. I was having intermittent audio drops with the latest build of the official Plex app. No issues with Plezy.",
|
||||
name: "Jon Evans",
|
||||
source: "Play Store",
|
||||
icon: GooglePlayIcon,
|
||||
},
|
||||
{
|
||||
text: "Fantastic and super lean looking! Must have if you have a Plex server.",
|
||||
@@ -121,10 +122,58 @@
|
||||
},
|
||||
];
|
||||
|
||||
let mounted = $state(false);
|
||||
const featuredReviews = [row1[3]!, row2[0]!, row2[4]!, row1[5]!];
|
||||
const featuredReviewSet = new Set(featuredReviews);
|
||||
const supportingReviews = [...row1, ...row2].filter((review) => !featuredReviewSet.has(review));
|
||||
|
||||
onMount(() => {
|
||||
mounted = true;
|
||||
let featuredIndex = $state(0);
|
||||
let carouselHovered = $state(false);
|
||||
let carouselFocused = $state(false);
|
||||
let prefersReducedMotion = $state(false);
|
||||
|
||||
const featuredReview = $derived(featuredReviews[featuredIndex]!);
|
||||
const carouselPaused = $derived(carouselHovered || carouselFocused || prefersReducedMotion);
|
||||
|
||||
function showReview(index: number) {
|
||||
featuredIndex = (index + featuredReviews.length) % featuredReviews.length;
|
||||
}
|
||||
|
||||
function previousReview() {
|
||||
showReview(featuredIndex - 1);
|
||||
}
|
||||
|
||||
function nextReview() {
|
||||
showReview(featuredIndex + 1);
|
||||
}
|
||||
|
||||
function handleCarouselFocusOut(event: FocusEvent) {
|
||||
const currentTarget = event.currentTarget;
|
||||
carouselFocused =
|
||||
currentTarget instanceof HTMLElement &&
|
||||
event.relatedTarget instanceof Node &&
|
||||
currentTarget.contains(event.relatedTarget);
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
const media = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
const updatePreference = () => {
|
||||
prefersReducedMotion = media.matches;
|
||||
};
|
||||
|
||||
updatePreference();
|
||||
media.addEventListener("change", updatePreference);
|
||||
return () => media.removeEventListener("change", updatePreference);
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
const currentIndex = featuredIndex;
|
||||
if (carouselPaused) return;
|
||||
|
||||
const timeout = window.setTimeout(() => {
|
||||
showReview(currentIndex + 1);
|
||||
}, 8000);
|
||||
|
||||
return () => window.clearTimeout(timeout);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -139,82 +188,85 @@
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
|
||||
<!-- Row 1: scrolls left -->
|
||||
<div class="marquee-row row-gap">
|
||||
<div class="marquee-track marquee-left">
|
||||
{#each row1 as review}
|
||||
{@const SourceIcon = review.icon}
|
||||
<div class="review-card glass-card">
|
||||
{#if review.source === "Reddit"}
|
||||
<div class="featured-wrap">
|
||||
<ScrollReveal>
|
||||
<article
|
||||
class="featured-review"
|
||||
aria-label="Featured user reviews"
|
||||
aria-roledescription="carousel"
|
||||
onpointerenter={() => carouselHovered = true}
|
||||
onpointerleave={() => carouselHovered = false}
|
||||
onfocusin={() => carouselFocused = true}
|
||||
onfocusout={handleCarouselFocusOut}
|
||||
>
|
||||
<div class="featured-top">
|
||||
{#if featuredReview.source === "Reddit"}
|
||||
<div class="upvote">
|
||||
<ArrowUpIcon />
|
||||
<span>{review.upvotes}</span>
|
||||
<span>{featuredReview.upvotes}</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="stars">
|
||||
<div class="stars" aria-label="5 out of 5 stars">
|
||||
{#each Array(5) as _}
|
||||
<StarIcon />
|
||||
<StarIcon aria-hidden="true" />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<p class="review-text">"{review.text}"</p>
|
||||
<div class="review-footer">
|
||||
<span class="review-name">{review.name}</span>
|
||||
<div class="review-source">
|
||||
<SourceIcon />
|
||||
<span>{review.source}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{#if mounted}
|
||||
<!-- Duplicate for seamless loop -->
|
||||
<div aria-hidden="true" class="duplicate-cards">
|
||||
{#each row1 as review}
|
||||
{@const SourceIcon = review.icon}
|
||||
<div class="review-card glass-card">
|
||||
{#if review.source === "Reddit"}
|
||||
<div class="upvote">
|
||||
<ArrowUpIcon />
|
||||
<span>{review.upvotes}</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="stars">
|
||||
{#each Array(5) as _}
|
||||
<StarIcon />
|
||||
|
||||
<div class="carousel-controls">
|
||||
<button type="button" class="carousel-arrow" onclick={previousReview} aria-label="Previous featured review">
|
||||
<ChevronLeftIcon />
|
||||
</button>
|
||||
<div class="carousel-dots" role="group" aria-label="Choose featured review">
|
||||
{#each featuredReviews as _, index}
|
||||
<button
|
||||
type="button"
|
||||
class="carousel-dot"
|
||||
class:active={featuredIndex === index}
|
||||
onclick={() => showReview(index)}
|
||||
aria-label={`Show featured review ${index + 1}`}
|
||||
aria-current={featuredIndex === index ? "true" : undefined}
|
||||
></button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<p class="review-text">"{review.text}"</p>
|
||||
<div class="review-footer">
|
||||
<span class="review-name">{review.name}</span>
|
||||
<div class="review-source">
|
||||
<SourceIcon />
|
||||
<span>{review.source}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<button type="button" class="carousel-arrow" onclick={nextReview} aria-label="Next featured review">
|
||||
<ChevronRightIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 2: scrolls right -->
|
||||
<div class="marquee-row">
|
||||
<div class="marquee-track marquee-right">
|
||||
{#each row2 as review}
|
||||
<div class="featured-slide" aria-live={carouselPaused ? "polite" : "off"}>
|
||||
{#key featuredIndex}
|
||||
{@const FeaturedSourceIcon = featuredReview.icon}
|
||||
<div class="featured-content">
|
||||
<blockquote>"{featuredReview.text}"</blockquote>
|
||||
<div class="review-footer">
|
||||
<span class="review-name">{featuredReview.name}</span>
|
||||
<div class="review-source">
|
||||
<FeaturedSourceIcon />
|
||||
<span>{featuredReview.source}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/key}
|
||||
</div>
|
||||
</article>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
|
||||
<div class="review-strip content-pad" role="list" aria-label="More user reviews">
|
||||
{#each supportingReviews as review}
|
||||
{@const SourceIcon = review.icon}
|
||||
<div class="review-card glass-card">
|
||||
<article class="review-card" role="listitem">
|
||||
{#if review.source === "Reddit"}
|
||||
<div class="upvote">
|
||||
<ArrowUpIcon />
|
||||
<span>{review.upvotes}</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="stars">
|
||||
<div class="stars" aria-label="5 out of 5 stars">
|
||||
{#each Array(5) as _}
|
||||
<StarIcon />
|
||||
<StarIcon aria-hidden="true" />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -226,90 +278,202 @@
|
||||
<span>{review.source}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{/each}
|
||||
{#if mounted}
|
||||
<!-- Duplicate for seamless loop -->
|
||||
<div aria-hidden="true" class="duplicate-cards">
|
||||
{#each row2 as review}
|
||||
{@const SourceIcon = review.icon}
|
||||
<div class="review-card glass-card">
|
||||
{#if review.source === "Reddit"}
|
||||
<div class="upvote">
|
||||
<ArrowUpIcon />
|
||||
<span>{review.upvotes}</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="stars">
|
||||
{#each Array(5) as _}
|
||||
<StarIcon />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<p class="review-text">"{review.text}"</p>
|
||||
<div class="review-footer">
|
||||
<span class="review-name">{review.name}</span>
|
||||
<div class="review-source">
|
||||
<SourceIcon />
|
||||
<span>{review.source}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.reviews-section {
|
||||
overflow: hidden;
|
||||
padding-block: 4rem;
|
||||
padding-block: clamp(4rem, 9vw, 8rem);
|
||||
}
|
||||
|
||||
.reviews-header,
|
||||
.featured-wrap {
|
||||
width: min(100%, var(--page-width));
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--page-gutter);
|
||||
}
|
||||
|
||||
.reviews-header {
|
||||
max-width: 64rem;
|
||||
margin-inline: auto;
|
||||
margin-bottom: 2.5rem;
|
||||
padding-inline: 1.5rem;
|
||||
margin-bottom: clamp(2rem, 5vw, 3.5rem);
|
||||
}
|
||||
|
||||
.section-label {
|
||||
margin-bottom: 0.75rem;
|
||||
color: var(--color-accent);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.025em;
|
||||
line-height: 1.25rem;
|
||||
text-transform: uppercase;
|
||||
width: fit-content;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: var(--radius-full);
|
||||
padding: 0.5rem 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
background: var(--color-surface);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
max-width: 12ch;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 2.25rem;
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(2.5rem, 7vw, 4.75rem);
|
||||
font-weight: 700;
|
||||
line-height: 2.5rem;
|
||||
letter-spacing: -0.045em;
|
||||
line-height: 1;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.section-description {
|
||||
max-width: 32rem;
|
||||
max-width: 34rem;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
font-size: clamp(1rem, 2vw, 1.125rem);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.row-gap {
|
||||
margin-bottom: 1rem;
|
||||
.featured-review {
|
||||
display: flex;
|
||||
min-height: clamp(22rem, 42vw, 31rem);
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
border-radius: var(--radius-xl);
|
||||
padding: clamp(1.5rem, 6vw, 4rem);
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.featured-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.featured-top .upvote {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.carousel-controls,
|
||||
.carousel-dots {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.carousel-controls {
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.carousel-dots {
|
||||
gap: 0.375rem;
|
||||
padding-inline: 0.25rem;
|
||||
}
|
||||
|
||||
.carousel-arrow,
|
||||
.carousel-dot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-full);
|
||||
color: var(--color-text-muted);
|
||||
background: var(--color-surface-highest);
|
||||
transition:
|
||||
color var(--motion-fast) var(--ease-standard),
|
||||
background-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.carousel-arrow {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
}
|
||||
|
||||
.carousel-arrow :global(svg) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.carousel-dot {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
padding: 0;
|
||||
background: var(--color-text-subtle);
|
||||
}
|
||||
|
||||
.carousel-dot.active {
|
||||
background: var(--color-text);
|
||||
}
|
||||
|
||||
.carousel-arrow:hover,
|
||||
.carousel-arrow:focus-visible,
|
||||
.carousel-dot:hover,
|
||||
.carousel-dot:focus-visible {
|
||||
color: var(--color-text);
|
||||
background: rgb(237 237 237 / 0.18);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.carousel-dot:focus-visible {
|
||||
box-shadow: 0 0 0 0.25rem rgb(237 237 237 / 0.12);
|
||||
}
|
||||
|
||||
.featured-slide {
|
||||
display: grid;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.featured-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
animation: featured-review-enter var(--motion-normal) var(--ease-standard) both;
|
||||
}
|
||||
|
||||
@keyframes featured-review-enter {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(0.5rem);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.featured-review blockquote {
|
||||
max-width: 24ch;
|
||||
margin: 2rem 0;
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(1.75rem, 4.5vw, 3.25rem);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.035em;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.review-strip {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.review-strip::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.review-card {
|
||||
display: flex;
|
||||
width: 300px;
|
||||
width: min(82vw, 20rem);
|
||||
min-height: 14rem;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
padding: 1.25rem;
|
||||
border-radius: 1rem;
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem;
|
||||
background: var(--color-surface-high);
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
.review-card:nth-child(3n) {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.upvote,
|
||||
@@ -321,23 +485,29 @@
|
||||
}
|
||||
|
||||
.upvote {
|
||||
width: fit-content;
|
||||
gap: 0.375rem;
|
||||
margin-bottom: 0.75rem;
|
||||
color: var(--color-accent);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem;
|
||||
margin-bottom: 1.25rem;
|
||||
border-radius: var(--radius-full);
|
||||
padding: 0.375rem 0.625rem;
|
||||
color: #ff9a7d;
|
||||
background: rgb(255 118 82 / 0.14);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.upvote :global(svg) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
}
|
||||
|
||||
.stars {
|
||||
gap: 0.125rem;
|
||||
margin-bottom: 0.75rem;
|
||||
color: var(--color-accent);
|
||||
color: var(--color-brand);
|
||||
}
|
||||
|
||||
.review-card .stars {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.stars :global(svg),
|
||||
@@ -348,116 +518,32 @@
|
||||
|
||||
.review-text {
|
||||
flex: 1;
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.625;
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.review-footer {
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.review-name {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.review-source {
|
||||
flex-shrink: 0;
|
||||
gap: 0.375rem;
|
||||
color: color-mix(in srgb, var(--color-text-muted) 60%, transparent);
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
color: var(--color-text-subtle);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.duplicate-cards {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.marquee-row {
|
||||
overflow: hidden;
|
||||
mask-image: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
black 5%,
|
||||
black 95%,
|
||||
transparent
|
||||
);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
black 5%,
|
||||
black 95%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.marquee-track {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
width: max-content;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.marquee-left {
|
||||
animation: scroll-left 60s linear infinite;
|
||||
}
|
||||
|
||||
.marquee-right {
|
||||
animation: scroll-right 60s linear infinite;
|
||||
}
|
||||
|
||||
.marquee-row:hover .marquee-track {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
@keyframes scroll-left {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scroll-right {
|
||||
from {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.marquee-track {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.reviews-section {
|
||||
padding-block: 6rem;
|
||||
}
|
||||
|
||||
.reviews-header {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.review-card {
|
||||
width: 340px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.reviews-section {
|
||||
padding-block: 8rem;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
.content-pad {
|
||||
padding-left: max(var(--page-gutter), calc((100vw - var(--page-width)) / 2 + var(--page-gutter)));
|
||||
padding-right: var(--page-gutter);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -249,82 +249,110 @@
|
||||
<style>
|
||||
.screenshots-section {
|
||||
overflow: hidden;
|
||||
padding-block: 4rem;
|
||||
padding-block: clamp(4rem, 9vw, 8rem);
|
||||
}
|
||||
|
||||
.screenshots-header {
|
||||
max-width: 64rem;
|
||||
margin-inline: auto;
|
||||
margin-bottom: 2rem;
|
||||
margin-bottom: clamp(2rem, 5vw, 3.5rem);
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
margin-bottom: 0.75rem;
|
||||
color: var(--color-accent);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.025em;
|
||||
line-height: 1.25rem;
|
||||
text-transform: uppercase;
|
||||
width: fit-content;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: var(--radius-full);
|
||||
padding: 0.5rem 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
background: var(--color-surface);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
max-width: 12ch;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 2.25rem;
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(2.5rem, 7vw, 4.75rem);
|
||||
font-weight: 700;
|
||||
line-height: 2.5rem;
|
||||
letter-spacing: -0.045em;
|
||||
line-height: 1;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.section-description {
|
||||
max-width: 32rem;
|
||||
margin-bottom: 1.5rem;
|
||||
max-width: 34rem;
|
||||
margin-bottom: 2rem;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
font-size: clamp(1rem, 2vw, 1.125rem);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.screenshot-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.device-tabs {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
gap: 0.25rem;
|
||||
border-radius: 0.75rem;
|
||||
background: rgb(255 255 255 / 0.04);
|
||||
max-width: 100%;
|
||||
gap: var(--group-gap);
|
||||
overflow-x: auto;
|
||||
border-radius: var(--radius-full);
|
||||
padding: 0.25rem;
|
||||
background: var(--color-surface);
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.device-tabs::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.device-button {
|
||||
display: flex;
|
||||
min-height: 2.75rem;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--radius-pill);
|
||||
padding-inline: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem;
|
||||
transition: background-color 150ms ease, color 150ms ease;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
transition:
|
||||
border-radius var(--motion-expressive) var(--ease-standard),
|
||||
color var(--motion-fast) var(--ease-standard),
|
||||
background-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.device-button:not(.active):hover {
|
||||
color: color-mix(in srgb, var(--color-text) 80%, transparent);
|
||||
.device-button:not(.active):hover,
|
||||
.device-button:not(.active):focus-visible {
|
||||
color: var(--color-text);
|
||||
background: rgb(237 237 237 / 0.12);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.device-button.active {
|
||||
color: var(--color-text);
|
||||
background: rgb(255 255 255 / 0.1);
|
||||
color: var(--color-on-primary);
|
||||
background: var(--color-text);
|
||||
}
|
||||
|
||||
.device-button.active:hover,
|
||||
.device-button.active:focus-visible {
|
||||
border-radius: var(--radius-md);
|
||||
background: #fff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.device-button :global(svg),
|
||||
.scroll-arrow :global(svg) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
|
||||
.device-label {
|
||||
@@ -334,28 +362,34 @@
|
||||
.scroll-arrows {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
gap: var(--group-gap);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.scroll-arrow {
|
||||
display: flex;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0.5rem;
|
||||
background: rgb(255 255 255 / 0.04);
|
||||
color: color-mix(in srgb, var(--color-text-muted) 30%, transparent);
|
||||
transition: background-color 150ms ease, color 150ms ease;
|
||||
border-radius: var(--radius-pill);
|
||||
color: var(--color-text-subtle);
|
||||
background: var(--color-surface);
|
||||
transition:
|
||||
border-radius var(--motion-expressive) var(--ease-standard),
|
||||
color var(--motion-fast) var(--ease-standard),
|
||||
background-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.scroll-arrow.enabled {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.scroll-arrow.enabled:hover {
|
||||
background: rgb(255 255 255 / 0.1);
|
||||
.scroll-arrow.enabled:hover,
|
||||
.scroll-arrow.enabled:focus-visible {
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-surface-highest);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.screenshot-panels {
|
||||
@@ -371,10 +405,11 @@
|
||||
width: 100%;
|
||||
gap: 1.25rem;
|
||||
overflow-x: auto;
|
||||
opacity: 0;
|
||||
padding-bottom: 1rem;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 220ms ease;
|
||||
scroll-snap-type: x mandatory;
|
||||
transition: opacity var(--motion-normal) var(--ease-standard);
|
||||
}
|
||||
|
||||
.screenshot-strip.panel-active {
|
||||
@@ -393,8 +428,6 @@
|
||||
.screenshot-frame {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border: 1px solid color-mix(in srgb, var(--color-border) 40%, transparent);
|
||||
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.5);
|
||||
}
|
||||
|
||||
.phone-frame {
|
||||
@@ -410,60 +443,35 @@
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.tv-frame {
|
||||
border-width: 2px;
|
||||
border-color: color-mix(in srgb, var(--color-border) 30%, transparent);
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.screenshot-image {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.content-pad {
|
||||
/* Left padding aligns with max-w-5xl (64rem) + px-6 */
|
||||
padding-left: max(1.5rem, calc((100vw - 64rem) / 2 + 1.5rem));
|
||||
padding-right: 1.5rem;
|
||||
scroll-padding-left: max(1.5rem, calc((100vw - 64rem) / 2 + 1.5rem));
|
||||
}
|
||||
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.screenshots-section {
|
||||
padding-block: 6rem;
|
||||
}
|
||||
|
||||
.screenshots-header {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.section-description {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 560px) {
|
||||
.device-label {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.screenshots-section {
|
||||
padding-block: 8rem;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.scroll-arrows {
|
||||
display: flex;
|
||||
}
|
||||
@@ -471,11 +479,11 @@
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.screenshot-panels {
|
||||
min-height: calc(480px + 1rem);
|
||||
min-height: calc(500px + 1rem);
|
||||
}
|
||||
|
||||
.screenshot-item {
|
||||
height: 480px;
|
||||
height: 500px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -25,15 +25,21 @@
|
||||
<div
|
||||
bind:this={el}
|
||||
class="{className} scroll-reveal"
|
||||
style="opacity: {visible ? 1 : 0}; transform: translateY({visible ? 0 : 24}px); transition-delay: {delay}ms;"
|
||||
style="opacity: {visible ? 1 : 0}; transform: translateY({visible ? 0 : 16}px); transition-delay: {delay}ms;"
|
||||
>
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.scroll-reveal {
|
||||
transition-property: opacity, transform;
|
||||
transition-duration: 600ms;
|
||||
transition-timing-function: ease-out;
|
||||
transition:
|
||||
opacity var(--motion-expressive) var(--ease-standard),
|
||||
transform var(--motion-expressive) var(--ease-standard);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.scroll-reveal {
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,33 +27,45 @@
|
||||
<style>
|
||||
.error-page {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4rem 1.5rem;
|
||||
padding: var(--page-gutter);
|
||||
}
|
||||
|
||||
.error-card {
|
||||
display: flex;
|
||||
max-width: 24rem;
|
||||
width: min(100%, 32rem);
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-radius: var(--radius-xl);
|
||||
padding: clamp(2rem, 8vw, 4rem);
|
||||
background: var(--color-surface);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-logo {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 2rem;
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-surface-highest);
|
||||
}
|
||||
|
||||
.error-logo :global(svg) {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 3rem;
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(3.5rem, 16vw, 6rem);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.06em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@@ -64,19 +76,23 @@
|
||||
|
||||
.home-link {
|
||||
display: inline-flex;
|
||||
min-height: 3rem;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-radius: 9999px;
|
||||
background: var(--color-accent);
|
||||
color: #fff;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25rem;
|
||||
transition: background-color 150ms ease;
|
||||
border-radius: var(--radius-pill);
|
||||
padding-inline: 1.25rem;
|
||||
color: var(--color-on-primary);
|
||||
background: var(--color-text);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
transition:
|
||||
border-radius var(--motion-expressive) var(--ease-standard),
|
||||
background-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.home-link:hover {
|
||||
background: color-mix(in srgb, var(--color-accent) 90%, transparent);
|
||||
.home-link:hover,
|
||||
.home-link:focus-visible {
|
||||
border-radius: var(--radius-md);
|
||||
background: #fff;
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import Reviews from '$lib/components/Reviews.svelte';
|
||||
import FAQ from '$lib/components/FAQ.svelte';
|
||||
import Footer from '$lib/components/Footer.svelte';
|
||||
import NoiseOverlay from '$lib/components/NoiseOverlay.svelte';
|
||||
import { faqSchemaMainEntity } from '$lib/content/faqs';
|
||||
|
||||
const { data } = $props();
|
||||
@@ -95,7 +94,6 @@
|
||||
{@html `<script type="application/ld+json">${JSON.stringify(faqSchema)}</script>`}
|
||||
</svelte:head>
|
||||
|
||||
<NoiseOverlay />
|
||||
<Hero />
|
||||
<Features />
|
||||
<Screenshots />
|
||||
|
||||
@@ -1,26 +1,49 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
|
||||
/* Mirrors the app's mono Material 3 theme. */
|
||||
--color-bg: #0e0f12;
|
||||
--color-surface: #15171c;
|
||||
--color-surface-hover: #1a1c22;
|
||||
--color-border: #2a2d35;
|
||||
--color-accent: #ff6b45;
|
||||
--color-accent-glow: #ff6b4533;
|
||||
--color-accent-dim: #ab543a;
|
||||
--color-surface-high: #1d1f25;
|
||||
--color-surface-highest: #25272e;
|
||||
--color-surface-hover: #2b2e35;
|
||||
--color-border: rgb(237 237 237 / 0.12);
|
||||
--color-text: #ededed;
|
||||
--color-text-muted: #9ca3af;
|
||||
--color-text-muted: rgb(237 237 237 / 0.6);
|
||||
--color-text-subtle: rgb(237 237 237 / 0.4);
|
||||
--color-brand: #ff7652;
|
||||
--color-accent: var(--color-brand);
|
||||
--color-accent-dim: #b9573b;
|
||||
--color-on-primary: #0e0f12;
|
||||
|
||||
--font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
||||
--radius-xs: 0.3125rem;
|
||||
--radius-sm: 0.5rem;
|
||||
--radius-md: 0.75rem;
|
||||
--radius-lg: 1.25rem;
|
||||
--radius-xl: 2.25rem;
|
||||
--radius-full: 999px;
|
||||
--radius-pill: 1.5rem;
|
||||
--group-gap: 0.1875rem;
|
||||
|
||||
--animate-fade-in-up: fade-in-up 0.6s ease-out both;
|
||||
--animate-pulse-glow: pulse-glow 3s ease-in-out infinite;
|
||||
--animate-float: float 6s ease-in-out infinite;
|
||||
--space: 0.75rem;
|
||||
--page-width: 76rem;
|
||||
--page-gutter: clamp(1rem, 4vw, 2rem);
|
||||
|
||||
--motion-fast: 120ms;
|
||||
--motion-normal: 200ms;
|
||||
--motion-slow: 300ms;
|
||||
--motion-expressive: 350ms;
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
|
||||
--font-display: "Avenir Next", "Segoe UI Variable Display", "SF Pro Display", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-body: "Inter", "Segoe UI Variable Text", "SF Pro Text", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-utility: ui-monospace, "SFMono-Regular", "Cascadia Code", monospace;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
border: 0 solid;
|
||||
}
|
||||
|
||||
html,
|
||||
@@ -35,18 +58,26 @@ ul {
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
background-color: var(--color-bg);
|
||||
scroll-padding-top: 6rem;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
body {
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
font-family: var(--font-sans);
|
||||
overflow-x: hidden;
|
||||
font-family: var(--font-body);
|
||||
line-height: 1.5;
|
||||
background-color: var(--color-bg);
|
||||
background: var(--color-bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
::selection {
|
||||
color: var(--color-on-primary);
|
||||
background: var(--color-text);
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -54,6 +85,11 @@ a {
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea,
|
||||
@@ -63,6 +99,7 @@ select {
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
@@ -97,84 +134,33 @@ ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.flat-card {
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
@keyframes fade-in-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(24px);
|
||||
transform: translateY(1rem);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% {
|
||||
opacity: 0.6;
|
||||
filter: blur(60px);
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
filter: blur(80px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes gradient-shift {
|
||||
0%, 100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
33% {
|
||||
transform: translate(30px, -20px) scale(1.1);
|
||||
}
|
||||
66% {
|
||||
transform: translate(-20px, 15px) scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
/* Glassmorphism card utility */
|
||||
.glass-card {
|
||||
background: color-mix(in srgb, var(--color-surface) 60%, transparent);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--color-border);
|
||||
transition: box-shadow 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.glass-card:hover {
|
||||
border-color: var(--color-accent-glow);
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.3),
|
||||
0 0 0 1px var(--color-accent-glow),
|
||||
0 0 40px -12px var(--color-accent-glow);
|
||||
}
|
||||
|
||||
/* Noise overlay texture */
|
||||
.noise-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 50;
|
||||
opacity: 0.035;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
|
||||
background-repeat: repeat;
|
||||
background-size: 256px 256px;
|
||||
}
|
||||
|
||||
/* Scroll indicator */
|
||||
@keyframes scroll-bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(8px);
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
scroll-behavior: auto !important;
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,93 +165,109 @@
|
||||
|
||||
<style>
|
||||
.privacy-article {
|
||||
max-width: 64rem;
|
||||
width: min(100%, 58rem);
|
||||
margin-inline: auto;
|
||||
padding: 4rem 1.5rem;
|
||||
padding: clamp(2rem, 7vw, 6rem) var(--page-gutter) clamp(5rem, 10vw, 9rem);
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
min-height: 2.75rem;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
margin-bottom: 3rem;
|
||||
margin-bottom: clamp(3rem, 8vw, 6rem);
|
||||
border-radius: var(--radius-full);
|
||||
padding-inline: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem;
|
||||
transition: color 150ms ease;
|
||||
background: var(--color-surface);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
transition:
|
||||
color var(--motion-fast) var(--ease-standard),
|
||||
background-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
.back-link:hover,
|
||||
.back-link:focus-visible {
|
||||
color: var(--color-text);
|
||||
background: var(--color-surface-highest);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.back-logo :global(svg) {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
width: 1.375rem;
|
||||
height: 1.375rem;
|
||||
}
|
||||
|
||||
.privacy-heading {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.875rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(2.75rem, 9vw, 5rem);
|
||||
font-weight: 700;
|
||||
line-height: 2.25rem;
|
||||
letter-spacing: -0.05em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.last-updated {
|
||||
margin-bottom: 3rem;
|
||||
margin-bottom: clamp(3rem, 7vw, 5rem);
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
font-family: var(--font-utility);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.prose {
|
||||
max-width: 48rem;
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.75;
|
||||
max-width: none;
|
||||
}
|
||||
.prose h2 {
|
||||
color: var(--color-text);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.prose h3 {
|
||||
color: var(--color-text);
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.prose h2 {
|
||||
margin-top: 3.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(1.5rem, 4vw, 2rem);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.025em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.prose h3 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 0.625rem;
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.0625rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.prose p {
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 1.125rem;
|
||||
}
|
||||
|
||||
.prose ul {
|
||||
margin-bottom: 1.25rem;
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.25rem 1.25rem 1.25rem 2.75rem;
|
||||
background: var(--color-surface);
|
||||
list-style: disc;
|
||||
padding-left: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.prose li {
|
||||
margin-bottom: 0.25rem;
|
||||
|
||||
.prose li + li {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.prose a {
|
||||
color: var(--color-accent);
|
||||
color: var(--color-text);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
.prose a:hover {
|
||||
opacity: 0.8;
|
||||
text-decoration-color: var(--color-text-subtle);
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.privacy-article {
|
||||
padding-block: 6rem;
|
||||
}
|
||||
|
||||
.privacy-heading {
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
.prose a:hover,
|
||||
.prose a:focus-visible {
|
||||
text-decoration-color: var(--color-text);
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -75,68 +75,87 @@
|
||||
<style>
|
||||
.scan-page {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4rem 1.5rem;
|
||||
padding: var(--page-gutter);
|
||||
}
|
||||
|
||||
.scan-card {
|
||||
display: flex;
|
||||
max-width: 24rem;
|
||||
width: min(100%, 32rem);
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-radius: var(--radius-xl);
|
||||
padding: clamp(2rem, 8vw, 4rem);
|
||||
background: var(--color-surface);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.scan-logo {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 2rem;
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-surface-highest);
|
||||
}
|
||||
|
||||
.scan-logo :global(svg) {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.scan-heading {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(2rem, 8vw, 3.25rem);
|
||||
font-weight: 700;
|
||||
line-height: 2rem;
|
||||
letter-spacing: -0.045em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.scan-description {
|
||||
max-width: 24rem;
|
||||
margin-bottom: 2rem;
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.store-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.store-button {
|
||||
display: inline-flex;
|
||||
min-height: 3rem;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-radius: 9999px;
|
||||
background: #fff;
|
||||
color: #111827;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25rem;
|
||||
transition: background-color 150ms ease;
|
||||
border-radius: var(--radius-pill);
|
||||
padding-inline: 1.25rem;
|
||||
color: var(--color-on-primary);
|
||||
background: var(--color-text);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 700;
|
||||
transition:
|
||||
border-radius var(--motion-expressive) var(--ease-standard),
|
||||
background-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.store-button:hover {
|
||||
background: #f3f4f6;
|
||||
.store-button:hover,
|
||||
.store-button:focus-visible {
|
||||
border-radius: var(--radius-md);
|
||||
background: #fff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.store-button :global(svg) {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user