feat(docs): add animated product website

This commit is contained in:
2026-07-16 16:38:30 +02:00
parent e40a4bfee2
commit d61d6f3c5b
38 changed files with 12046 additions and 0 deletions

381
docs/app/privacy/page.tsx Normal file
View File

@@ -0,0 +1,381 @@
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 = {
title: "Privacy policy",
description:
"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 = [
["scope", "Scope"],
["transfers", "Transfers"],
["local-data", "Local data"],
["diagnostics", "Diagnostics"],
["website", "Website"],
["permissions", "Permissions"],
["providers", "Service providers"],
["retention", "Retention"],
["choices", "Your choices"],
["security", "Security"],
["changes", "Changes"],
["contact", "Contact"],
];
export default function PrivacyPage() {
return (
<main id="main-content" className="privacy-page">
<section className="privacy-hero">
<div className="privacy-hero-grid" aria-hidden="true" />
<div className="page-shell privacy-hero-layout">
<Reveal className="privacy-hero-copy">
<Link className="privacy-back" href="/">
<Icon name="arrow" />
Back to VniDrop
</Link>
<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>
</section>
<section className="privacy-document-section">
<div className="page-shell privacy-document-layout">
<aside className="privacy-toc">
<p>On this page</p>
<nav aria-label="Privacy policy sections">
{sections.map(([id, label], index) => (
<a key={id} href={`#${id}`}>
<span>{String(index + 1).padStart(2, "0")}</span>
{label}
</a>
))}
</nav>
</aside>
<article className="privacy-document">
<div className="privacy-callout">
<Icon name="spark" />
<div>
<strong>The short version</strong>
<p>
VniDrop has no user accounts and does not upload your transfer to a VniDrop file
store. Files travel over an authenticated, end-to-end encrypted connection.
Product diagnostics are opt-in; a bug report is sent only when you submit one.
</p>
</div>
</div>
<section id="scope" className="policy-section">
<span className="policy-number">01</span>
<h2>Scope and who VniDrop means</h2>
<p>
This policy covers the official VniDrop website, the VniDrop applications for
Android, iOS, macOS, Windows, and Linux, and the diagnostics service configured by
the official project. In this policy, VniDrop, we, and us refer to the
maintainers of the official VniDrop project and the official builds they distribute.
</p>
<p>
VniDrop is open-source software. A build distributed or operated by someone else
may use different networking infrastructure, diagnostics settings, or website
hosting. That distributor is responsible for explaining its own practices.
</p>
</section>
<section id="transfers" className="policy-section">
<span className="policy-number">02</span>
<h2>What happens during a transfer</h2>
<h3>File contents</h3>
<p>
The sender chooses files or folders on their device. VniDrop streams those bytes to
an approved receiver and does not first upload them to a VniDrop-hosted storage
bucket. The receiver saves the files to a destination they choose. Relayed traffic
remains end-to-end encrypted.
</p>
<h3>Invitations and transfer metadata</h3>
<p>
A QR code, NFC tag, or <code>.vnd</code> file contains a transfer invitation. The
invitation includes connection and content identifiers plus transfer metadata such
as the transfer name, optional sender name, creation time, file count, and total
size. It is a capability: anyone who receives it may be able to request the transfer
while the share is active. Treat it like a private access link.
</p>
<h3>What peers and relays can see</h3>
<p>
A receive request can disclose the receivers chosen display or device name,
application version, and a technical endpoint identifier to the sender. A direct
connection exposes the peers IP addresses to one another. When a public relay is
used, its operator can observe connection metadata such as source and destination IP
addresses, connection time, and the amount of relayed data, but cannot read the
encrypted transfer contents.
</p>
<div className="policy-note">
<Icon name="shield" />
<p>
Approval is required by default. If the sender selects Anyone with this transfer,
anyone holding the invitation may receive the files until sharing stops.
</p>
</div>
</section>
<section id="local-data" className="policy-section">
<span className="policy-number">03</span>
<h2>Information kept on your device</h2>
<p>VniDrop stores the information needed to operate the app locally, including:</p>
<ul>
<li>device identity and networking keys used to establish secure connections;</li>
<li>active shares, transfer history, receiver requests, progress, and status;</li>
<li>app preferences, including access and diagnostics choices;</li>
<li>download destinations and locally managed transfer data; and</li>
<li>an anonymous installation identifier used only for diagnostics correlation.</li>
</ul>
<p>
This information remains until you remove the relevant history, stop or delete a
share, clear the apps data, or uninstall the app, subject to operating-system file
behavior. Removing VniDrop history does not delete a file you already downloaded;
delete that file through your operating system if you no longer want it.
</p>
</section>
<section id="diagnostics" className="policy-section">
<span className="policy-number">04</span>
<h2>Optional diagnostics and bug reports</h2>
<h3>Automatic product diagnostics</h3>
<p>
When an official build includes diagnostics, automatic usage events and crash
reports are disabled until you enable Share diagnostics. If enabled, VniDrop may
send an anonymous installation ID, app version, platform, sparse event names and
properties, crash type and message, a redacted stack trace, timestamps, and recent
in-app breadcrumbs. You can turn this off at any time; doing so also removes pending
local crash reports.
</p>
<h3>User-submitted bug reports</h3>
<p>
A bug report is separate from the diagnostics toggle and is sent only when you press
submit. It can contain what you say happened, what you expected, reproduction steps,
an optional contact email, app and platform versions, an anonymous installation ID,
device name and model, operating system, network and battery information, recent
breadcrumbs, and optional recent logs. You can exclude logs before submitting.
</p>
<h3>Data deliberately excluded</h3>
<p>
Automatic diagnostics are designed to exclude transfer contents, invitations, and
file paths. Before diagnostic text or optional logs are sent, VniDrop applies rules
intended to redact invitation tokens, endpoint identifiers, absolute paths, file and
content URIs, and platform document identifiers. No redaction system is perfect, so
review anything you type into a bug report and avoid including secrets.
</p>
</section>
<section id="website" className="policy-section">
<span className="policy-number">05</span>
<h2>The VniDrop website</h2>
<p>
This website is a static product site. It does not provide an account, contact form,
advertising, behavioral analytics, marketing pixels, or non-essential cookies. It
does not ask the browser for access to your files, camera, contacts, location, or
nearby devices.
</p>
<p>
The hosting and security infrastructure may process routine request informationsuch
as IP address, time, requested page, referrer, and browser user agentto deliver the
site, maintain reliability, and prevent abuse. The live hosting provider must be
identified in this policy before public deployment if it differs from the providers
described below.
</p>
</section>
<section id="permissions" className="policy-section">
<span className="policy-number">06</span>
<h2>Device permissions</h2>
<div className="permission-grid">
<div><Icon name="folder" /><strong>Files & folders</strong><p>Choose what to send and where received files are saved.</p></div>
<div><Icon name="qr" /><strong>Camera / scanner</strong><p>Scan a QR invitation when you choose that receive method.</p></div>
<div><Icon name="nfc" /><strong>NFC</strong><p>Read or write an invitation through a compatible NFC tag.</p></div>
<div><Icon name="devices" /><strong>Network & notifications</strong><p>Connect peers and alert you to background receiver requests.</p></div>
</div>
<p>
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
permission prompts and service-provider terms also apply.
</p>
</section>
<section id="providers" className="policy-section">
<span className="policy-number">07</span>
<h2>Infrastructure and external services</h2>
<div className="provider-list">
<div>
<span>I</span>
<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>
</div>
<div>
<span>C</span>
<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>
</div>
<div>
<span>G</span>
<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>
</div>
<div>
<span>GH</span>
<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>
</div>
</div>
<p className="provider-links">
Provider policies: {" "}
<a href="https://services.iroh.computer/legal/privacy" target="_blank" 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>
</section>
<section id="retention" className="policy-section">
<span className="policy-number">08</span>
<h2>Retention and deletion</h2>
<div className="retention-table" role="table" aria-label="Data retention periods">
<div className="retention-row retention-head" role="row">
<span role="columnheader">Data</span><span role="columnheader">Typical retention</span>
</div>
<div className="retention-row" role="row">
<span role="cell">Transfer history and settings</span><span role="cell">Until you delete them, clear app data, or uninstall</span>
</div>
<div className="retention-row" role="row">
<span role="cell">Pending local crash reports</span><span role="cell">Up to 30 days and 20 reports; deleted when diagnostics is disabled</span>
</div>
<div className="retention-row" role="row">
<span role="cell">Server diagnostics and bug reports</span><span role="cell">The current project configuration is 90 days, with scheduled deletion</span>
</div>
<div className="retention-row" role="row">
<span role="cell">Downloaded files</span><span role="cell">Until you delete them through your operating system</span>
</div>
</div>
<p>
Operational backups, provider logs, and deletion backlogs may persist briefly beyond
the stated period where necessary for security, integrity, or legal obligations. If
the production diagnostics retention configuration changes, this policy should be
updated to match it.
</p>
</section>
<section id="choices" className="policy-section">
<span className="policy-number">09</span>
<h2>Your choices and rights</h2>
<ul>
<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>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>Delete downloaded files using your operating system, or clear all app data by uninstalling or resetting the app.</li>
</ul>
<p>
Depending on where you live, privacy law may provide rights to access, correct,
delete, restrict, or object to processing of personal information. Because VniDrop
has no account and automatic diagnostics use an anonymous installation ID, we may
not be able to connect a server record to you without additional information. Use
the contact method below and provide only what is needed to locate your submission.
</p>
</section>
<section id="security" className="policy-section">
<span className="policy-number">10</span>
<h2>Security</h2>
<p>
VniDrop uses authenticated end-to-end encrypted connections, content verification,
deny-by-default share access, bounded diagnostics payloads, redaction, and safe file
publishing that avoids silently replacing an existing file. No system can guarantee
absolute security. Keep invitations private, verify receiver names, keep your device
updated, and stop sharing when a transfer is finished.
</p>
<p>
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.
</p>
</section>
<section id="changes" className="policy-section">
<span className="policy-number">11</span>
<h2>Changes to this policy</h2>
<p>
VniDrop is in early development. Features and data practices may change. When this
policy changes, we will update the effective date and version at the top of the page
and publish the revised text with the project. Material changes should be called out
in release notes or the application where practical.
</p>
</section>
<section id="contact" className="policy-section policy-contact">
<span className="policy-number">12</span>
<h2>Contact</h2>
<p>
VniDrop is currently maintained as an open-source project and does not yet publish a
dedicated privacy email or postal address. For a privacy question or request, open a
request in the project issue tracker. Do not put an invitation, file content,
credentials, or other sensitive information in a public issue.
</p>
<a
className="button button-primary"
href="https://github.com/vnidrop/vnidrop/issues/new"
target="_blank"
rel="noreferrer"
>
Contact the maintainers
<Icon name="arrow" />
</a>
</section>
</article>
</div>
</section>
</main>
);
}

View File

@@ -0,0 +1,660 @@
.privacy-page {
background: #fcfbfd;
}
.privacy-hero {
position: relative;
overflow: hidden;
padding: 165px 0 105px;
background:
radial-gradient(circle at 80% 20%, rgba(192, 132, 252, 0.2), transparent 30%),
linear-gradient(180deg, #fdfcfe, #f7f3f9);
}
.privacy-hero-grid {
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;
width: max-content;
align-items: center;
gap: 8px;
margin-bottom: 42px;
color: var(--ink-muted);
font-size: 12px;
font-weight: 650;
}
.privacy-hero-copy > .kicker {
display: block;
width: max-content;
}
.privacy-back svg {
width: 16px;
transform: rotate(180deg);
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);
font-size: 18px;
line-height: 1.72;
}
.privacy-meta {
display: flex;
align-items: center;
gap: 12px;
color: var(--ink-muted);
font-family: var(--font-mono);
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 > svg {
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 {
padding: 135px 0 155px;
}
.privacy-document-layout {
display: grid;
grid-template-columns: 210px minmax(0, 780px);
justify-content: center;
gap: 105px;
}
.privacy-toc {
position: sticky;
top: 112px;
align-self: start;
}
.privacy-toc > p {
margin-bottom: 17px;
color: var(--ink);
font-family: var(--font-mono);
font-size: 9px;
font-weight: 800;
letter-spacing: 0.09em;
text-transform: uppercase;
}
.privacy-toc nav {
display: flex;
flex-direction: column;
}
.privacy-toc a {
display: grid;
grid-template-columns: 26px 1fr;
align-items: center;
gap: 6px;
padding: 7px 0;
color: #79707e;
font-size: 11px;
font-weight: 600;
transition: color 160ms ease, transform 160ms ease;
}
.privacy-toc a:hover {
color: var(--brand-600);
transform: translateX(3px);
}
.privacy-toc a span {
color: #b5aabb;
font-family: var(--font-mono);
font-size: 8px;
}
.privacy-document {
min-width: 0;
}
.privacy-callout {
display: grid;
grid-template-columns: auto 1fr;
gap: 17px;
margin-bottom: 75px;
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 {
display: block;
margin-bottom: 5px;
font-family: var(--font-display);
font-size: 14px;
}
.privacy-callout p {
margin: 0;
color: var(--ink-soft);
font-size: 12px;
line-height: 1.7;
}
.policy-section {
position: relative;
scroll-margin-top: 110px;
padding: 0 0 72px;
}
.policy-section + .policy-section {
padding-top: 3px;
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 {
margin-bottom: 27px;
font-size: clamp(2rem, 3.3vw, 3.15rem);
font-weight: 720;
letter-spacing: -0.052em;
line-height: 1.08;
}
.policy-section h3 {
margin: 34px 0 10px;
font-size: 16px;
font-weight: 720;
letter-spacing: -0.018em;
}
.policy-section p,
.policy-section li {
color: #514956;
font-size: 14px;
line-height: 1.82;
}
.policy-section p {
margin-bottom: 19px;
}
.policy-section ul {
display: grid;
gap: 8px;
margin: 17px 0 22px;
padding-left: 22px;
}
.policy-section li::marker {
color: var(--brand-500);
}
.policy-section code {
padding: 2px 6px;
border: 1px solid #e7dceb;
border-radius: 5px;
background: #f8f3fa;
color: var(--brand-700);
font-family: var(--font-mono);
font-size: 0.86em;
}
.policy-section a:not(.button) {
color: var(--brand-700);
font-weight: 650;
text-decoration: underline;
text-decoration-color: rgba(132, 47, 238, 0.28);
text-underline-offset: 3px;
}
.policy-note {
display: grid;
grid-template-columns: auto 1fr;
gap: 13px;
margin-top: 28px;
padding: 18px;
border: 1px solid #e2d6e8;
border-radius: 13px;
background: #faf7fb;
}
.policy-note svg {
width: 21px;
color: var(--brand-600);
}
.policy-note p {
margin: 0;
color: #5e5264;
font-size: 12px;
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;
}
.provider-list {
display: grid;
gap: 9px;
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);
}
.retention-row span {
padding: 14px 16px;
color: var(--ink-soft);
font-size: 10px;
line-height: 1.55;
}
.retention-row span + span {
border-left: 1px solid var(--line);
color: var(--ink-muted);
}
.retention-head {
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;
}
.policy-contact {
overflow: hidden;
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 {
max-width: 620px;
margin-bottom: 26px;
}
.policy-contact .button {
color: white;
text-decoration: none;
}

View File

@@ -0,0 +1,156 @@
@keyframes privacy-orbit {
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 {
grid-template-columns: 180px minmax(0, 720px);
gap: 60px;
}
}
@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 {
position: relative;
top: auto;
padding-bottom: 25px;
border-bottom: 1px solid var(--line);
}
.privacy-toc nav {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 560px) {
.privacy-hero {
padding: 120px 0 90px;
}
.privacy-back {
margin-bottom: 32px;
}
.privacy-hero-copy h1 {
font-size: clamp(3rem, 15vw, 4.5rem);
}
.privacy-hero-copy > p {
font-size: 15px;
}
.privacy-hero-art {
min-height: 360px;
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 {
padding: 95px 0 105px;
}
.privacy-toc nav {
grid-template-columns: 1fr 1fr;
}
.privacy-callout {
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;
}
.policy-contact {
padding: 35px 24px !important;
}
}