feat(docs): streamline product and privacy pages

This commit is contained in:
2026-07-16 19:49:17 +02:00
parent 0a185077af
commit 526a5da444
16 changed files with 869 additions and 1755 deletions

View File

@@ -1,72 +1,36 @@
.site-footer { .site-footer {
padding-top: 75px; padding-top: 42px;
background: var(--paper); background: var(--paper);
} }
.footer-main { .footer-inner {
display: grid;
grid-template-columns: 1fr auto;
gap: 60px;
padding-bottom: 70px;
}
.footer-brand {
display: flex; display: flex;
align-items: flex-start; align-items: center;
gap: 15px; justify-content: space-between;
gap: 40px;
padding-bottom: 38px;
} }
.footer-mark { .footer-identity {
display: grid; display: inline-flex;
width: 48px; align-items: center;
height: 48px; gap: 7px;
flex: 0 0 auto;
place-items: center;
border: 1px solid var(--line);
border-radius: 14px;
background: white;
}
.footer-mark img {
width: 32px;
height: 32px;
}
.footer-brand p {
margin-bottom: 0;
color: var(--ink-muted);
font-size: 13px;
}
.footer-brand .footer-name {
margin-bottom: 3px;
color: var(--ink); color: var(--ink);
font-family: var(--font-display); font-family: var(--font-display);
font-size: 21px; font-size: 18px;
font-weight: 750; font-weight: 750;
letter-spacing: -0.04em; letter-spacing: -0.04em;
} }
.footer-identity img {
width: 38px;
height: 38px;
}
.footer-links { .footer-links {
display: grid;
grid-template-columns: repeat(2, minmax(140px, 1fr));
gap: 70px;
}
.footer-links > div {
display: flex; display: flex;
flex-direction: column; align-items: center;
gap: 9px; gap: 24px;
}
.footer-links .footer-label {
margin-bottom: 7px;
color: var(--ink-muted);
font-family: var(--font-mono);
font-size: 10px;
font-weight: 800;
letter-spacing: 0.1em;
text-transform: uppercase;
} }
.footer-links a { .footer-links a {
@@ -74,8 +38,8 @@
align-items: center; align-items: center;
gap: 6px; gap: 6px;
color: var(--ink-soft); color: var(--ink-soft);
font-size: 13px; font-size: 12px;
font-weight: 600; font-weight: 620;
} }
.footer-links a:hover { .footer-links a:hover {
@@ -83,15 +47,15 @@
} }
.footer-links svg { .footer-links svg {
width: 13px; width: 14px;
} }
.footer-bottom { .footer-bottom {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding-top: 22px; padding-top: 20px;
padding-bottom: 28px; padding-bottom: 26px;
border-top: 1px solid var(--line); border-top: 1px solid var(--line);
color: var(--ink-muted); color: var(--ink-muted);
font-size: 11px; font-size: 11px;

View File

@@ -97,7 +97,7 @@
} }
.hero-lead { .hero-lead {
max-width: 610px; max-width: 590px;
margin-bottom: 30px; margin-bottom: 30px;
color: var(--ink-soft); color: var(--ink-soft);
font-size: 18px; font-size: 18px;
@@ -138,157 +138,69 @@
} }
.hero-platforms li { .hero-platforms li {
padding: 4px 8px; display: grid;
width: 36px;
height: 36px;
place-items: center;
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: 7px; border-radius: 10px;
background: rgba(255, 255, 255, 0.65); background: rgba(255, 255, 255, 0.78);
box-shadow: 0 7px 18px rgba(55, 35, 63, 0.055);
} }
.hero-platforms .platform-glyph {
width: 20px;
height: 20px;
}
.platform-glyph[data-platform="android"] { color: #2fbf71; }
.platform-glyph[data-platform="ios"] { color: #201c23; }
.platform-glyph[data-platform="macos"] { color: #1687f8; }
.platform-glyph[data-platform="windows"] { color: #008de5; }
.hero-art-reveal { .hero-art-reveal {
min-width: 0; min-width: 0;
} }
.trust-section { .hero-app-preview {
position: relative; position: relative;
z-index: 3; width: min(112%, 850px);
margin-top: -32px; margin: 0 0 0 -3%;
isolation: isolate;
} }
.trust-grid { .hero-app-preview::before {
display: grid; position: absolute;
grid-template-columns: repeat(3, 1fr); z-index: -1;
overflow: hidden; inset: 14% 8% 8% 10%;
border: 1px solid var(--line); border-radius: 48%;
border-radius: 23px; background: rgba(168, 85, 247, 0.2);
background: rgba(255, 255, 255, 0.94); filter: blur(70px);
box-shadow: var(--shadow-lg); content: "";
backdrop-filter: blur(12px);
} }
.trust-item { .hero-app-preview-image {
display: flex;
min-height: 124px;
align-items: center;
gap: 19px;
padding: 24px 28px;
}
.trust-item + .trust-item {
border-left: 1px solid var(--line);
}
.trust-number {
color: var(--brand-500);
font-family: var(--font-mono);
font-size: 11px;
font-weight: 800;
}
.trust-item strong {
display: block; display: block;
margin-bottom: 3px; width: 100%;
font-family: var(--font-display); height: auto;
font-size: 18px; filter: drop-shadow(0 28px 42px rgba(36, 24, 42, 0.2));
letter-spacing: -0.025em;
} }
.trust-item p { .simple-section {
margin: 0; padding: 124px 0;
color: var(--ink-muted);
font-size: 13px;
line-height: 1.45;
} }
.word-stream { .simple-heading {
overflow: hidden; max-width: 760px;
padding: 92px 0 28px; margin-bottom: 58px;
color: #ebe5ee;
font-family: var(--font-display);
font-size: clamp(3.5rem, 7vw, 7.6rem);
font-weight: 800;
letter-spacing: -0.055em;
line-height: 1;
white-space: nowrap;
}
.word-stream > div {
display: flex;
width: max-content;
align-items: center;
gap: 35px;
animation: word-stream 36s linear infinite;
}
.word-stream i {
color: var(--brand-300);
font-size: 0.18em;
font-style: normal;
}
.section {
position: relative;
padding: 132px 0;
}
.section-heading {
margin-bottom: 64px;
}
.section-heading h2,
.privacy-intro h2,
.network-copy h2,
.platforms-copy h2,
.final-cta h2,
.approval-copy h2 {
margin-bottom: 20px;
font-size: clamp(2.7rem, 4.4vw, 4.8rem);
font-weight: 740;
letter-spacing: -0.06em;
line-height: 1.02;
}
.section-heading p,
.section-lead {
color: var(--ink-soft);
font-size: 17px;
line-height: 1.72;
}
.section-heading-centered {
max-width: 810px;
margin-right: auto;
margin-left: auto;
text-align: center;
}
.section-heading-centered p {
max-width: 620px;
margin-right: auto;
margin-bottom: 0;
margin-left: auto;
}
.section-heading-split {
display: grid;
grid-template-columns: 1.1fr 0.72fr;
align-items: end;
gap: 90px;
}
.section-heading-split h2 {
margin-bottom: 0;
}
.section-heading-split p {
margin-bottom: 6px;
} }
.kicker { .kicker {
display: inline-block; display: inline-block;
margin-bottom: 18px; margin-bottom: 17px;
color: var(--brand-600); color: var(--brand-600);
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 11px; font-size: 10px;
font-weight: 800; font-weight: 800;
letter-spacing: 0.13em; letter-spacing: 0.13em;
} }
@@ -297,405 +209,150 @@
color: #d89bff; color: #d89bff;
} }
.flow-section { .simple-heading h2,
padding-top: 105px; .trust-summary-copy h2 {
margin-bottom: 18px;
font-size: clamp(2.6rem, 4.2vw, 4.6rem);
font-weight: 740;
letter-spacing: -0.06em;
line-height: 1.02;
} }
.invitation-section { .simple-heading p {
overflow: hidden; max-width: 650px;
background: var(--paper-warm);
}
.approval-section {
padding: 150px 0;
}
.approval-layout {
display: grid;
grid-template-columns: minmax(0, 0.86fr) minmax(480px, 1.14fr);
align-items: center;
gap: 100px;
}
.approval-copy h2 {
max-width: 620px;
}
.approval-copy .section-lead {
max-width: 570px;
margin-bottom: 27px;
}
.check-list {
display: grid;
gap: 13px;
margin: 0 0 25px;
padding: 0;
list-style: none;
}
.check-list li {
display: flex;
align-items: center;
gap: 11px;
color: #3c3540;
font-size: 14px;
font-weight: 600;
}
.check-list svg {
width: 20px;
height: 20px;
padding: 4px;
border-radius: 50%;
background: var(--brand-100);
color: var(--brand-600);
stroke-width: 2.5;
}
.approval-note {
max-width: 555px;
margin: 0; margin: 0;
padding: 15px 17px; color: var(--ink-soft);
border: 1px solid #eadff0; font-size: 17px;
border-radius: 12px; line-height: 1.7;
background: #faf7fb;
color: var(--ink-muted);
font-size: 12px;
line-height: 1.55;
} }
.approval-demo-wrap { .simple-steps {
position: relative;
}
.demo-hint {
position: absolute;
right: 22px;
bottom: -45px;
display: inline-flex;
align-items: center;
gap: 7px;
color: var(--brand-600);
font-family: var(--font-mono);
font-size: 10px;
font-weight: 800;
letter-spacing: 0.07em;
transform: rotate(-2deg);
}
.demo-hint svg {
width: 15px;
}
.privacy-principles-section {
position: relative;
overflow: hidden;
padding: 145px 0 135px;
background:
radial-gradient(circle at 90% 8%, rgba(168, 85, 247, 0.18), transparent 25%),
radial-gradient(circle at 5% 90%, rgba(124, 42, 239, 0.12), transparent 25%),
var(--dark);
color: white;
}
.privacy-principles-section::after {
position: absolute;
inset: 0;
background-image: radial-gradient(rgba(255, 255, 255, 0.12) 0.7px, transparent 0.7px);
background-size: 22px 22px;
content: "";
opacity: 0.13;
pointer-events: none;
}
.privacy-principles-section .page-shell {
position: relative;
z-index: 1;
}
.privacy-intro {
max-width: 890px;
margin-bottom: 65px;
}
.privacy-intro h2 {
max-width: 840px;
}
.privacy-intro > p {
max-width: 700px;
margin-bottom: 0;
color: #bdb4c2;
font-size: 18px;
line-height: 1.72;
}
.privacy-point-grid {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 12px; border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
} }
.privacy-point { .simple-step {
min-height: 275px; min-height: 235px;
padding: 24px; padding: 30px 30px 36px;
border: 1px solid var(--dark-line);
border-radius: 20px;
background: rgba(255, 255, 255, 0.035);
transition: border-color 200ms ease, transform 200ms ease, background 200ms ease;
} }
.privacy-point:hover { .simple-step + .simple-step {
transform: translateY(-5px); border-left: 1px solid var(--line);
border-color: rgba(192, 132, 252, 0.45);
background: rgba(255, 255, 255, 0.055);
} }
.privacy-point-top { .simple-step-top {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 43px; margin-bottom: 46px;
} }
.privacy-point-top > span { .simple-step-top span {
display: grid; color: var(--brand-600);
width: 44px;
height: 44px;
place-items: center;
border: 1px solid rgba(192, 132, 252, 0.28);
border-radius: 12px;
background: rgba(168, 85, 247, 0.1);
color: var(--brand-400);
}
.privacy-point-top svg {
width: 22px;
}
.privacy-point-top small {
color: #706775;
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 10px; font-size: 11px;
font-weight: 800;
} }
.privacy-point h3 { .simple-step-top svg {
width: 24px;
height: 24px;
color: var(--brand-500);
}
.simple-step h3 {
margin-bottom: 10px; margin-bottom: 10px;
font-size: 19px; font-size: 22px;
letter-spacing: -0.025em; letter-spacing: -0.035em;
} }
.privacy-point p { .simple-step p {
margin-bottom: 0; max-width: 310px;
color: #a99fac; margin: 0;
font-size: 13px; color: var(--ink-muted);
font-size: 14px;
line-height: 1.65; line-height: 1.65;
} }
.privacy-policy-link { .trust-summary-section {
display: flex; padding: 112px 0;
background: #171319;
color: white;
}
.trust-summary {
display: grid;
grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
align-items: start;
gap: 100px;
}
.trust-summary-copy {
max-width: 520px;
}
.trust-summary-copy p {
margin-bottom: 28px;
color: #b7adb9;
font-size: 16px;
line-height: 1.72;
}
.simple-text-link {
display: inline-flex;
align-items: center; align-items: center;
justify-content: space-between; gap: 9px;
gap: 30px; color: #ddaaff;
margin-top: 38px; font-size: 14px;
padding-top: 27px; font-weight: 720;
}
.simple-text-link svg {
width: 17px;
}
.simple-text-link:hover {
color: white;
}
.trust-summary-list {
border-top: 1px solid var(--dark-line); border-top: 1px solid var(--dark-line);
} }
.privacy-policy-link p { .trust-summary-item {
display: grid;
grid-template-columns: 44px 1fr;
gap: 18px;
padding: 24px 0;
border-bottom: 1px solid var(--dark-line);
}
.trust-summary-item > span {
display: grid;
width: 42px;
height: 42px;
place-items: center;
border: 1px solid rgba(192, 132, 252, 0.24);
border-radius: 12px;
color: var(--brand-400);
}
.trust-summary-item svg {
width: 21px;
}
.trust-summary-item h3 {
margin-bottom: 6px;
font-size: 18px;
letter-spacing: -0.025em;
}
.trust-summary-item p {
max-width: 530px;
margin: 0; margin: 0;
color: #a99fac; color: #a99fac;
font-size: 13px; font-size: 13px;
line-height: 1.62;
} }
.privacy-policy-link a {
display: inline-flex;
align-items: center;
gap: 10px;
color: #dca7ff;
font-size: 14px;
font-weight: 750;
}
.privacy-policy-link svg {
width: 18px;
transition: transform 180ms ease;
}
.privacy-policy-link a:hover svg {
transform: translateX(4px);
}
.network-section {
padding: 150px 0;
background: #0f0c11;
color: white;
}
.network-layout {
display: grid;
grid-template-columns: minmax(0, 0.78fr) minmax(580px, 1.22fr);
align-items: center;
gap: 75px;
}
.network-copy h2 {
font-size: clamp(2.6rem, 4vw, 4.2rem);
}
.network-copy > p {
margin-bottom: 35px;
color: #aaa0ae;
font-size: 16px;
line-height: 1.75;
}
.network-copy blockquote {
position: relative;
margin: 0;
padding: 20px 0 20px 25px;
border-left: 2px solid var(--brand-500);
color: #f0e9f3;
font-family: var(--font-display);
font-size: 20px;
font-weight: 630;
line-height: 1.45;
}
.network-copy blockquote span {
position: absolute;
top: -14px;
left: 12px;
color: var(--brand-500);
font-family: Georgia, serif;
font-size: 44px;
opacity: 0.55;
}
.platforms-section {
overflow: hidden;
padding: 155px 0;
}
.platforms-layout {
display: grid;
grid-template-columns: minmax(0, 0.86fr) minmax(520px, 1.14fr);
align-items: center;
gap: 75px;
}
.platforms-copy .section-lead {
max-width: 570px;
margin-bottom: 29px;
}
.platform-feature-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.platform-feature-list span {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 11px;
border: 1px solid var(--line);
border-radius: 9px;
background: white;
color: var(--ink-soft);
font-size: 12px;
font-weight: 620;
}
.platform-feature-list svg {
width: 16px;
color: var(--brand-600);
}
.orbit-wrap {
min-width: 0;
}
.final-cta-section {
padding: 0 0 40px;
}
.final-cta {
position: relative;
overflow: hidden;
padding: 100px 50px;
border-radius: 31px;
background:
linear-gradient(125deg, rgba(255, 255, 255, 0.06), transparent 40%),
linear-gradient(135deg, #25172f, #171019 55%, #28143a);
color: white;
text-align: center;
}
.final-cta::after {
position: absolute;
inset: 0;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: inherit;
content: "";
pointer-events: none;
}
.final-cta h2 {
position: relative;
z-index: 1;
max-width: 850px;
margin-right: auto;
margin-left: auto;
font-size: clamp(2.8rem, 5vw, 5.4rem);
}
.final-cta > p {
position: relative;
z-index: 1;
max-width: 600px;
margin: 0 auto 31px;
color: #bfb4c4;
font-size: 16px;
}
.final-cta > .kicker {
position: relative;
z-index: 1;
}
.final-cta-actions {
position: relative;
z-index: 1;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
}
.final-cta-orb {
position: absolute;
border-radius: 50%;
background: var(--brand-500);
filter: blur(90px);
opacity: 0.18;
}
.final-cta-orb-one {
top: -140px;
left: -70px;
width: 380px;
height: 380px;
animation: cta-orb 12s ease-in-out infinite alternate;
}
.final-cta-orb-two {
right: -80px;
bottom: -200px;
width: 430px;
height: 430px;
animation: cta-orb 15s ease-in-out -4s infinite alternate-reverse;
}

View File

@@ -7,16 +7,8 @@ import "../components/header.css";
import "./home.css"; import "./home.css";
import "./footer.css"; import "./footer.css";
import "./privacy/privacy-document.css"; import "./privacy/privacy-document.css";
import "../components/hero-scene.css";
import "../components/hero-device.css";
import "../components/transfer-flow.css";
import "../components/invitation-scene.css";
import "../components/approval-scene.css";
import "../components/network-scene.css";
import "../components/platform-scene.css";
import "./responsive.css"; import "./responsive.css";
import "./privacy/privacy-responsive.css"; import "./privacy/privacy-responsive.css";
import "../components/illustration-motion.css";
export const metadata: Metadata = { export const metadata: Metadata = {
title: { title: {

View File

@@ -1,48 +1,54 @@
import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { ApprovalDemo } from "@/components/approval-demo";
import { HeroIllustration } from "@/components/hero-illustration";
import { Icon, type IconName } from "@/components/icons"; import { Icon, type IconName } from "@/components/icons";
import { InvitationShowcase } from "@/components/invitation-showcase"; import { PlatformIcon, supportedPlatforms } from "@/components/platform-icon";
import { NetworkIllustration } from "@/components/network-illustration";
import { PlatformOrbit } from "@/components/platform-orbit";
import { Reveal } from "@/components/reveal"; import { Reveal } from "@/components/reveal";
import { TransferFlow } from "@/components/transfer-flow";
const trustItems = [ const steps: Array<{
{ value: "No account", detail: "Open the app and start sharing" },
{ value: "No hosted copy", detail: "Your transfer is not parked in our cloud" },
{ value: "No silent overwrite", detail: "Existing files stay untouched" },
];
const privacyPoints: Array<{
icon: IconName; icon: IconName;
number: string; number: string;
title: string; title: string;
text: string; text: string;
}> = [ }> = [
{ {
icon: "lock", icon: "folder",
number: "01", number: "01",
title: "Encrypted in transit", title: "Choose what to send",
text: "Each connection is authenticated and end-to-end encrypted, including through a relay.", text: "Pick files, a batch, or a complete folder. VniDrop preserves the folder structure.",
},
{
icon: "qr",
number: "02",
title: "Share an invitation",
text: "Introduce the devices with a QR code, NFC tap, or portable .vnd invitation.",
}, },
{ {
icon: "shield", icon: "shield",
number: "02", number: "03",
title: "Approval by default", title: "Approve and transfer",
text: "An invitation helps devices meet. It is not automatic permission to download.", text: "The receiver asks first. Once approved, the files move over an authenticated encrypted connection.",
},
];
const trustItems: Array<{
icon: IconName;
title: string;
text: string;
}> = [
{
icon: "devices",
title: "Direct when possible",
text: "Devices connect directly when they can. An encrypted relay forwards traffic when they cannot.",
},
{
icon: "lock",
title: "No hosted copy",
text: "A relay is a route, not storage. Your transfer is never turned into a cloud download.",
}, },
{ {
icon: "verified", icon: "verified",
number: "03",
title: "Verified on arrival", title: "Verified on arrival",
text: "Content addressing checks that incoming bytes match exactly what was offered.", text: "Content addressing checks that the received bytes match exactly what you sent.",
},
{
icon: "stop",
number: "04",
title: "Revocable access",
text: "Stop a share at any time and the invitation can no longer be used.",
}, },
]; ];
@@ -67,224 +73,95 @@ export default function HomePage() {
between devices. between devices.
</h1> </h1>
<p className="hero-lead"> <p className="hero-lead">
Send files and folders directly when possible, with an end-to-end encrypted relay Send files and folders across your devicesdirect when possible, private by design,
when needed. No account. No hosted transfer copy. You decide who receives them. and always under your control.
</p> </p>
<div className="hero-actions"> <div className="hero-actions">
<a className="button button-primary" href="#how-it-works"> <a className="button button-primary" href="#how-it-works">
See how it works See how it works
<Icon name="arrow" /> <Icon name="arrow" />
</a> </a>
<a
className="button button-ghost"
href="https://github.com/vnidrop/vnidrop"
target="_blank"
rel="noreferrer"
>
<Icon name="github" />
Explore the project
</a>
</div> </div>
<div className="hero-platforms" aria-label="Supported platforms"> <div className="hero-platforms" aria-label="Supported platforms">
<span>Available across</span> <span>Available across</span>
<ul> <ul>
<li>Android</li> {supportedPlatforms.map((platform) => (
<li>iOS</li> <li key={platform} aria-label={platform} title={platform}>
<li>macOS</li> <PlatformIcon className="platform-glyph" platform={platform} />
<li>Windows</li> <span className="sr-only">{platform}</span>
<li>Linux</li> </li>
))}
</ul> </ul>
</div> </div>
</Reveal> </Reveal>
</div> </div>
<Reveal className="hero-art-reveal" delay={120}> <Reveal className="hero-art-reveal" delay={120}>
<HeroIllustration /> <figure className="hero-app-preview">
<Image
className="hero-app-preview-image"
src="/App.png"
width={2338}
height={1873}
sizes="(max-width: 920px) calc(100vw - 32px), 56vw"
alt="VniDrop running on desktop and iPhone, showing transfer review, receiver permissions, and invitation options."
priority
unoptimized
/>
</figure>
</Reveal> </Reveal>
</div> </div>
</section> </section>
<section className="trust-section" aria-label="VniDrop principles"> <section id="how-it-works" className="simple-section simple-flow-section">
<div className="page-shell trust-grid">
{trustItems.map((item, index) => (
<Reveal key={item.value} className="trust-item" delay={index * 70}>
<span className="trust-number">0{index + 1}</span>
<div>
<strong>{item.value}</strong>
<p>{item.detail}</p>
</div>
</Reveal>
))}
</div>
</section>
<div className="word-stream" aria-hidden="true">
<div>
<span>FILES</span><i></i><span>FOLDERS</span><i></i><span>PHOTOS</span><i></i>
<span>PROJECTS</span><i></i><span>FILES</span><i></i><span>FOLDERS</span><i></i>
<span>PHOTOS</span><i></i><span>PROJECTS</span><i></i>
</div>
</div>
<section id="how-it-works" className="section flow-section">
<div className="page-shell"> <div className="page-shell">
<Reveal className="section-heading section-heading-centered"> <Reveal className="simple-heading">
<span className="kicker">ONE DROP, FIVE MOMENTS</span> <span className="kicker">HOW IT WORKS</span>
<h2>A handoff that stays in your hands.</h2> <h2>Three steps. No account.</h2>
<p> <p>
VniDrop keeps the transfer understandable from first selection to final verified file. Choose the files, introduce the devices, and approve the handoff. VniDrop handles the
secure route.
</p> </p>
</Reveal> </Reveal>
<Reveal delay={100}> <div className="simple-steps">
<TransferFlow /> {steps.map((step) => (
</Reveal> <article key={step.number} className="simple-step">
</div> <div className="simple-step-top">
</section> <span>{step.number}</span>
<Icon name={step.icon} />
<section id="invitations" className="section invitation-section">
<div className="page-shell">
<Reveal className="section-heading section-heading-split">
<div>
<span className="kicker">MAKE THE INTRODUCTION</span>
<h2>One transfer. Three ways to meet.</h2>
</div>
<p>
The invitation is small enough to scan, tap, or send. Your files stay where they are
until a receiver connects and you allow the transfer.
</p>
</Reveal>
<Reveal delay={100}>
<InvitationShowcase />
</Reveal>
</div>
</section>
<section className="section approval-section">
<div className="page-shell approval-layout">
<Reveal className="approval-copy">
<span className="kicker">CONTROL IS BUILT IN</span>
<h2>A link is not permission.</h2>
<p className="section-lead">
Every new receiver knocks by default. Approve people you recognize, refuse the rest,
and follow progress for each device.
</p>
<ul className="check-list">
<li><Icon name="check" /> See the receiver before anything moves</li>
<li><Icon name="check" /> Cancel a transfer already in progress</li>
<li><Icon name="check" /> Stop sharing to revoke future access</li>
</ul>
<p className="approval-note">
Need an open handoff? An optional Anyone with this transfer mode is available for
non-sensitive files.
</p>
</Reveal>
<Reveal className="approval-demo-wrap" delay={120}>
<ApprovalDemo />
<span className="demo-hint"><Icon name="spark" /> Try the controls</span>
</Reveal>
</div>
</section>
<section id="privacy" className="privacy-principles-section">
<div className="page-shell">
<Reveal className="privacy-intro">
<span className="kicker kicker-light">PRIVACY BY DESIGN</span>
<h2>The file never becomes our file.</h2>
<p>
VniDrop coordinates a secure handoff. It does not turn your transfer into a hosted
download sitting on someone elses storage.
</p>
</Reveal>
<div className="privacy-point-grid">
{privacyPoints.map((point, index) => (
<Reveal key={point.number} className="privacy-point" delay={index * 70}>
<div className="privacy-point-top">
<span><Icon name={point.icon} /></span>
<small>{point.number}</small>
</div> </div>
<h3>{point.title}</h3> <h3>{step.title}</h3>
<p>{point.text}</p> <p>{step.text}</p>
</Reveal> </article>
))} ))}
</div> </div>
<Reveal className="privacy-policy-link" delay={120}> </div>
<p>Diagnostics are optional and exclude invitations, file paths, and transfer contents.</p> </section>
<Link href="/privacy/">
Read the full privacy policy <section id="privacy" className="trust-summary-section">
<div className="page-shell trust-summary">
<Reveal className="trust-summary-copy">
<span className="kicker kicker-light">PRIVACY BY DESIGN</span>
<h2>What VniDrop doesand doesnt do.</h2>
<p>
The transfer happens between devices. These are the details that matter when you
decide what to share and who can receive it.
</p>
<Link className="simple-text-link" href="/privacy/">
Read the privacy policy
<Icon name="arrow" /> <Icon name="arrow" />
</Link> </Link>
</Reveal> </Reveal>
</div> <div className="trust-summary-list">
</section> {trustItems.map((item) => (
<article key={item.title} className="trust-summary-item">
<section className="network-section"> <span><Icon name={item.icon} /></span>
<div className="page-shell network-layout"> <div>
<Reveal className="network-copy"> <h3>{item.title}</h3>
<span className="kicker kicker-light">DIRECT WHEN POSSIBLE</span> <p>{item.text}</p>
<h2>The shortest safe path, chosen automatically.</h2> </div>
<p> </article>
VniDrop tries to connect devices directlyeven across home routers and mobile ))}
networks. If that path is not available, a relay forwards the same encrypted </div>
connection without becoming a file store.
</p>
<blockquote>
<span></span>
The relay can move the envelope. It cannot open it.
</blockquote>
</Reveal>
<Reveal delay={120}>
<NetworkIllustration />
</Reveal>
</div>
</section>
<section id="platforms" className="section platforms-section">
<div className="page-shell platforms-layout">
<Reveal className="platforms-copy">
<span className="kicker">ONE FAMILIAR HANDOFF</span>
<h2>At home on every screen.</h2>
<p className="section-lead">
Android, iOS, macOS, Windows, and Linux share one clear transfer experience while each
platform keeps control of its own file picker and save destination.
</p>
<div className="platform-feature-list">
<span><Icon name="devices" /> Cross-platform by design</span>
<span><Icon name="folder" /> Native file destinations</span>
<span><Icon name="code" /> Open-source core</span>
</div>
</Reveal>
<Reveal className="orbit-wrap" delay={100}>
<PlatformOrbit />
</Reveal>
</div>
</section>
<section className="final-cta-section">
<div className="page-shell">
<Reveal className="final-cta">
<div className="final-cta-orb final-cta-orb-one" aria-hidden="true" />
<div className="final-cta-orb final-cta-orb-two" aria-hidden="true" />
<span className="kicker kicker-light">OPEN SOURCE · APACHE 2.0</span>
<h2>Drop the cloud holding pen. Keep the control.</h2>
<p>
VniDrop is in early development. Explore the code, build the app, and help shape a
more direct way to share.
</p>
<div className="final-cta-actions">
<a
className="button button-light"
href="https://github.com/vnidrop/vnidrop"
target="_blank"
rel="noreferrer"
>
<Icon name="github" />
View the source
</a>
<Link className="button button-dark-ghost" href="/privacy/">
Privacy policy
<Icon name="arrow" />
</Link>
</div>
</Reveal>
</div> </div>
</section> </section>
</main> </main>

View File

@@ -1,8 +1,4 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link";
import { BrandMark } from "@/components/brand";
import { Icon, type IconName } from "@/components/icons";
import { Reveal } from "@/components/reveal";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Privacy policy", title: "Privacy policy",
@@ -10,13 +6,6 @@ export const metadata: Metadata = {
"How VniDrop handles transfers, local app data, optional diagnostics, bug reports, and website visits.", "How VniDrop handles transfers, local app data, optional diagnostics, bug reports, and website visits.",
}; };
const summaries: Array<{ icon: IconName; label: string; value: string }> = [
{ icon: "devices", label: "Transfers", value: "Device to device" },
{ icon: "globe", label: "Accounts", value: "None" },
{ icon: "shield", label: "Diagnostics", value: "Optional" },
{ icon: "folder", label: "Hosted file copy", value: "None" },
];
const sections = [ const sections = [
["scope", "Scope"], ["scope", "Scope"],
["transfers", "Transfers"], ["transfers", "Transfers"],
@@ -36,55 +25,13 @@ export default function PrivacyPage() {
return ( return (
<main id="main-content" className="privacy-page"> <main id="main-content" className="privacy-page">
<section className="privacy-hero"> <section className="privacy-hero">
<div className="privacy-hero-grid" aria-hidden="true" /> <div className="page-shell privacy-hero-inner">
<div className="page-shell privacy-hero-layout"> <h1>Privacy Policy</h1>
<Reveal className="privacy-hero-copy"> <p>
<Link className="privacy-back" href="/"> This policy explains what moves between devices, what stays local, and what is sent
<Icon name="arrow" /> only when you choose to share diagnostics or a bug report.
Back to VniDrop </p>
</Link> <p className="privacy-meta">Effective July 16, 2026 · Version 1.0</p>
<span className="kicker">PRIVACY POLICY</span>
<h1>Clear by design.<br />Specific by default.</h1>
<p>
This policy explains what moves between devices, what stays local, and what is sent
only when you choose to share diagnostics or a bug report.
</p>
<div className="privacy-meta">
<span>Effective July 16, 2026</span>
<i />
<span>Version 1.0</span>
</div>
</Reveal>
<Reveal className="privacy-hero-art" delay={100}>
<div className="privacy-art-orbit privacy-art-orbit-one" aria-hidden="true" />
<div className="privacy-art-orbit privacy-art-orbit-two" aria-hidden="true" />
<div className="privacy-mark-card" aria-hidden="true">
<span className="privacy-mark-glow" />
<BrandMark />
<span className="privacy-mark-lock"><Icon name="lock" /></span>
</div>
<div className="privacy-data-pill privacy-data-pill-one" aria-hidden="true">
<Icon name="file" /> CONTENT
</div>
<div className="privacy-data-pill privacy-data-pill-two" aria-hidden="true">
<Icon name="shield" /> APPROVAL
</div>
<div className="privacy-data-pill privacy-data-pill-three" aria-hidden="true">
<Icon name="verified" /> VERIFIED
</div>
</Reveal>
</div>
</section>
<section className="privacy-summary" aria-label="Privacy summary">
<div className="page-shell privacy-summary-grid">
{summaries.map((summary, index) => (
<Reveal key={summary.label} className="privacy-summary-card" delay={index * 60}>
<span><Icon name={summary.icon} /></span>
<small>{summary.label}</small>
<strong>{summary.value}</strong>
</Reveal>
))}
</div> </div>
</section> </section>
@@ -93,30 +40,27 @@ export default function PrivacyPage() {
<aside className="privacy-toc"> <aside className="privacy-toc">
<p>On this page</p> <p>On this page</p>
<nav aria-label="Privacy policy sections"> <nav aria-label="Privacy policy sections">
{sections.map(([id, label], index) => ( <ol>
<a key={id} href={`#${id}`}> {sections.map(([id, label]) => (
<span>{String(index + 1).padStart(2, "0")}</span> <li key={id}>
{label} <a href={`#${id}`}>{label}</a>
</a> </li>
))} ))}
</ol>
</nav> </nav>
</aside> </aside>
<article className="privacy-document"> <article className="privacy-document">
<div className="privacy-callout"> <div className="privacy-callout">
<Icon name="spark" /> <strong>The short version</strong>
<div> <p>
<strong>The short version</strong> VniDrop has no user accounts and does not upload your transfer to a VniDrop file
<p> store. Files travel over an authenticated, end-to-end encrypted connection.
VniDrop has no user accounts and does not upload your transfer to a VniDrop file Product diagnostics are opt-in; a bug report is sent only when you submit one.
store. Files travel over an authenticated, end-to-end encrypted connection. </p>
Product diagnostics are opt-in; a bug report is sent only when you submit one.
</p>
</div>
</div> </div>
<section id="scope" className="policy-section"> <section id="scope" className="policy-section">
<span className="policy-number">01</span>
<h2>Scope and who VniDrop means</h2> <h2>Scope and who VniDrop means</h2>
<p> <p>
This policy covers the official VniDrop website, the VniDrop applications for This policy covers the official VniDrop website, the VniDrop applications for
@@ -132,7 +76,6 @@ export default function PrivacyPage() {
</section> </section>
<section id="transfers" className="policy-section"> <section id="transfers" className="policy-section">
<span className="policy-number">02</span>
<h2>What happens during a transfer</h2> <h2>What happens during a transfer</h2>
<h3>File contents</h3> <h3>File contents</h3>
<p> <p>
@@ -159,7 +102,6 @@ export default function PrivacyPage() {
encrypted transfer contents. encrypted transfer contents.
</p> </p>
<div className="policy-note"> <div className="policy-note">
<Icon name="shield" />
<p> <p>
Approval is required by default. If the sender selects Anyone with this transfer, Approval is required by default. If the sender selects Anyone with this transfer,
anyone holding the invitation may receive the files until sharing stops. anyone holding the invitation may receive the files until sharing stops.
@@ -168,7 +110,6 @@ export default function PrivacyPage() {
</section> </section>
<section id="local-data" className="policy-section"> <section id="local-data" className="policy-section">
<span className="policy-number">03</span>
<h2>Information kept on your device</h2> <h2>Information kept on your device</h2>
<p>VniDrop stores the information needed to operate the app locally, including:</p> <p>VniDrop stores the information needed to operate the app locally, including:</p>
<ul> <ul>
@@ -187,7 +128,6 @@ export default function PrivacyPage() {
</section> </section>
<section id="diagnostics" className="policy-section"> <section id="diagnostics" className="policy-section">
<span className="policy-number">04</span>
<h2>Optional diagnostics and bug reports</h2> <h2>Optional diagnostics and bug reports</h2>
<h3>Automatic product diagnostics</h3> <h3>Automatic product diagnostics</h3>
<p> <p>
@@ -217,7 +157,6 @@ export default function PrivacyPage() {
</section> </section>
<section id="website" className="policy-section"> <section id="website" className="policy-section">
<span className="policy-number">05</span>
<h2>The VniDrop website</h2> <h2>The VniDrop website</h2>
<p> <p>
This website is a static product site. It does not provide an account, contact form, This website is a static product site. It does not provide an account, contact form,
@@ -235,14 +174,25 @@ export default function PrivacyPage() {
</section> </section>
<section id="permissions" className="policy-section"> <section id="permissions" className="policy-section">
<span className="policy-number">06</span>
<h2>Device permissions</h2> <h2>Device permissions</h2>
<div className="permission-grid"> <dl className="permission-list">
<div><Icon name="folder" /><strong>Files & folders</strong><p>Choose what to send and where received files are saved.</p></div> <div>
<div><Icon name="qr" /><strong>Camera / scanner</strong><p>Scan a QR invitation when you choose that receive method.</p></div> <dt>Files &amp; folders</dt>
<div><Icon name="nfc" /><strong>NFC</strong><p>Read or write an invitation through a compatible NFC tag.</p></div> <dd>Choose what to send and where received files are saved.</dd>
<div><Icon name="devices" /><strong>Network & notifications</strong><p>Connect peers and alert you to background receiver requests.</p></div> </div>
</div> <div>
<dt>Camera / scanner</dt>
<dd>Scan a QR invitation when you choose that receive method.</dd>
</div>
<div>
<dt>NFC</dt>
<dd>Read or write an invitation through a compatible NFC tag.</dd>
</div>
<div>
<dt>Network &amp; notifications</dt>
<dd>Connect peers and alert you to background receiver requests.</dd>
</div>
</dl>
<p> <p>
VniDrop requests a platform permission only for the related feature. On Android, QR VniDrop requests a platform permission only for the related feature. On Android, QR
scanning may be provided through Google Play services Code Scanner. Platform-level scanning may be provided through Google Play services Code Scanner. Platform-level
@@ -251,54 +201,100 @@ export default function PrivacyPage() {
</section> </section>
<section id="providers" className="policy-section"> <section id="providers" className="policy-section">
<span className="policy-number">07</span>
<h2>Infrastructure and external services</h2> <h2>Infrastructure and external services</h2>
<div className="provider-list"> <dl className="provider-list">
<div> <div>
<span>I</span> <dt>Iroh / public relay operators</dt>
<div><strong>Iroh / public relay operators</strong><p>Device discovery, connection establishment, and encrypted relay fallback. Relays process connection metadata but cannot decrypt transfer contents.</p></div> <dd>
Device discovery, connection establishment, and encrypted relay fallback.
Relays process connection metadata but cannot decrypt transfer contents.
</dd>
</div> </div>
<div> <div>
<span>C</span> <dt>Cloudflare</dt>
<div><strong>Cloudflare</strong><p>The projects diagnostics design uses Cloudflare Workers, D1, and R2. Cloudflare also processes source IPs for request delivery and abuse controls.</p></div> <dd>
The projects diagnostics design uses Cloudflare Workers, D1, and R2.
Cloudflare also processes source IPs for request delivery and abuse controls.
</dd>
</div> </div>
<div> <div>
<span>G</span> <dt>Google Play services</dt>
<div><strong>Google Play services</strong><p>May provide the QR code scanner on supported Android devices when you choose to scan an invitation.</p></div> <dd>
May provide the QR code scanner on supported Android devices when you choose to
scan an invitation.
</dd>
</div> </div>
<div> <div>
<span>GH</span> <dt>GitHub</dt>
<div><strong>GitHub</strong><p>Hosts the source repository, issue tracker, and external pages linked from this site. GitHubs own privacy terms apply after you follow those links.</p></div> <dd>
Hosts the source repository, issue tracker, and external pages linked from this
site. GitHubs own privacy terms apply after you follow those links.
</dd>
</div> </div>
</div> </dl>
<p className="provider-links"> <p className="provider-links">
Provider policies: {" "} Provider policies:{" "}
<a href="https://services.iroh.computer/legal/privacy" target="_blank" rel="noreferrer">Iroh</a>, {" "} <a
<a href="https://www.cloudflare.com/policies/privacy/" target="_blank" rel="noreferrer">Cloudflare</a>, {" "} href="https://services.iroh.computer/legal/privacy"
<a href="https://policies.google.com/privacy" target="_blank" rel="noreferrer">Google</a>, and {" "} target="_blank"
<a href="https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement" target="_blank" rel="noreferrer">GitHub</a>. rel="noreferrer"
>
Iroh
</a>
,{" "}
<a
href="https://www.cloudflare.com/policies/privacy/"
target="_blank"
rel="noreferrer"
>
Cloudflare
</a>
,{" "}
<a href="https://policies.google.com/privacy" target="_blank" rel="noreferrer">
Google
</a>
, and{" "}
<a
href="https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement"
target="_blank"
rel="noreferrer"
>
GitHub
</a>
.
</p> </p>
</section> </section>
<section id="retention" className="policy-section"> <section id="retention" className="policy-section">
<span className="policy-number">08</span>
<h2>Retention and deletion</h2> <h2>Retention and deletion</h2>
<div className="retention-table" role="table" aria-label="Data retention periods"> <div className="retention-table-wrap">
<div className="retention-row retention-head" role="row"> <table className="retention-table">
<span role="columnheader">Data</span><span role="columnheader">Typical retention</span> <caption className="sr-only">Data retention periods</caption>
</div> <thead>
<div className="retention-row" role="row"> <tr>
<span role="cell">Transfer history and settings</span><span role="cell">Until you delete them, clear app data, or uninstall</span> <th scope="col">Data</th>
</div> <th scope="col">Typical retention</th>
<div className="retention-row" role="row"> </tr>
<span role="cell">Pending local crash reports</span><span role="cell">Up to 30 days and 20 reports; deleted when diagnostics is disabled</span> </thead>
</div> <tbody>
<div className="retention-row" role="row"> <tr>
<span role="cell">Server diagnostics and bug reports</span><span role="cell">The current project configuration is 90 days, with scheduled deletion</span> <th scope="row">Transfer history and settings</th>
</div> <td>Until you delete them, clear app data, or uninstall</td>
<div className="retention-row" role="row"> </tr>
<span role="cell">Downloaded files</span><span role="cell">Until you delete them through your operating system</span> <tr>
</div> <th scope="row">Pending local crash reports</th>
<td>Up to 30 days and 20 reports; deleted when diagnostics is disabled</td>
</tr>
<tr>
<th scope="row">Server diagnostics and bug reports</th>
<td>The current project configuration is 90 days, with scheduled deletion</td>
</tr>
<tr>
<th scope="row">Downloaded files</th>
<td>Until you delete them through your operating system</td>
</tr>
</tbody>
</table>
</div> </div>
<p> <p>
Operational backups, provider logs, and deletion backlogs may persist briefly beyond Operational backups, provider logs, and deletion backlogs may persist briefly beyond
@@ -309,14 +305,22 @@ export default function PrivacyPage() {
</section> </section>
<section id="choices" className="policy-section"> <section id="choices" className="policy-section">
<span className="policy-number">09</span>
<h2>Your choices and rights</h2> <h2>Your choices and rights</h2>
<ul> <ul>
<li>Enable or disable Share diagnostics in VniDrop settings.</li> <li>Enable or disable Share diagnostics in VniDrop settings.</li>
<li>Submit a bug report only when you choose, omit contact information, and exclude logs.</li> <li>
Submit a bug report only when you choose, omit contact information, and exclude
logs.
</li>
<li>Approve or refuse each receiver, cancel a transfer, or stop sharing.</li> <li>Approve or refuse each receiver, cancel a transfer, or stop sharing.</li>
<li>Delete individual transfer history or clear completed, failed, and cancelled receive history.</li> <li>
<li>Delete downloaded files using your operating system, or clear all app data by uninstalling or resetting the app.</li> Delete individual transfer history or clear completed, failed, and cancelled
receive history.
</li>
<li>
Delete downloaded files using your operating system, or clear all app data by
uninstalling or resetting the app.
</li>
</ul> </ul>
<p> <p>
Depending on where you live, privacy law may provide rights to access, correct, Depending on where you live, privacy law may provide rights to access, correct,
@@ -328,7 +332,6 @@ export default function PrivacyPage() {
</section> </section>
<section id="security" className="policy-section"> <section id="security" className="policy-section">
<span className="policy-number">10</span>
<h2>Security</h2> <h2>Security</h2>
<p> <p>
VniDrop uses authenticated end-to-end encrypted connections, content verification, VniDrop uses authenticated end-to-end encrypted connections, content verification,
@@ -338,13 +341,19 @@ export default function PrivacyPage() {
updated, and stop sharing when a transfer is finished. updated, and stop sharing when a transfer is finished.
</p> </p>
<p> <p>
Please report a suspected vulnerability through the private process in the {" "} Please report a suspected vulnerability through the private process in the{" "}
<a href="https://github.com/vnidrop/vnidrop/blob/master/SECURITY.md" target="_blank" rel="noreferrer">VniDrop security policy</a>, not in a public issue. <a
href="https://github.com/vnidrop/vnidrop/blob/master/SECURITY.md"
target="_blank"
rel="noreferrer"
>
VniDrop security policy
</a>
, not in a public issue.
</p> </p>
</section> </section>
<section id="changes" className="policy-section"> <section id="changes" className="policy-section">
<span className="policy-number">11</span>
<h2>Changes to this policy</h2> <h2>Changes to this policy</h2>
<p> <p>
VniDrop is in early development. Features and data practices may change. When this VniDrop is in early development. Features and data practices may change. When this
@@ -355,7 +364,6 @@ export default function PrivacyPage() {
</section> </section>
<section id="contact" className="policy-section policy-contact"> <section id="contact" className="policy-section policy-contact">
<span className="policy-number">12</span>
<h2>Contact</h2> <h2>Contact</h2>
<p> <p>
VniDrop is currently maintained as an open-source project and does not yet publish a VniDrop is currently maintained as an open-source project and does not yet publish a
@@ -364,13 +372,12 @@ export default function PrivacyPage() {
credentials, or other sensitive information in a public issue. credentials, or other sensitive information in a public issue.
</p> </p>
<a <a
className="button button-primary" className="privacy-contact-link"
href="https://github.com/vnidrop/vnidrop/issues/new" href="https://github.com/vnidrop/vnidrop/issues/new"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
Contact the maintainers Contact the maintainers
<Icon name="arrow" />
</a> </a>
</section> </section>
</article> </article>

View File

@@ -1,659 +1,288 @@
.privacy-page { .privacy-page {
background: #fcfbfd; background: var(--surface);
} }
.privacy-hero { .privacy-hero {
position: relative; padding: 158px 0 72px;
overflow: hidden; border-bottom: 1px solid var(--line);
padding: 165px 0 105px; text-align: center;
background:
radial-gradient(circle at 80% 20%, rgba(192, 132, 252, 0.2), transparent 30%),
linear-gradient(180deg, #fdfcfe, #f7f3f9);
} }
.privacy-hero-grid { .privacy-hero-inner {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(114, 84, 128, 0.055) 1px, transparent 1px),
linear-gradient(90deg, rgba(114, 84, 128, 0.055) 1px, transparent 1px);
background-size: 32px 32px;
mask-image: linear-gradient(to bottom, black, transparent 90%);
}
.privacy-hero-layout {
position: relative;
display: grid;
grid-template-columns: minmax(0, 0.92fr) minmax(430px, 0.72fr);
align-items: center;
gap: 95px;
}
.privacy-back {
display: flex; display: flex;
width: max-content; flex-direction: column;
align-items: center; align-items: center;
gap: 8px;
margin-bottom: 42px;
color: var(--ink-muted);
font-size: 12px;
font-weight: 650;
} }
.privacy-hero-copy > .kicker { .privacy-hero h1 {
display: block; margin-bottom: 22px;
width: max-content; font-size: clamp(3.25rem, 5.2vw, 4.5rem);
font-weight: 700;
letter-spacing: -0.055em;
line-height: 1;
} }
.privacy-back svg { .privacy-hero-inner > p:not(.privacy-meta) {
width: 16px; max-width: 640px;
transform: rotate(180deg); margin-bottom: 24px;
transition: transform 180ms ease;
}
.privacy-back:hover {
color: var(--brand-600);
}
.privacy-back:hover svg {
transform: rotate(180deg) translateX(4px);
}
.privacy-hero-copy h1 {
margin: 0 0 25px;
font-size: clamp(3.7rem, 6.2vw, 6.5rem);
font-weight: 760;
letter-spacing: -0.075em;
line-height: 0.98;
}
.privacy-hero-copy > p {
max-width: 680px;
margin-bottom: 28px;
color: var(--ink-soft); color: var(--ink-soft);
font-size: 18px; font-size: 16px;
line-height: 1.72; line-height: 1.7;
} }
.privacy-meta { .privacy-meta {
display: flex; margin: 0;
align-items: center;
gap: 12px;
color: var(--ink-muted); color: var(--ink-muted);
font-family: var(--font-mono); font-size: 13px;
font-size: 9px;
font-weight: 700;
letter-spacing: 0.07em;
text-transform: uppercase;
}
.privacy-meta i {
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--brand-500);
}
.privacy-hero-art {
position: relative;
min-height: 435px;
}
.privacy-art-orbit {
position: absolute;
top: 50%;
left: 50%;
border: 1px solid rgba(168, 85, 247, 0.18);
border-radius: 50%;
transform: translate(-50%, -50%);
}
.privacy-art-orbit::before,
.privacy-art-orbit::after {
position: absolute;
border-radius: 50%;
background: var(--brand-400);
content: "";
}
.privacy-art-orbit-one {
width: 420px;
height: 420px;
animation: privacy-orbit 28s linear infinite;
}
.privacy-art-orbit-one::before {
top: 11%;
left: 16%;
width: 7px;
height: 7px;
box-shadow: 0 0 0 7px rgba(168, 85, 247, 0.09);
}
.privacy-art-orbit-one::after {
right: 8%;
bottom: 25%;
width: 5px;
height: 5px;
}
.privacy-art-orbit-two {
width: 295px;
height: 295px;
border-style: dashed;
animation: privacy-orbit 21s linear infinite reverse;
}
.privacy-art-orbit-two::before {
top: 48%;
left: -4px;
width: 7px;
height: 7px;
}
.privacy-art-orbit-two::after {
top: 4%;
right: 22%;
width: 5px;
height: 5px;
}
.privacy-mark-card {
position: absolute;
z-index: 2;
top: 50%;
left: 50%;
display: grid;
width: 180px;
height: 180px;
place-items: center;
border: 1px solid #dfd2e6;
border-radius: 45px;
background: rgba(255, 255, 255, 0.91);
box-shadow: 0 35px 90px rgba(65, 30, 80, 0.17);
transform: translate(-50%, -50%) rotate(-3deg);
backdrop-filter: blur(14px);
}
.privacy-mark-card > img {
position: relative;
z-index: 2;
width: 112px;
height: 112px;
}
.privacy-mark-glow {
position: absolute;
inset: -35px;
border-radius: 50%;
background: rgba(168, 85, 247, 0.2);
filter: blur(42px);
animation: privacy-glow 5s ease-in-out infinite;
}
.privacy-mark-lock {
position: absolute;
right: -12px;
bottom: -10px;
display: grid;
width: 49px;
height: 49px;
place-items: center;
border: 4px solid #f8f4fa;
border-radius: 15px;
background: var(--ink);
box-shadow: 0 12px 30px rgba(42, 25, 50, 0.2);
color: white;
transform: rotate(3deg);
}
.privacy-mark-lock svg {
width: 21px;
}
.privacy-data-pill {
position: absolute;
z-index: 4;
display: inline-flex;
align-items: center;
gap: 7px;
padding: 8px 11px;
border: 1px solid #dfd3e4;
border-radius: 10px;
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 12px 35px rgba(58, 33, 69, 0.11);
color: var(--brand-600);
font-family: var(--font-mono);
font-size: 8px;
font-weight: 800;
letter-spacing: 0.07em;
backdrop-filter: blur(10px);
animation: privacy-pill 5s ease-in-out infinite;
}
.privacy-data-pill svg {
width: 15px;
}
.privacy-data-pill-one {
top: 11%;
left: 4%;
transform: rotate(-5deg);
}
.privacy-data-pill-two {
top: 29%;
right: -2%;
transform: rotate(4deg);
animation-delay: -2s;
}
.privacy-data-pill-three {
bottom: 6%;
left: 7%;
transform: rotate(3deg);
animation-delay: -3.5s;
}
.privacy-summary {
position: relative;
z-index: 4;
margin-top: -37px;
}
.privacy-summary-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
overflow: hidden;
border: 1px solid var(--line);
border-radius: 22px;
background: rgba(255, 255, 255, 0.95);
box-shadow: var(--shadow-lg);
backdrop-filter: blur(12px);
}
.privacy-summary-card {
display: grid;
min-height: 151px;
align-content: center;
padding: 25px 27px;
}
.privacy-summary-card + .privacy-summary-card {
border-left: 1px solid var(--line);
}
.privacy-summary-card > span {
display: grid;
width: 37px;
height: 37px;
margin-bottom: 17px;
place-items: center;
border-radius: 10px;
background: var(--brand-100);
color: var(--brand-600);
}
.privacy-summary-card svg {
width: 19px;
}
.privacy-summary-card small {
margin-bottom: 1px;
color: var(--ink-muted);
font-family: var(--font-mono);
font-size: 8px;
font-weight: 700;
letter-spacing: 0.07em;
text-transform: uppercase;
}
.privacy-summary-card strong {
font-family: var(--font-display);
font-size: 16px;
letter-spacing: -0.02em;
} }
.privacy-document-section { .privacy-document-section {
padding: 135px 0 155px; padding: 80px 0 144px;
} }
.privacy-document-layout { .privacy-document-layout {
display: grid; display: grid;
grid-template-columns: 210px minmax(0, 780px); grid-template-areas: "article toc";
grid-template-columns: minmax(0, 720px) 220px;
justify-content: center; justify-content: center;
gap: 105px; gap: 92px;
}
.privacy-document {
grid-area: article;
min-width: 0;
} }
.privacy-toc { .privacy-toc {
position: sticky; position: sticky;
top: 112px; top: 108px;
grid-area: toc;
align-self: start; align-self: start;
padding-left: 24px;
border-left: 1px solid var(--line);
} }
.privacy-toc > p { .privacy-toc > p {
margin-bottom: 17px; margin-bottom: 14px;
color: var(--ink); color: var(--ink);
font-family: var(--font-mono); font-size: 13px;
font-size: 9px; font-weight: 700;
font-weight: 800;
letter-spacing: 0.09em;
text-transform: uppercase;
} }
.privacy-toc nav { .privacy-toc ol {
display: flex; display: grid;
flex-direction: column; gap: 1px;
margin: 0;
padding: 0;
list-style: none;
} }
.privacy-toc a { .privacy-toc a {
display: grid; display: block;
grid-template-columns: 26px 1fr; padding: 5px 0;
align-items: center; color: var(--ink-muted);
gap: 6px; font-size: 12px;
padding: 7px 0; line-height: 1.45;
color: #79707e; transition: color 160ms ease;
font-size: 11px;
font-weight: 600;
transition: color 160ms ease, transform 160ms ease;
} }
.privacy-toc a:hover { .privacy-toc a:hover {
color: var(--brand-600); color: var(--ink);
transform: translateX(3px);
}
.privacy-toc a span {
color: #b5aabb;
font-family: var(--font-mono);
font-size: 8px;
}
.privacy-document {
min-width: 0;
} }
.privacy-callout { .privacy-callout {
display: grid; margin-bottom: 64px;
grid-template-columns: auto 1fr; padding: 0 0 40px 20px;
gap: 17px; border-bottom: 1px solid var(--line);
margin-bottom: 75px; border-left: 2px solid var(--ink);
padding: 25px;
border: 1px solid #dfd0e7;
border-radius: 17px;
background: linear-gradient(145deg, #fbf6ff, #f7f1fa);
}
.privacy-callout > svg {
width: 24px;
color: var(--brand-600);
} }
.privacy-callout strong { .privacy-callout strong {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 8px;
font-family: var(--font-display); font-family: var(--font-display);
font-size: 14px; font-size: 17px;
} }
.privacy-callout p { .privacy-callout p {
margin: 0; margin: 0;
color: var(--ink-soft); color: var(--ink-soft);
font-size: 12px; font-size: 15px;
line-height: 1.7; line-height: 1.75;
} }
.policy-section { .policy-section {
position: relative; scroll-margin-top: 104px;
scroll-margin-top: 110px; padding-bottom: 56px;
padding: 0 0 72px;
} }
.policy-section + .policy-section { .policy-section + .policy-section {
padding-top: 3px; padding-top: 56px;
border-top: 1px solid var(--line); border-top: 1px solid var(--line);
} }
.policy-number {
display: inline-flex;
margin-top: 0;
margin-bottom: 15px;
color: var(--brand-600);
font-family: var(--font-mono);
font-size: 9px;
font-weight: 800;
letter-spacing: 0.08em;
}
.policy-section + .policy-section .policy-number {
margin-top: 65px;
}
.policy-section h2 { .policy-section h2 {
margin-bottom: 27px; margin-bottom: 24px;
font-size: clamp(2rem, 3.3vw, 3.15rem); font-size: clamp(1.75rem, 2.7vw, 2rem);
font-weight: 720; font-weight: 680;
letter-spacing: -0.052em; letter-spacing: -0.035em;
line-height: 1.08; line-height: 1.22;
} }
.policy-section h3 { .policy-section h3 {
margin: 34px 0 10px; margin: 32px 0 10px;
font-size: 16px; font-size: 19px;
font-weight: 720; font-weight: 680;
letter-spacing: -0.018em; letter-spacing: -0.018em;
line-height: 1.35;
} }
.policy-section p, .policy-section p,
.policy-section li { .policy-section li,
color: #514956; .permission-list dd,
font-size: 14px; .provider-list dd {
line-height: 1.82; color: var(--ink-soft);
font-size: 15px;
line-height: 1.75;
} }
.policy-section p { .policy-section p {
margin-bottom: 19px; margin-bottom: 18px;
} }
.policy-section ul { .policy-section ul {
display: grid; display: grid;
gap: 8px; gap: 7px;
margin: 17px 0 22px; margin: 18px 0 24px;
padding-left: 22px; padding-left: 22px;
} }
.policy-section li::marker { .policy-section li::marker {
color: var(--brand-500); color: var(--ink-muted);
} }
.policy-section code { .policy-section code {
padding: 2px 6px; padding: 2px 5px;
border: 1px solid #e7dceb; border: 1px solid var(--line);
border-radius: 5px; border-radius: 4px;
background: #f8f3fa; background: var(--paper-warm);
color: var(--brand-700); color: var(--ink);
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 0.86em; font-size: 0.88em;
} }
.policy-section a:not(.button) { .policy-section a,
.privacy-contact-link {
color: var(--brand-700); color: var(--brand-700);
font-weight: 650; font-weight: 620;
text-decoration: underline; text-decoration: underline;
text-decoration-color: rgba(132, 47, 238, 0.28); text-decoration-color: rgba(111, 39, 233, 0.34);
text-underline-offset: 3px; text-underline-offset: 3px;
} }
.policy-note { .policy-section a:hover,
display: grid; .privacy-contact-link:hover {
grid-template-columns: auto 1fr; text-decoration-color: currentColor;
gap: 13px;
margin-top: 28px;
padding: 18px;
border: 1px solid #e2d6e8;
border-radius: 13px;
background: #faf7fb;
} }
.policy-note svg { .policy-note {
width: 21px; margin-top: 28px;
color: var(--brand-600); padding: 18px 20px;
border-left: 2px solid var(--line-strong);
background: var(--paper-warm);
} }
.policy-note p { .policy-note p {
margin: 0; margin: 0;
color: #5e5264; font-size: 14px;
font-size: 12px; line-height: 1.7;
line-height: 1.65;
}
.permission-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin: 30px 0;
}
.permission-grid > div {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
gap: 5px 12px;
padding: 17px;
border: 1px solid var(--line);
border-radius: 13px;
background: white;
}
.permission-grid svg {
width: 21px;
grid-row: 1 / 3;
color: var(--brand-600);
}
.permission-grid strong {
font-size: 11px;
}
.permission-grid p {
margin: 0;
color: var(--ink-muted);
font-size: 9px;
line-height: 1.5;
} }
.permission-list,
.provider-list { .provider-list {
display: grid;
gap: 9px;
margin: 28px 0; margin: 28px 0;
}
.provider-list > div {
display: grid;
grid-template-columns: auto 1fr;
gap: 15px;
padding: 17px;
border: 1px solid var(--line);
border-radius: 13px;
background: white;
}
.provider-list > div > span {
display: grid;
width: 38px;
height: 38px;
place-items: center;
border-radius: 10px;
background: var(--brand-100);
color: var(--brand-700);
font-family: var(--font-mono);
font-size: 9px;
font-weight: 850;
}
.provider-list strong {
display: block;
margin-bottom: 3px;
font-size: 11px;
}
.provider-list p {
margin: 0;
color: var(--ink-muted);
font-size: 10px;
line-height: 1.55;
}
.provider-links {
font-size: 11px !important;
}
.retention-table {
overflow: hidden;
margin: 28px 0;
border: 1px solid var(--line);
border-radius: 14px;
background: white;
}
.retention-row {
display: grid;
grid-template-columns: minmax(190px, 0.72fr) 1.28fr;
}
.retention-row + .retention-row {
border-top: 1px solid var(--line); border-top: 1px solid var(--line);
} }
.retention-row span { .permission-list > div,
padding: 14px 16px; .provider-list > div {
color: var(--ink-soft); display: grid;
font-size: 10px; grid-template-columns: minmax(150px, 0.42fr) 1fr;
gap: 24px;
padding: 17px 0;
border-bottom: 1px solid var(--line);
}
.permission-list dt,
.provider-list dt {
color: var(--ink);
font-size: 14px;
font-weight: 680;
line-height: 1.55; line-height: 1.55;
} }
.retention-row span + span { .permission-list dd,
border-left: 1px solid var(--line); .provider-list dd {
margin: 0;
font-size: 14px;
line-height: 1.65;
}
.provider-links {
font-size: 14px !important;
}
.retention-table-wrap {
overflow-x: auto;
margin: 28px 0;
border-top: 1px solid var(--line);
-webkit-overflow-scrolling: touch;
}
.retention-table {
width: 100%;
min-width: 600px;
border-collapse: collapse;
text-align: left;
}
.retention-table th,
.retention-table td {
padding: 15px 16px 15px 0;
border-bottom: 1px solid var(--line);
color: var(--ink-soft);
font-size: 13px;
font-weight: 400;
line-height: 1.6;
vertical-align: top;
}
.retention-table th:first-child {
width: 38%;
}
.retention-table thead th {
color: var(--ink-muted); color: var(--ink-muted);
} font-size: 11px;
font-weight: 700;
.retention-head { letter-spacing: 0.04em;
background: #f4eff6;
}
.retention-head span {
color: var(--ink) !important;
font-family: var(--font-mono);
font-size: 8px;
font-weight: 800;
letter-spacing: 0.07em;
text-transform: uppercase; text-transform: uppercase;
} }
.retention-table tbody th {
color: var(--ink);
font-weight: 620;
}
.policy-contact { .policy-contact {
overflow: hidden; padding-bottom: 0;
padding: 54px !important;
border: 1px solid #dfd0e7 !important;
border-radius: 21px;
background:
radial-gradient(circle at 100% 0%, rgba(168, 85, 247, 0.14), transparent 35%),
linear-gradient(145deg, #fbf7fe, #f6f0f9);
}
.policy-contact .policy-number {
margin-top: 0 !important;
}
.policy-contact h2 {
margin-bottom: 16px;
} }
.policy-contact p { .policy-contact p {
max-width: 620px; margin-bottom: 18px;
margin-bottom: 26px;
} }
.policy-contact .button { .privacy-contact-link::after {
color: white; margin-left: 5px;
text-decoration: none; content: "↗";
} }

View File

@@ -1,156 +1,89 @@
@keyframes privacy-orbit { @media (max-width: 940px) {
from { transform: translate(-50%, -50%) rotate(0); }
to { transform: translate(-50%, -50%) rotate(360deg); }
}
@keyframes privacy-glow {
0%, 100% { opacity: 0.5; transform: scale(0.84); }
50% { opacity: 1; transform: scale(1.14); }
}
@keyframes privacy-pill {
0%, 100% { margin-top: 0; }
50% { margin-top: -8px; }
}
@media (max-width: 1050px) {
.privacy-hero-layout {
grid-template-columns: minmax(0, 1fr) 390px;
gap: 45px;
}
.privacy-hero-copy h1 {
font-size: clamp(3.6rem, 6.8vw, 5.5rem);
}
.privacy-document-layout { .privacy-document-layout {
grid-template-columns: 180px minmax(0, 720px); grid-template-areas:
gap: 60px; "toc"
} "article";
} grid-template-columns: minmax(0, 720px);
gap: 56px;
@media (max-width: 820px) {
.privacy-hero {
padding-top: 135px;
}
.privacy-hero-layout {
grid-template-columns: 1fr;
}
.privacy-hero-copy {
max-width: 690px;
}
.privacy-hero-art {
width: min(100%, 520px);
margin: 0 auto;
}
.privacy-summary-grid {
grid-template-columns: 1fr 1fr;
}
.privacy-summary-card:nth-child(3) {
border-top: 1px solid var(--line);
border-left: 0;
}
.privacy-summary-card:nth-child(4) {
border-top: 1px solid var(--line);
}
.privacy-document-layout {
grid-template-columns: 1fr;
} }
.privacy-toc { .privacy-toc {
position: relative; position: static;
top: auto; padding: 0 0 28px;
padding-bottom: 25px;
border-bottom: 1px solid var(--line); border-bottom: 1px solid var(--line);
border-left: 0;
} }
.privacy-toc nav { .privacy-toc ol {
display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-columns: repeat(3, 1fr); column-gap: 24px;
} }
} }
@media (max-width: 560px) { @media (max-width: 640px) {
.privacy-hero { .privacy-hero {
padding: 120px 0 90px; padding: 122px 0 54px;
} }
.privacy-back { .privacy-hero h1 {
margin-bottom: 32px; margin-bottom: 18px;
font-size: clamp(2.55rem, 12vw, 3.4rem);
} }
.privacy-hero-copy h1 { .privacy-hero-inner > p:not(.privacy-meta) {
font-size: clamp(3rem, 15vw, 4.5rem);
}
.privacy-hero-copy > p {
font-size: 15px; font-size: 15px;
line-height: 1.65;
} }
.privacy-hero-art { .privacy-meta {
min-height: 360px; font-size: 12px;
transform: scale(0.86);
}
.privacy-summary-grid {
grid-template-columns: 1fr;
}
.privacy-summary-card {
min-height: 120px;
}
.privacy-summary-card + .privacy-summary-card {
border-top: 1px solid var(--line);
border-left: 0;
} }
.privacy-document-section { .privacy-document-section {
padding: 95px 0 105px; padding: 56px 0 96px;
} }
.privacy-toc nav { .privacy-document-layout {
grid-template-columns: 1fr 1fr; gap: 0;
} }
.privacy-callout { .privacy-toc {
padding: 20px;
}
.policy-section h2 {
font-size: 2.25rem;
}
.policy-section p,
.policy-section li {
font-size: 13px;
}
.permission-grid {
grid-template-columns: 1fr;
}
.retention-row {
grid-template-columns: 1fr;
}
.retention-row span + span {
padding-top: 0;
border-left: 0;
}
.retention-head {
display: none; display: none;
} }
.policy-contact { .privacy-callout {
padding: 35px 24px !important; margin-bottom: 48px;
padding: 0 0 32px 16px;
}
.privacy-callout p,
.policy-section p,
.policy-section li,
.permission-list dd,
.provider-list dd {
font-size: 14px;
}
.policy-section {
padding-bottom: 44px;
}
.policy-section + .policy-section {
padding-top: 44px;
}
.policy-section h2 {
font-size: 1.65rem;
}
.policy-section h3 {
margin-top: 28px;
font-size: 17px;
}
.permission-list > div,
.provider-list > div {
grid-template-columns: 1fr;
gap: 5px;
} }
} }

View File

@@ -3,15 +3,6 @@
to { transform: translate3d(24px, 18px, 0) rotate(8deg); } to { transform: translate3d(24px, 18px, 0) rotate(8deg); }
} }
@keyframes word-stream {
to { transform: translateX(-50%); }
}
@keyframes cta-orb {
from { transform: translate3d(-20px, -10px, 0) scale(0.9); }
to { transform: translate3d(40px, 25px, 0) scale(1.12); }
}
@media (max-width: 1120px) { @media (max-width: 1120px) {
.hero-layout { .hero-layout {
grid-template-columns: minmax(0, 0.88fr) minmax(470px, 1.12fr); grid-template-columns: minmax(0, 0.88fr) minmax(470px, 1.12fr);
@@ -21,18 +12,8 @@
font-size: clamp(3.2rem, 5.4vw, 4.5rem); font-size: clamp(3.2rem, 5.4vw, 4.5rem);
} }
.approval-layout, .trust-summary {
.network-layout, gap: 65px;
.platforms-layout {
gap: 55px;
}
.privacy-point-grid {
grid-template-columns: repeat(2, 1fr);
}
.privacy-point {
min-height: 230px;
} }
} }
@@ -62,46 +43,23 @@
justify-content: center; justify-content: center;
} }
.trust-grid { .hero-app-preview {
width: min(100%, 780px);
margin: 12px auto 0;
}
.simple-step {
padding-right: 22px;
padding-left: 22px;
}
.trust-summary {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 50px;
} }
.trust-item { .trust-summary-copy {
min-height: 102px; max-width: 680px;
}
.trust-item + .trust-item {
border-top: 1px solid var(--line);
border-left: 0;
}
.section-heading-split {
grid-template-columns: 1fr;
gap: 20px;
}
.section-heading-split p {
max-width: 640px;
}
.approval-layout,
.network-layout,
.platforms-layout {
grid-template-columns: 1fr;
}
.approval-copy,
.network-copy,
.platforms-copy {
max-width: 690px;
}
.network-layout {
gap: 75px;
}
.platforms-layout {
gap: 65px;
} }
} }
@@ -141,97 +99,73 @@
justify-content: center; justify-content: center;
} }
.trust-section { .hero-app-preview {
margin-top: -18px; width: calc(100% + 24px);
margin-left: -12px;
} }
.trust-grid { .simple-section,
border-radius: 18px; .trust-summary-section {
padding: 88px 0;
} }
.trust-item { .simple-heading {
padding: 20px; margin-bottom: 40px;
} }
.word-stream { .simple-heading h2,
padding-top: 70px; .trust-summary-copy h2 {
font-size: 3.6rem;
}
.section {
padding: 95px 0;
}
.section-heading {
margin-bottom: 42px;
}
.section-heading h2,
.privacy-intro h2,
.network-copy h2,
.platforms-copy h2,
.final-cta h2,
.approval-copy h2 {
font-size: clamp(2.35rem, 12vw, 3.5rem); font-size: clamp(2.35rem, 12vw, 3.5rem);
} }
.section-heading p, .simple-heading p,
.section-lead { .trust-summary-copy p {
font-size: 15px; font-size: 15px;
} }
.approval-section, .simple-steps {
.platforms-section,
.network-section,
.privacy-principles-section {
padding: 100px 0;
}
.approval-layout {
gap: 55px;
}
.privacy-point-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.privacy-point { .simple-step {
min-height: 0; min-height: 0;
padding: 28px 0 32px;
} }
.privacy-point-top { .simple-step + .simple-step {
margin-bottom: 30px; border-top: 1px solid var(--line);
border-left: 0;
} }
.privacy-policy-link { .simple-step-top {
align-items: flex-start; margin-bottom: 28px;
flex-direction: column;
} }
.final-cta-section { .trust-summary-item {
padding-bottom: 24px; grid-template-columns: 40px 1fr;
gap: 15px;
} }
.final-cta { .trust-summary-item > span {
padding: 76px 22px; width: 38px;
border-radius: 23px; height: 38px;
}
.final-cta-actions {
display: grid;
}
.footer-main {
grid-template-columns: 1fr;
}
.footer-links {
gap: 35px;
} }
.footer-inner,
.footer-bottom { .footer-bottom {
align-items: flex-start; align-items: flex-start;
flex-direction: column; flex-direction: column;
}
.footer-inner {
gap: 24px;
}
.footer-links {
flex-wrap: wrap;
}
.footer-bottom {
gap: 5px; gap: 5px;
} }
} }

View File

@@ -37,8 +37,11 @@ export function AppIcon({ className, title }: BrandAssetProps) {
export function Brand({ compact = false }: { compact?: boolean }) { export function Brand({ compact = false }: { compact?: boolean }) {
return ( return (
<Link className="brand" href="/" aria-label="VniDrop home"> <Link className="brand" href="/" aria-label="VniDrop home">
<BrandMark className="brand-mark-image" /> <span className="brand-primary">
{!compact && <span className="brand-name">VniDrop</span>} <BrandMark className="brand-mark-image" />
{!compact && <span className="brand-name">VniDrop</span>}
</span>
{!compact && <span className="brand-tagline">Send files directly. Stay in control.</span>}
</Link> </Link>
); );
} }

View File

@@ -20,10 +20,17 @@
} }
.brand { .brand {
display: inline-flex;
flex-direction: column;
align-items: flex-start;
width: max-content;
}
.brand-primary {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 3px; gap: 3px;
width: max-content; height: 47px;
} }
.brand-mark-image { .brand-mark-image {
@@ -42,6 +49,17 @@
letter-spacing: -0.045em; letter-spacing: -0.045em;
} }
.brand-tagline {
margin-top: -4px;
margin-left: 4px;
color: #756c79;
font-size: 9px;
font-weight: 580;
line-height: 1;
letter-spacing: -0.01em;
white-space: nowrap;
}
.header-github-button { .header-github-button {
display: inline-flex; display: inline-flex;
min-height: 40px; min-height: 40px;
@@ -57,15 +75,19 @@
font-size: 12px; font-size: 12px;
font-weight: 680; font-weight: 680;
line-height: 1; line-height: 1;
transition: background 160ms ease, border-color 160ms ease, box-shadow 160ms ease, animation: none;
transform 160ms ease; transform: none;
} }
.header-github-button:hover { .header-github-button:hover {
border-color: #403944; border-color: #6f6475;
background: #29242d; background: #302a34;
box-shadow: 0 10px 26px rgba(23, 20, 25, 0.17), inset 0 1px rgba(255, 255, 255, 0.12); box-shadow: 0 11px 28px rgba(23, 20, 25, 0.2), inset 0 0 0 1px rgba(255, 255, 255, 0.08);
transform: translateY(-1px); }
.header-github-button:focus-visible {
outline: 3px solid rgba(168, 85, 247, 0.28);
outline-offset: 3px;
} }
.header-github-button svg { .header-github-button svg {
@@ -85,10 +107,20 @@
height: 44px; height: 44px;
} }
.brand-primary {
height: 43px;
}
.brand-name { .brand-name {
font-size: 17px; font-size: 17px;
} }
.brand-tagline {
margin-top: -3px;
margin-left: 3px;
font-size: 8px;
}
.header-github-button { .header-github-button {
min-height: 38px; min-height: 38px;
gap: 7px; gap: 7px;
@@ -107,6 +139,10 @@
font-size: 16px; font-size: 16px;
} }
.brand-tagline {
font-size: 7.5px;
}
.header-github-button { .header-github-button {
min-height: 36px; min-height: 36px;
padding-inline: 10px; padding-inline: 10px;

View File

@@ -267,8 +267,9 @@
.platform-orbit { width: 112%; margin-left: -6%; } .platform-orbit { width: 112%; margin-left: -6%; }
.platform-center { width: 112px; height: 112px; border-radius: 30px; } .platform-center { width: 112px; height: 112px; border-radius: 30px; }
.platform-center svg { width: 68px; height: 68px; } .platform-center img { width: 68px; height: 68px; }
.platform-chip { padding: 8px 10px; font-size: 9px; } .platform-chip { width: 48px; height: 48px; padding: 0; }
.platform-chip .platform-glyph { width: 25px; height: 25px; }
.platform-chip-1 { left: 10%; } .platform-chip-1 { left: 10%; }
.platform-chip-2 { right: 0; } .platform-chip-2 { right: 0; }
.platform-chip-3 { right: -2%; } .platform-chip-3 { right: -2%; }

View File

@@ -0,0 +1,90 @@
import type { SVGProps } from "react";
export const supportedPlatforms = ["Android", "iOS", "macOS", "Windows", "Linux"] as const;
export type PlatformName = (typeof supportedPlatforms)[number];
type PlatformIconProps = Omit<SVGProps<SVGSVGElement>, "children"> & {
platform: PlatformName;
};
function AndroidIcon() {
return (
<>
<path
d="M7.25 8.15h9.5a1.5 1.5 0 0 1 1.5 1.5v6.1a1.5 1.5 0 0 1-1.5 1.5h-9.5a1.5 1.5 0 0 1-1.5-1.5v-6.1a1.5 1.5 0 0 1 1.5-1.5Z"
fill="currentColor"
/>
<path d="M8.15 8.1a3.95 3.95 0 0 1 7.7 0" fill="currentColor" />
<path d="m8.35 4.45-1.2-1.7M15.65 4.45l1.2-1.7" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" />
<path d="M4.2 10.05v5.15M19.8 10.05v5.15M8.5 17v3.05M15.5 17v3.05" stroke="currentColor" strokeWidth="2.1" strokeLinecap="round" />
<circle cx="9.5" cy="6.65" r="0.62" fill="white" />
<circle cx="14.5" cy="6.65" r="0.62" fill="white" />
</>
);
}
function AppleIcon() {
return (
<path
d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.32.07 2.24.76 3.01.82 1.15-.23 2.25-.89 3.44-.8 1.43.12 2.51.68 3.22 1.7-2.96 1.78-2.26 5.68.46 6.77-.54 1.42-1.24 2.83-2.13 4.48ZM12.03 7.25c-.15-2.13 1.59-3.89 3.58-4.06.27 2.46-2.23 4.3-3.58 4.06Z"
fill="currentColor"
/>
);
}
function MacIcon() {
return (
<>
<rect x="2.5" y="2.5" width="19" height="19" rx="5.5" fill="currentColor" />
<path d="M12 2.5v19" stroke="white" strokeOpacity="0.72" />
<path d="M8 9.15h.01M16 9.15h.01" stroke="white" strokeWidth="1.9" strokeLinecap="round" />
<path d="M7.1 15.2c2.7 1.45 7.1 1.45 9.8 0" stroke="white" strokeWidth="1.35" strokeLinecap="round" />
<path d="m13.55 6.25-2.45 8.3" stroke="white" strokeWidth="1.1" strokeLinecap="round" strokeOpacity="0.82" />
</>
);
}
function WindowsIcon() {
return (
<path
d="M3 5.15 11 4v7H3V5.15Zm9-1.3L21 2.5V11h-9V3.85ZM3 12h8v7l-8-1.15V12Zm9 0h9v8.5l-9-1.35V12Z"
fill="currentColor"
/>
);
}
function LinuxIcon() {
return (
<>
<ellipse cx="12" cy="12.6" rx="5.25" ry="7.55" fill="#242027" />
<ellipse cx="12" cy="14.55" rx="3.45" ry="4.7" fill="#f8f7f9" />
<ellipse cx="9.95" cy="8.45" rx="1.35" ry="1.8" fill="white" />
<ellipse cx="14.05" cy="8.45" rx="1.35" ry="1.8" fill="white" />
<circle cx="10.35" cy="8.65" r="0.55" fill="#242027" />
<circle cx="13.65" cy="8.65" r="0.55" fill="#242027" />
<path d="m9.8 10.15 2.2-1 2.2 1-2.2 2.1-2.2-2.1Z" fill="#f5b82e" />
<path d="M6.15 18.45c1.2-1 2.45-1.3 3.75-.75-.7 1.6-2.35 2.45-4.9 2.2.2-.6.58-1.08 1.15-1.45ZM17.85 18.45c-1.2-1-2.45-1.3-3.75-.75.7 1.6 2.35 2.45 4.9 2.2-.2-.6-.58-1.08-1.15-1.45Z" fill="#f5b82e" />
</>
);
}
export function PlatformIcon({ platform, className, ...props }: PlatformIconProps) {
return (
<svg
className={className}
data-platform={platform.toLowerCase()}
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
focusable="false"
{...props}
>
{platform === "Android" && <AndroidIcon />}
{platform === "iOS" && <AppleIcon />}
{platform === "macOS" && <MacIcon />}
{platform === "Windows" && <WindowsIcon />}
{platform === "Linux" && <LinuxIcon />}
</svg>
);
}

View File

@@ -1,23 +1,27 @@
import { BrandMark } from "@/components/brand"; import { BrandMark } from "@/components/brand";
import { PlatformIcon, supportedPlatforms } from "@/components/platform-icon";
const platforms = ["Android", "iOS", "macOS", "Windows", "Linux"];
export function PlatformOrbit() { export function PlatformOrbit() {
return ( return (
<div className="platform-orbit" aria-label="VniDrop supports Android, iOS, macOS, Windows, and Linux"> <div
className="platform-orbit"
role="img"
aria-label="VniDrop supports Android, iOS, macOS, Windows, and Linux"
>
<div className="platform-ring platform-ring-outer" aria-hidden="true" /> <div className="platform-ring platform-ring-outer" aria-hidden="true" />
<div className="platform-ring platform-ring-inner" aria-hidden="true" /> <div className="platform-ring platform-ring-inner" aria-hidden="true" />
<div className="platform-center" aria-hidden="true"> <div className="platform-center" aria-hidden="true">
<span className="platform-center-glow" /> <span className="platform-center-glow" />
<BrandMark /> <BrandMark />
</div> </div>
{platforms.map((platform, index) => ( {supportedPlatforms.map((platform, index) => (
<span <span
key={platform} key={platform}
className={`platform-chip platform-chip-${index + 1}`} className={`platform-chip platform-chip-${index + 1}`}
title={platform}
> >
<i /> <PlatformIcon className="platform-glyph" platform={platform} />
{platform} <span className="sr-only">{platform}</span>
</span> </span>
))} ))}
</div> </div>

View File

@@ -75,24 +75,20 @@
z-index: 4; z-index: 4;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 8px; justify-content: center;
padding: 10px 14px; width: 58px;
height: 58px;
border: 1px solid #e2d9e6; border: 1px solid #e2d9e6;
border-radius: 999px; border-radius: 18px;
background: rgba(255, 255, 255, 0.93); background: rgba(255, 255, 255, 0.93);
box-shadow: 0 12px 35px rgba(53, 34, 61, 0.1); box-shadow: 0 12px 35px rgba(53, 34, 61, 0.1);
color: #4b4350; color: #4b4350;
font-size: 11px;
font-weight: 700;
animation: platform-chip-float 5s ease-in-out infinite; animation: platform-chip-float 5s ease-in-out infinite;
} }
.platform-chip i { .platform-chip .platform-glyph {
width: 7px; width: 30px;
height: 7px; height: 30px;
border-radius: 50%;
background: var(--brand-500);
box-shadow: 0 0 0 4px var(--brand-100);
} }
.platform-chip-1 { top: 8%; left: 17%; animation-delay: -1s; } .platform-chip-1 { top: 8%; left: 17%; animation-delay: -1s; }
@@ -100,3 +96,8 @@
.platform-chip-3 { right: 1%; bottom: 22%; animation-delay: -4s; } .platform-chip-3 { right: 1%; bottom: 22%; animation-delay: -4s; }
.platform-chip-4 { bottom: 7%; left: 24%; animation-delay: -0.5s; } .platform-chip-4 { bottom: 7%; left: 24%; animation-delay: -0.5s; }
.platform-chip-5 { top: 45%; left: 0; animation-delay: -3.2s; } .platform-chip-5 { top: 45%; left: 0; animation-delay: -3.2s; }
.platform-glyph[data-platform="android"] { color: #2fbf71; }
.platform-glyph[data-platform="ios"] { color: #201c23; }
.platform-glyph[data-platform="macos"] { color: #1687f8; }
.platform-glyph[data-platform="windows"] { color: #008de5; }

View File

@@ -5,38 +5,24 @@ import { Icon } from "@/components/icons";
export function SiteFooter() { export function SiteFooter() {
return ( return (
<footer className="site-footer"> <footer className="site-footer">
<div className="footer-main page-shell"> <div className="footer-inner page-shell">
<div className="footer-brand"> <div className="footer-identity">
<span className="footer-mark"> <BrandMark />
<BrandMark /> <span>VniDrop</span>
</span>
<div>
<p className="footer-name">VniDrop</p>
<p>Send files directly. Stay in control.</p>
</div>
</div>
<div className="footer-links">
<div>
<p className="footer-label">Explore</p>
<Link href="/#how-it-works">How it works</Link>
<Link href="/#privacy">Privacy by design</Link>
<Link href="/#platforms">Platforms</Link>
</div>
<div>
<p className="footer-label">Project</p>
<a href="https://github.com/vnidrop/vnidrop" target="_blank" rel="noreferrer">
GitHub <Icon name="github" />
</a>
<a
href="https://github.com/vnidrop/vnidrop/blob/master/LICENSE"
target="_blank"
rel="noreferrer"
>
Apache 2.0 <Icon name="arrow" />
</a>
<Link href="/privacy/">Privacy policy</Link>
</div>
</div> </div>
<nav className="footer-links" aria-label="Footer navigation">
<a href="https://github.com/vnidrop/vnidrop" target="_blank" rel="noreferrer">
<Icon name="github" /> GitHub
</a>
<Link href="/privacy/">Privacy</Link>
<a
href="https://github.com/vnidrop/vnidrop/blob/master/LICENSE"
target="_blank"
rel="noreferrer"
>
Apache 2.0
</a>
</nav>
</div> </div>
<div className="footer-bottom page-shell"> <div className="footer-bottom page-shell">
<p>© 2026 VniDrop contributors.</p> <p>© 2026 VniDrop contributors.</p>

BIN
docs/public/App.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB