mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
feat(docs): add OG image and prune unused assets
This commit is contained in:
@@ -1,86 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Icon } from "@/components/icons";
|
||||
|
||||
type ApprovalState = "pending" | "approved" | "refused";
|
||||
|
||||
export function ApprovalDemo() {
|
||||
const [state, setState] = useState<ApprovalState>("pending");
|
||||
|
||||
return (
|
||||
<div className={`approval-demo state-${state}`}>
|
||||
<div className="approval-window-bar">
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
<small>Receiver requests</small>
|
||||
</div>
|
||||
<div className="approval-body" aria-live="polite">
|
||||
<div className="approval-heading">
|
||||
<div className="device-avatar">
|
||||
<span className="device-avatar-screen" />
|
||||
<span className="device-avatar-glow" />
|
||||
</div>
|
||||
<div>
|
||||
<span className="mono-label">
|
||||
{state === "pending" ? "NEW REQUEST" : state === "approved" ? "IN PROGRESS" : "REQUEST CLOSED"}
|
||||
</span>
|
||||
<h3>
|
||||
{state === "pending" && "Maya’s phone wants to receive"}
|
||||
{state === "approved" && "Sending securely to Maya"}
|
||||
{state === "refused" && "Request refused"}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="approval-file">
|
||||
<span className="approval-file-icon">
|
||||
<Icon name="folder" />
|
||||
</span>
|
||||
<span>
|
||||
<strong>Summer photos</strong>
|
||||
<small>42 files · 1.8 GB</small>
|
||||
</span>
|
||||
{state === "approved" && <span className="approval-percent">68%</span>}
|
||||
</div>
|
||||
|
||||
{state === "pending" && (
|
||||
<div className="approval-actions">
|
||||
<button className="approval-refuse" type="button" onClick={() => setState("refused")}>
|
||||
Refuse
|
||||
</button>
|
||||
<button className="approval-accept" type="button" onClick={() => setState("approved")}>
|
||||
<Icon name="check" />
|
||||
Approve
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{state === "approved" && (
|
||||
<div className="approval-progress">
|
||||
<div className="approval-progress-track">
|
||||
<span />
|
||||
</div>
|
||||
<div className="approval-progress-meta">
|
||||
<span><i className="live-dot" /> Direct connection</span>
|
||||
<button type="button" onClick={() => setState("pending")}>Reset demo</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{state === "refused" && (
|
||||
<div className="approval-refused">
|
||||
<span><Icon name="x" /></span>
|
||||
<p>This device was not given access.</p>
|
||||
<button type="button" onClick={() => setState("pending")}>Try again</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="approval-footer">
|
||||
<Icon name="shield" />
|
||||
Approval is required by default
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
/* Interactive receiver approval */
|
||||
.approval-demo {
|
||||
overflow: hidden;
|
||||
border: 1px solid #ded6e2;
|
||||
border-radius: 24px;
|
||||
background: #fbf9fc;
|
||||
box-shadow: 0 35px 90px rgba(49, 29, 58, 0.16);
|
||||
transform: rotate(1deg);
|
||||
transition: transform 280ms ease, border-color 280ms ease;
|
||||
}
|
||||
|
||||
.approval-demo:hover { transform: rotate(0deg) translateY(-3px); }
|
||||
.approval-demo.state-approved { border-color: rgba(45, 185, 117, 0.4); }
|
||||
.approval-demo.state-refused { border-color: rgba(221, 92, 75, 0.35); }
|
||||
|
||||
.approval-window-bar {
|
||||
display: flex;
|
||||
height: 48px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid #ded6e2;
|
||||
background: #f0ecf2;
|
||||
}
|
||||
|
||||
.approval-window-bar > span {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #d1c7d5;
|
||||
}
|
||||
|
||||
.approval-window-bar > span:nth-child(1) { background: #f1a191; }
|
||||
.approval-window-bar > span:nth-child(2) { background: #f1ca72; }
|
||||
.approval-window-bar > span:nth-child(3) { background: #72cf9b; }
|
||||
|
||||
.approval-window-bar small {
|
||||
margin-left: auto;
|
||||
color: #8a7f8f;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 8px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.approval-body {
|
||||
min-height: 385px;
|
||||
padding: 35px;
|
||||
background:
|
||||
radial-gradient(circle at 90% 0%, rgba(192, 132, 252, 0.16), transparent 27%),
|
||||
white;
|
||||
}
|
||||
|
||||
.approval-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 17px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.device-avatar {
|
||||
position: relative;
|
||||
width: 52px;
|
||||
height: 60px;
|
||||
flex: 0 0 auto;
|
||||
border: 4px solid #352e39;
|
||||
border-radius: 13px;
|
||||
background: #211d24;
|
||||
box-shadow: 0 10px 25px rgba(42, 30, 48, 0.16);
|
||||
}
|
||||
|
||||
.device-avatar::before {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 50%;
|
||||
width: 16px;
|
||||
height: 3px;
|
||||
border-radius: 999px;
|
||||
background: #0c0a0d;
|
||||
content: "";
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.device-avatar-screen {
|
||||
position: absolute;
|
||||
inset: 14px 8px 8px;
|
||||
border-radius: 7px;
|
||||
background: linear-gradient(145deg, #b55efa, #6f27e9);
|
||||
}
|
||||
|
||||
.device-avatar-glow {
|
||||
position: absolute;
|
||||
inset: -5px;
|
||||
z-index: -1;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 0 0 0 rgba(168, 85, 247, 0.25);
|
||||
animation: avatar-pulse 2.7s ease-out infinite;
|
||||
}
|
||||
|
||||
.approval-heading h3 {
|
||||
max-width: 360px;
|
||||
margin: 5px 0 0;
|
||||
font-size: 21px;
|
||||
letter-spacing: -0.035em;
|
||||
line-height: 1.22;
|
||||
}
|
||||
|
||||
.approval-file {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 13px;
|
||||
padding: 16px;
|
||||
border: 1px solid #e5dde8;
|
||||
border-radius: 14px;
|
||||
background: #f8f5f9;
|
||||
}
|
||||
|
||||
.approval-file-icon {
|
||||
display: grid;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
flex: 0 0 auto;
|
||||
place-items: center;
|
||||
border-radius: 11px;
|
||||
background: var(--brand-100);
|
||||
color: var(--brand-600);
|
||||
}
|
||||
|
||||
.approval-file-icon svg { width: 21px; }
|
||||
.approval-file strong,
|
||||
.approval-file small { display: block; }
|
||||
.approval-file strong { font-size: 12px; }
|
||||
.approval-file small { color: var(--ink-muted); font-size: 9px; }
|
||||
|
||||
.approval-percent {
|
||||
margin-left: auto;
|
||||
color: var(--brand-600);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.approval-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.4fr;
|
||||
gap: 10px;
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.approval-actions button,
|
||||
.approval-refused button {
|
||||
min-height: 47px;
|
||||
border: 1px solid #ded4e2;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 750;
|
||||
cursor: pointer;
|
||||
transition: transform 160ms ease, background 160ms ease, box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.approval-actions button:hover,
|
||||
.approval-refused button:hover { transform: translateY(-2px); }
|
||||
.approval-refuse { background: white; color: #625769; }
|
||||
.approval-accept {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border-color: transparent !important;
|
||||
background: linear-gradient(135deg, var(--brand-500), var(--brand-700));
|
||||
box-shadow: 0 10px 24px rgba(132, 47, 238, 0.22);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.approval-accept svg { width: 17px; }
|
||||
|
||||
.approval-progress {
|
||||
margin-top: 25px;
|
||||
animation: approval-state-enter 350ms ease;
|
||||
}
|
||||
|
||||
.approval-progress-track {
|
||||
overflow: hidden;
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: #eee8f1;
|
||||
}
|
||||
|
||||
.approval-progress-track span {
|
||||
display: block;
|
||||
width: 68%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(90deg, var(--brand-500), var(--brand-700));
|
||||
box-shadow: 0 0 15px rgba(168, 85, 247, 0.38);
|
||||
animation: progress-fill 900ms cubic-bezier(0.25, 0.8, 0.25, 1) both;
|
||||
}
|
||||
|
||||
.approval-progress-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 13px;
|
||||
color: var(--ink-muted);
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.approval-progress-meta > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
}
|
||||
|
||||
.approval-progress-meta button,
|
||||
.approval-refused button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--brand-600);
|
||||
font-size: 9px;
|
||||
font-weight: 750;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.approval-refused {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 23px;
|
||||
padding: 14px;
|
||||
border: 1px solid #f0d5d0;
|
||||
border-radius: 12px;
|
||||
background: #fff8f7;
|
||||
animation: approval-state-enter 350ms ease;
|
||||
}
|
||||
|
||||
.approval-refused > span {
|
||||
display: grid;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
flex: 0 0 auto;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
background: #fde7e3;
|
||||
color: #d85848;
|
||||
}
|
||||
|
||||
.approval-refused svg { width: 15px; }
|
||||
.approval-refused p { margin: 0; color: #705c59; font-size: 10px; }
|
||||
.approval-refused button { margin-left: auto; }
|
||||
|
||||
.approval-footer {
|
||||
display: flex;
|
||||
height: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
border-top: 1px solid #ded6e2;
|
||||
background: #f2eef4;
|
||||
color: #766b7b;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 8px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.approval-footer svg { width: 15px; color: var(--brand-600); }
|
||||
|
||||
@@ -20,28 +20,14 @@ export function BrandMark({ className, title }: BrandAssetProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export function AppIcon({ className, title }: BrandAssetProps) {
|
||||
return (
|
||||
<Image
|
||||
className={className}
|
||||
src="/icon.svg"
|
||||
width={1024}
|
||||
height={1024}
|
||||
alt={title ?? ""}
|
||||
aria-hidden={title ? undefined : true}
|
||||
unoptimized
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function Brand({ compact = false }: { compact?: boolean }) {
|
||||
export function Brand() {
|
||||
return (
|
||||
<Link className="brand" href="/" aria-label="VniDrop home">
|
||||
<span className="brand-primary">
|
||||
<BrandMark className="brand-mark-image" />
|
||||
{!compact && <span className="brand-name">VniDrop</span>}
|
||||
<span className="brand-name">VniDrop</span>
|
||||
</span>
|
||||
{!compact && <span className="brand-tagline">Send files directly. Stay in control.</span>}
|
||||
<span className="brand-tagline">Send files directly. Stay in control.</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,490 +0,0 @@
|
||||
/* Receiver hardware, status overlays, and responsive stage composition */
|
||||
.receiver-phone-shell {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
top: 17%;
|
||||
right: 1.5%;
|
||||
width: 23.5%;
|
||||
aspect-ratio: 0.49;
|
||||
transform: translate(
|
||||
calc(var(--pointer-x) * 7px),
|
||||
calc(var(--pointer-y) * 5px)
|
||||
)
|
||||
rotateY(calc(var(--pointer-x) * -1.6deg))
|
||||
rotateX(calc(var(--pointer-y) * 1.3deg));
|
||||
transform-origin: center;
|
||||
transition: transform 180ms ease-out;
|
||||
}
|
||||
|
||||
.receiver-phone-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 7px;
|
||||
border: 1px solid #bdb7c1;
|
||||
border-radius: 38px;
|
||||
background: linear-gradient(145deg, #e7e4e9, #aaa4ae 42%, #f3f1f4 72%, #aaa3ae);
|
||||
box-shadow:
|
||||
0 38px 85px rgba(43, 27, 51, 0.22),
|
||||
0 9px 25px rgba(43, 27, 51, 0.12),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.receiver-phone-frame::before {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
inset: 2px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.7);
|
||||
border-radius: 35px;
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.phone-side-button {
|
||||
position: absolute;
|
||||
right: -3px;
|
||||
width: 3px;
|
||||
border-radius: 0 2px 2px 0;
|
||||
background: #98929d;
|
||||
}
|
||||
|
||||
.phone-side-button-one { top: 24%; height: 14%; }
|
||||
.phone-side-button-two { top: 42%; height: 21%; }
|
||||
|
||||
.phone-screen {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 31px;
|
||||
background:
|
||||
radial-gradient(circle at 80% 18%, rgba(192, 132, 252, 0.12), transparent 28%),
|
||||
#faf9fb;
|
||||
box-shadow: inset 0 0 0 1px rgba(26, 22, 29, 0.18);
|
||||
}
|
||||
|
||||
.phone-screen::after {
|
||||
position: absolute;
|
||||
top: -28%;
|
||||
left: -42%;
|
||||
width: 82%;
|
||||
height: 130%;
|
||||
background: linear-gradient(110deg, transparent 35%, rgba(255, 255, 255, 0.32), transparent 66%);
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
transform: rotate(8deg);
|
||||
}
|
||||
|
||||
.phone-status-bar {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
padding: 0 10px;
|
||||
color: #2e2931;
|
||||
font-size: 6px;
|
||||
font-weight: 760;
|
||||
}
|
||||
|
||||
.phone-island {
|
||||
width: 42px;
|
||||
height: 12px;
|
||||
justify-self: center;
|
||||
border-radius: 999px;
|
||||
background: #171419;
|
||||
}
|
||||
|
||||
.phone-signal {
|
||||
justify-self: end;
|
||||
font-size: 5px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.phone-app-bar {
|
||||
display: grid;
|
||||
grid-template-columns: 26px 1fr 25px;
|
||||
align-items: center;
|
||||
padding: 5px 11px 8px;
|
||||
}
|
||||
|
||||
.phone-brand-mark {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.phone-app-bar strong {
|
||||
color: #29242c;
|
||||
font-size: 9px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.phone-app-bar > span {
|
||||
display: grid;
|
||||
width: 23px;
|
||||
height: 23px;
|
||||
place-items: center;
|
||||
border: 1px solid #e9e3ec;
|
||||
border-radius: 8px;
|
||||
background: white;
|
||||
color: var(--brand-700);
|
||||
}
|
||||
|
||||
.phone-app-bar svg { width: 11px; }
|
||||
|
||||
.phone-transfer-card {
|
||||
display: grid;
|
||||
margin: 6px 10px 0;
|
||||
padding: 11px 9px 10px;
|
||||
border: 1px solid #e6e0e9;
|
||||
border-radius: 14px;
|
||||
background: white;
|
||||
box-shadow: 0 9px 25px rgba(48, 33, 55, 0.055);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.phone-file-art {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
margin: 0 auto 8px;
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
border-radius: 13px;
|
||||
background:
|
||||
linear-gradient(145deg, rgba(255, 255, 255, 0.2), transparent 48%),
|
||||
linear-gradient(135deg, #c084fc, #7c2aef);
|
||||
color: white;
|
||||
box-shadow: 0 10px 22px rgba(132, 47, 238, 0.22);
|
||||
}
|
||||
|
||||
.phone-file-art::after {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: rgba(235, 211, 255, 0.65);
|
||||
content: "";
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.phone-file-art svg { width: 21px; }
|
||||
|
||||
.phone-transfer-card small {
|
||||
color: #9a8fa0;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 5px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.09em;
|
||||
}
|
||||
|
||||
.phone-transfer-card strong {
|
||||
margin-top: 3px;
|
||||
color: #29232d;
|
||||
font-size: 9px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.phone-transfer-card > span {
|
||||
color: #8d8391;
|
||||
font-size: 6px;
|
||||
}
|
||||
|
||||
.phone-state-area {
|
||||
position: relative;
|
||||
height: 88px;
|
||||
margin: 9px 10px 0;
|
||||
overflow: hidden;
|
||||
border-radius: 13px;
|
||||
background: #f2eff4;
|
||||
}
|
||||
|
||||
.phone-state {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.state-symbol {
|
||||
display: grid;
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
margin-bottom: 6px;
|
||||
place-items: center;
|
||||
border-radius: 10px;
|
||||
background: #e5d3f3;
|
||||
color: var(--brand-700);
|
||||
}
|
||||
|
||||
.state-symbol.is-success {
|
||||
background: #d8f4e7;
|
||||
color: #188457;
|
||||
}
|
||||
|
||||
.state-symbol svg { width: 15px; }
|
||||
|
||||
.phone-state strong {
|
||||
color: #39313d;
|
||||
font-size: 7px;
|
||||
}
|
||||
|
||||
.phone-state small {
|
||||
color: #8d8291;
|
||||
font-size: 5.5px;
|
||||
}
|
||||
|
||||
.transfer-percentage {
|
||||
margin-bottom: 3px;
|
||||
color: var(--brand-700);
|
||||
font-family: var(--font-display);
|
||||
font-size: 24px;
|
||||
font-weight: 740;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.08em;
|
||||
}
|
||||
|
||||
.transfer-percentage span {
|
||||
margin-left: 1px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.phone-home-indicator {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 50%;
|
||||
width: 42px;
|
||||
height: 3px;
|
||||
border-radius: 999px;
|
||||
background: #27222a;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.hero-route-card,
|
||||
.hero-verified-card {
|
||||
position: absolute;
|
||||
z-index: 8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(214, 204, 220, 0.92);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
box-shadow: 0 15px 45px rgba(49, 27, 61, 0.12);
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
|
||||
.hero-route-card {
|
||||
top: 3%;
|
||||
left: 36%;
|
||||
gap: 8px;
|
||||
min-width: 176px;
|
||||
padding: 9px 10px;
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
.route-pulse {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
flex: 0 0 auto;
|
||||
border: 2px solid #def7eb;
|
||||
border-radius: 50%;
|
||||
background: #2fbc7a;
|
||||
box-shadow: 0 0 0 4px rgba(47, 188, 122, 0.1);
|
||||
}
|
||||
|
||||
.hero-route-card > span:nth-child(2),
|
||||
.hero-verified-card > span:nth-child(2) {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.hero-route-card small {
|
||||
color: #978d9b;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 5px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
|
||||
.hero-route-card strong,
|
||||
.hero-verified-card strong {
|
||||
color: #342d37;
|
||||
font-size: 7.5px;
|
||||
}
|
||||
|
||||
.route-latency {
|
||||
margin-left: auto;
|
||||
color: var(--brand-700);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 6px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.hero-verified-card {
|
||||
right: 0;
|
||||
bottom: 12%;
|
||||
gap: 8px;
|
||||
padding: 9px 11px 9px 8px;
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
.hero-verified-card > span:first-child {
|
||||
display: grid;
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
place-items: center;
|
||||
border-radius: 10px;
|
||||
background: #dcf6e9;
|
||||
color: #1b8d5d;
|
||||
}
|
||||
|
||||
.hero-verified-card svg { width: 16px; }
|
||||
|
||||
.hero-verified-card small {
|
||||
color: #8e8492;
|
||||
font-size: 6px;
|
||||
}
|
||||
|
||||
.hero-privacy-rail {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
bottom: 3.5%;
|
||||
left: 8%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
padding: 7px 10px;
|
||||
border: 1px solid rgba(217, 208, 222, 0.82);
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
color: #786e7c;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 5.5px;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.035em;
|
||||
text-transform: uppercase;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.hero-privacy-rail span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.hero-privacy-rail svg {
|
||||
width: 9px;
|
||||
color: var(--brand-700);
|
||||
}
|
||||
|
||||
.hero-privacy-rail i {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 50%;
|
||||
background: #c4b9c8;
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.hero-stage {
|
||||
aspect-ratio: 1.02;
|
||||
}
|
||||
|
||||
.hero-stage-grid {
|
||||
inset: 2% -2% 2%;
|
||||
border-radius: 30px;
|
||||
background-size: 22px 22px, 22px 22px, auto, auto;
|
||||
}
|
||||
|
||||
.desktop-product-shell {
|
||||
top: 12%;
|
||||
left: -2%;
|
||||
width: 70%;
|
||||
height: 56%;
|
||||
}
|
||||
|
||||
.receiver-phone-shell {
|
||||
top: 22%;
|
||||
right: -1%;
|
||||
width: 26%;
|
||||
}
|
||||
|
||||
.desktop-app-layout { grid-template-columns: 42px 1fr; }
|
||||
.desktop-transfer-view { padding: 11px 10px 9px; }
|
||||
.desktop-sidebar { gap: 4px; padding-top: 7px; }
|
||||
.product-brand-mark { width: 29px; height: 29px; }
|
||||
.desktop-sidebar-item { width: 26px; height: 26px; }
|
||||
.desktop-sidebar-item svg { width: 12px; }
|
||||
.file-collection-card { grid-template-columns: 51px 1fr; gap: 7px; }
|
||||
.file-collage { width: 51px; height: 45px; }
|
||||
.collection-menu { display: none; }
|
||||
.receiver-state-stack { width: 72px; }
|
||||
.desktop-transfer-foot { display: none; }
|
||||
|
||||
.approval-knock-card {
|
||||
right: -6%;
|
||||
bottom: -22%;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.hero-route-card {
|
||||
top: 1%;
|
||||
left: 27%;
|
||||
min-width: 156px;
|
||||
}
|
||||
|
||||
.hero-verified-card { right: -1%; bottom: 8%; }
|
||||
.hero-privacy-rail { bottom: 0; left: 3%; }
|
||||
.stage-coordinate-side { display: none; }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.desktop-window-bar {
|
||||
grid-template-columns: 1fr auto;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.window-title { display: none; }
|
||||
.desktop-app-layout { height: calc(100% - 28px); }
|
||||
.desktop-view-heading { margin-bottom: 7px; }
|
||||
.desktop-view-heading strong { font-size: 12px; }
|
||||
.file-collection-card { padding: 7px; }
|
||||
.receiver-section-heading { margin-top: 8px; }
|
||||
.desktop-receiver-row { grid-template-columns: 27px 1fr; }
|
||||
.receiver-avatar { width: 26px; height: 26px; }
|
||||
.receiver-state-stack { display: none; }
|
||||
.receiver-progress-track { left: 43px; }
|
||||
.approval-knock-card { bottom: -27%; width: 184px; padding: 9px; }
|
||||
.approval-knock-card p { margin: 6px 0; }
|
||||
|
||||
.phone-app-bar { grid-template-columns: 22px 1fr 22px; padding-inline: 7px; }
|
||||
.phone-brand-mark { width: 22px; height: 22px; }
|
||||
.phone-transfer-card { margin-inline: 7px; padding-inline: 7px; }
|
||||
.phone-file-art { width: 38px; height: 38px; }
|
||||
.phone-state-area { height: 73px; margin-inline: 7px; }
|
||||
.state-symbol { width: 27px; height: 27px; margin-bottom: 3px; }
|
||||
.transfer-percentage { font-size: 20px; }
|
||||
|
||||
.hero-route-card {
|
||||
left: 20%;
|
||||
min-width: 142px;
|
||||
padding: 7px 8px;
|
||||
}
|
||||
|
||||
.route-latency { display: none; }
|
||||
.hero-verified-card { padding: 7px; }
|
||||
.hero-verified-card > span:first-child { width: 27px; height: 27px; }
|
||||
|
||||
.hero-privacy-rail {
|
||||
gap: 6px;
|
||||
font-size: 5px;
|
||||
}
|
||||
|
||||
.hero-privacy-rail span:nth-of-type(3),
|
||||
.hero-privacy-rail i:nth-of-type(2) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import type { CSSProperties, PointerEvent } from "react";
|
||||
import { useRef } from "react";
|
||||
import { motion, useReducedMotion } from "motion/react";
|
||||
import { BrandMark } from "@/components/brand";
|
||||
import { Icon } from "@/components/icons";
|
||||
|
||||
type StageStyle = CSSProperties & {
|
||||
"--pointer-x": string;
|
||||
"--pointer-y": string;
|
||||
};
|
||||
|
||||
const cycle = 10.5;
|
||||
const rest = 1.2;
|
||||
|
||||
export function HeroIllustration() {
|
||||
const stageRef = useRef<HTMLDivElement>(null);
|
||||
const reduceMotion = useReducedMotion();
|
||||
|
||||
const updateParallax = (event: PointerEvent<HTMLDivElement>) => {
|
||||
const stage = stageRef.current;
|
||||
if (!stage || reduceMotion) return;
|
||||
const bounds = stage.getBoundingClientRect();
|
||||
const x = ((event.clientX - bounds.left) / bounds.width - 0.5) * 2;
|
||||
const y = ((event.clientY - bounds.top) / bounds.height - 0.5) * 2;
|
||||
stage.style.setProperty("--pointer-x", x.toFixed(3));
|
||||
stage.style.setProperty("--pointer-y", y.toFixed(3));
|
||||
};
|
||||
|
||||
const resetParallax = () => {
|
||||
stageRef.current?.style.setProperty("--pointer-x", "0");
|
||||
stageRef.current?.style.setProperty("--pointer-y", "0");
|
||||
};
|
||||
|
||||
const repeating = { duration: cycle, repeat: Infinity, repeatDelay: rest };
|
||||
|
||||
return (
|
||||
<figure className="hero-visual">
|
||||
<div
|
||||
ref={stageRef}
|
||||
className="hero-stage"
|
||||
style={{ "--pointer-x": "0", "--pointer-y": "0" } as StageStyle}
|
||||
onPointerMove={updateParallax}
|
||||
onPointerLeave={resetParallax}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div className="hero-stage-grid" />
|
||||
<div className="hero-stage-glow" />
|
||||
<span className="stage-coordinate stage-coordinate-top">NODE / SEND · 01</span>
|
||||
<span className="stage-coordinate stage-coordinate-side">SECURE CHANNEL</span>
|
||||
|
||||
<div className="desktop-product-shell">
|
||||
<div className="desktop-product-window">
|
||||
<div className="desktop-window-bar">
|
||||
<span className="window-controls"><i /><i /><i /></span>
|
||||
<span className="window-title">VniDrop · Send</span>
|
||||
<span className="window-security"><Icon name="lock" /> Encrypted</span>
|
||||
</div>
|
||||
|
||||
<div className="desktop-app-layout">
|
||||
<aside className="desktop-sidebar">
|
||||
<BrandMark className="product-brand-mark" />
|
||||
<span className="desktop-sidebar-item is-active"><Icon name="file" /></span>
|
||||
<span className="desktop-sidebar-item"><Icon name="devices" /></span>
|
||||
<span className="desktop-sidebar-item"><Icon name="shield" /></span>
|
||||
<span className="desktop-sidebar-spacer" />
|
||||
<span className="desktop-profile">AH</span>
|
||||
</aside>
|
||||
|
||||
<div className="desktop-transfer-view">
|
||||
<div className="desktop-view-heading">
|
||||
<div>
|
||||
<small>OUTGOING TRANSFER</small>
|
||||
<strong>Summer archive</strong>
|
||||
</div>
|
||||
<span className="sharing-pill"><i /> Sharing</span>
|
||||
</div>
|
||||
|
||||
<div className="file-collection-card">
|
||||
<div className="file-collage" aria-hidden="true">
|
||||
<span /><span /><span /><span />
|
||||
</div>
|
||||
<div className="file-collection-copy">
|
||||
<strong>summer-photos</strong>
|
||||
<span>42 files · 486 MB</span>
|
||||
<small><Icon name="folder" /> Structure preserved</small>
|
||||
</div>
|
||||
<span className="collection-menu">•••</span>
|
||||
</div>
|
||||
|
||||
<div className="receiver-section-heading">
|
||||
<span>Receivers</span>
|
||||
<small>1 connected</small>
|
||||
</div>
|
||||
|
||||
<div className="desktop-receiver-row">
|
||||
<span className="receiver-avatar">M</span>
|
||||
<span className="receiver-identity">
|
||||
<strong>Maya's iPhone</strong>
|
||||
<small>iOS · nearby device</small>
|
||||
</span>
|
||||
<span className="receiver-state-stack">
|
||||
<motion.span
|
||||
className="receiver-row-state is-waiting"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { opacity: 0 }
|
||||
: { opacity: [1, 1, 0, 0] }
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.4, 0.48, 1] }}
|
||||
>
|
||||
Approval required
|
||||
</motion.span>
|
||||
<motion.span
|
||||
className="receiver-row-state is-sending"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { opacity: 0 }
|
||||
: { opacity: [0, 0, 1, 1, 0, 0] }
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.43, 0.5, 0.71, 0.78, 1] }}
|
||||
>
|
||||
Sending securely
|
||||
</motion.span>
|
||||
<motion.span
|
||||
className="receiver-row-state is-complete"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { opacity: 1 }
|
||||
: { opacity: [0, 0, 1, 1] }
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.72, 0.79, 1] }}
|
||||
>
|
||||
<Icon name="verified" /> Verified
|
||||
</motion.span>
|
||||
</span>
|
||||
<div className="receiver-progress-track">
|
||||
<motion.span
|
||||
animate={
|
||||
reduceMotion
|
||||
? { scaleX: 1 }
|
||||
: { scaleX: [0, 0, 0.04, 0.72, 1, 1] }
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.46, 0.51, 0.67, 0.76, 1] }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="desktop-transfer-foot">
|
||||
<span><Icon name="shield" /> Approval by default</span>
|
||||
<span><Icon name="lock" /> End-to-end encrypted</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="approval-knock-card"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { opacity: 0, y: 8, scale: 0.98 }
|
||||
: {
|
||||
opacity: [0, 0, 1, 1, 0, 0],
|
||||
y: [12, 12, 0, 0, -5, -5],
|
||||
scale: [0.97, 0.97, 1, 1, 0.985, 0.985],
|
||||
}
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.24, 0.3, 0.42, 0.49, 1], ease: "easeInOut" }}
|
||||
>
|
||||
<div className="approval-knock-top">
|
||||
<span className="receiver-avatar">M</span>
|
||||
<span>
|
||||
<small>NEW RECEIVER</small>
|
||||
<strong>Maya's iPhone</strong>
|
||||
</span>
|
||||
<span className="approval-lock"><Icon name="lock" /></span>
|
||||
</div>
|
||||
<p>Wants to receive <strong>summer-photos</strong></p>
|
||||
<div className="approval-knock-actions">
|
||||
<span>Refuse</span>
|
||||
<motion.span
|
||||
className="approval-accept"
|
||||
animate={reduceMotion ? undefined : { scale: [1, 1, 1.055, 1, 1] }}
|
||||
transition={{ ...repeating, times: [0, 0.38, 0.42, 0.46, 1] }}
|
||||
>
|
||||
<Icon name="check" /> Approve
|
||||
</motion.span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svg className="hero-conduit" viewBox="0 0 760 620" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="conduit-gradient" x1="410" y1="340" x2="650" y2="390" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="#C084FC" />
|
||||
<stop offset="1" stopColor="#7C2AEF" />
|
||||
</linearGradient>
|
||||
<filter id="conduit-blur" x="-30%" y="-80%" width="160%" height="260%">
|
||||
<feGaussianBlur stdDeviation="8" />
|
||||
</filter>
|
||||
</defs>
|
||||
<path
|
||||
d="M414 342C480 344 508 392 571 390C606 389 624 373 646 351"
|
||||
stroke="#D9D0DE"
|
||||
strokeWidth="14"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<motion.path
|
||||
d="M414 342C480 344 508 392 571 390C606 389 624 373 646 351"
|
||||
stroke="#A855F7"
|
||||
strokeOpacity="0.24"
|
||||
strokeWidth="28"
|
||||
strokeLinecap="round"
|
||||
filter="url(#conduit-blur)"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { opacity: 0.55, pathLength: 1 }
|
||||
: { opacity: [0.05, 0.05, 0.55, 0.55, 0.15], pathLength: [0, 0, 1, 1, 1] }
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.46, 0.7, 0.82, 1], ease: "easeInOut" }}
|
||||
/>
|
||||
<motion.path
|
||||
d="M414 342C480 344 508 392 571 390C606 389 624 373 646 351"
|
||||
stroke="url(#conduit-gradient)"
|
||||
strokeWidth="6"
|
||||
strokeLinecap="round"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { pathLength: 1, opacity: 0.85 }
|
||||
: { pathLength: [0, 0, 1, 1, 1], opacity: [0.25, 0.25, 1, 1, 0.45] }
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.46, 0.7, 0.82, 1], ease: "easeInOut" }}
|
||||
/>
|
||||
<motion.circle
|
||||
r="8"
|
||||
fill="#FCF9FF"
|
||||
stroke="#A855F7"
|
||||
strokeWidth="3"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { cx: 646, cy: 351, opacity: 0 }
|
||||
: {
|
||||
cx: [414, 414, 448, 505, 570, 620, 646, 646],
|
||||
cy: [342, 342, 348, 382, 390, 372, 351, 351],
|
||||
opacity: [0, 0, 1, 1, 1, 1, 0, 0],
|
||||
scale: [0.7, 0.7, 1, 1, 1, 1, 0.7, 0.7],
|
||||
}
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.49, 0.53, 0.59, 0.65, 0.7, 0.74, 1], ease: "easeInOut" }}
|
||||
/>
|
||||
<motion.circle
|
||||
r="5"
|
||||
fill="#D8B4FE"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { cx: 646, cy: 351, opacity: 0 }
|
||||
: {
|
||||
cx: [414, 414, 448, 505, 570, 620, 646, 646],
|
||||
cy: [342, 342, 348, 382, 390, 372, 351, 351],
|
||||
opacity: [0, 0, 1, 1, 1, 1, 0, 0],
|
||||
}
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.56, 0.6, 0.66, 0.72, 0.77, 0.81, 1], ease: "easeInOut" }}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div className="receiver-phone-shell">
|
||||
<span className="phone-side-button phone-side-button-one" />
|
||||
<span className="phone-side-button phone-side-button-two" />
|
||||
<div className="receiver-phone-frame">
|
||||
<div className="phone-screen">
|
||||
<div className="phone-status-bar">
|
||||
<span>9:41</span>
|
||||
<span className="phone-island" />
|
||||
<span className="phone-signal">● ◒</span>
|
||||
</div>
|
||||
<div className="phone-app-bar">
|
||||
<BrandMark className="phone-brand-mark" />
|
||||
<strong>Receive</strong>
|
||||
<span><Icon name="shield" /></span>
|
||||
</div>
|
||||
<div className="phone-transfer-card">
|
||||
<div className="phone-file-art"><Icon name="folder" /></div>
|
||||
<small>INCOMING FROM ALEX</small>
|
||||
<strong>summer-photos</strong>
|
||||
<span>42 files · 486 MB</span>
|
||||
</div>
|
||||
<div className="phone-state-area">
|
||||
<motion.div
|
||||
className="phone-state is-waiting"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { opacity: 0 }
|
||||
: { opacity: [1, 1, 0, 0] }
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.43, 0.5, 1] }}
|
||||
>
|
||||
<span className="state-symbol"><Icon name="lock" /></span>
|
||||
<strong>Waiting for approval</strong>
|
||||
<small>The sender stays in control.</small>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className="phone-state is-transferring"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { opacity: 0 }
|
||||
: { opacity: [0, 0, 1, 1, 0, 0] }
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.45, 0.51, 0.71, 0.78, 1] }}
|
||||
>
|
||||
<span className="transfer-percentage">68<span>%</span></span>
|
||||
<strong>Receiving securely</strong>
|
||||
<small>Direct path · 38 MB/s</small>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className="phone-state is-verified"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { opacity: 1, scale: 1 }
|
||||
: { opacity: [0, 0, 1, 1], scale: [0.92, 0.92, 1, 1] }
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.72, 0.79, 1], ease: "easeOut" }}
|
||||
>
|
||||
<span className="state-symbol is-success"><Icon name="verified" /></span>
|
||||
<strong>Received & verified</strong>
|
||||
<small>Saved to Downloads</small>
|
||||
</motion.div>
|
||||
</div>
|
||||
<div className="phone-home-indicator" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="hero-route-card"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { opacity: 1, y: 0 }
|
||||
: { opacity: [0, 1, 1, 1], y: [-8, 0, 0, 0] }
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.08, 0.92, 1], ease: "easeOut" }}
|
||||
>
|
||||
<span className="route-pulse" />
|
||||
<span>
|
||||
<small>ROUTE</small>
|
||||
<strong>Direct path established</strong>
|
||||
</span>
|
||||
<span className="route-latency">18 ms</span>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="hero-verified-card"
|
||||
animate={
|
||||
reduceMotion
|
||||
? { opacity: 1, y: 0, scale: 1 }
|
||||
: {
|
||||
opacity: [0, 0, 1, 1],
|
||||
y: [8, 8, 0, 0],
|
||||
scale: [0.96, 0.96, 1, 1],
|
||||
}
|
||||
}
|
||||
transition={{ ...repeating, times: [0, 0.74, 0.81, 1], ease: "easeOut" }}
|
||||
>
|
||||
<span><Icon name="verified" /></span>
|
||||
<span>
|
||||
<strong>Content verified</strong>
|
||||
<small>42 / 42 files match</small>
|
||||
</span>
|
||||
</motion.div>
|
||||
|
||||
<div className="hero-privacy-rail">
|
||||
<span><Icon name="lock" /> E2E encrypted</span>
|
||||
<i />
|
||||
<span>No hosted copy</span>
|
||||
<i />
|
||||
<span>Approval required</span>
|
||||
</div>
|
||||
</div>
|
||||
<figcaption className="sr-only">
|
||||
A VniDrop transfer moves from a desktop sender to a phone after receiver approval, then is
|
||||
verified on arrival.
|
||||
</figcaption>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
@@ -1,606 +0,0 @@
|
||||
/* Coordinated product transfer stage */
|
||||
.hero-visual {
|
||||
position: relative;
|
||||
width: min(100%, 760px);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero-stage {
|
||||
--pointer-x: 0;
|
||||
--pointer-y: 0;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 1.17;
|
||||
isolation: isolate;
|
||||
perspective: 1400px;
|
||||
}
|
||||
|
||||
.hero-stage-grid {
|
||||
position: absolute;
|
||||
z-index: -3;
|
||||
inset: 3% 0 4% 1%;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(159, 132, 173, 0.15);
|
||||
border-radius: 44px;
|
||||
background:
|
||||
linear-gradient(rgba(110, 86, 122, 0.055) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(110, 86, 122, 0.055) 1px, transparent 1px),
|
||||
radial-gradient(circle at 72% 35%, rgba(192, 132, 252, 0.13), transparent 38%),
|
||||
rgba(255, 255, 255, 0.2);
|
||||
background-size: 28px 28px, 28px 28px, auto, auto;
|
||||
mask-image: linear-gradient(to bottom, transparent 0, black 9%, black 88%, transparent 100%);
|
||||
}
|
||||
|
||||
.hero-stage-grid::before,
|
||||
.hero-stage-grid::after {
|
||||
position: absolute;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-style: solid;
|
||||
border-color: rgba(168, 85, 247, 0.35);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.hero-stage-grid::before {
|
||||
top: 18px;
|
||||
left: 18px;
|
||||
border-width: 1px 0 0 1px;
|
||||
}
|
||||
|
||||
.hero-stage-grid::after {
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
border-width: 0 1px 1px 0;
|
||||
}
|
||||
|
||||
.hero-stage-glow {
|
||||
position: absolute;
|
||||
z-index: -2;
|
||||
top: 18%;
|
||||
right: 9%;
|
||||
width: 45%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background: rgba(168, 85, 247, 0.16);
|
||||
filter: blur(70px);
|
||||
transform: translate(
|
||||
calc(var(--pointer-x) * 9px),
|
||||
calc(var(--pointer-y) * 9px)
|
||||
);
|
||||
}
|
||||
|
||||
.stage-coordinate {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
color: rgba(87, 70, 95, 0.48);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 7px;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.13em;
|
||||
}
|
||||
|
||||
.stage-coordinate-top {
|
||||
top: 1.5%;
|
||||
left: 5%;
|
||||
}
|
||||
|
||||
.stage-coordinate-side {
|
||||
top: 27%;
|
||||
right: -3%;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.desktop-product-shell {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 11%;
|
||||
left: 1.5%;
|
||||
width: 64%;
|
||||
height: 55%;
|
||||
transform: translate(
|
||||
calc(var(--pointer-x) * -5px),
|
||||
calc(var(--pointer-y) * -4px)
|
||||
)
|
||||
rotateY(calc(var(--pointer-x) * 1.4deg))
|
||||
rotateX(calc(var(--pointer-y) * -1.2deg));
|
||||
transform-origin: center;
|
||||
transition: transform 180ms ease-out;
|
||||
}
|
||||
|
||||
.desktop-product-window {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
border: 1px solid rgba(195, 186, 200, 0.95);
|
||||
border-radius: 20px;
|
||||
background: #fcfbfc;
|
||||
box-shadow:
|
||||
0 38px 90px rgba(52, 34, 62, 0.16),
|
||||
0 8px 24px rgba(52, 34, 62, 0.08),
|
||||
inset 0 1px white;
|
||||
}
|
||||
|
||||
.desktop-product-window::after {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(118deg, rgba(255, 255, 255, 0.5), transparent 24%, transparent 72%, rgba(168, 85, 247, 0.025));
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.desktop-window-bar {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
height: 34px;
|
||||
padding: 0 11px;
|
||||
border-bottom: 1px solid #e9e5eb;
|
||||
border-radius: 20px 20px 0 0;
|
||||
background: rgba(248, 247, 249, 0.92);
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.window-controls i {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: #d8d2dc;
|
||||
}
|
||||
|
||||
.window-controls i:first-child { background: #c084fc; }
|
||||
|
||||
.window-title {
|
||||
color: #777078;
|
||||
font-size: 8px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.window-security {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-self: end;
|
||||
gap: 4px;
|
||||
color: #7b707f;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 6px;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.window-security svg {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
color: var(--brand-600);
|
||||
}
|
||||
|
||||
.desktop-app-layout {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 50px 1fr;
|
||||
height: calc(100% - 34px);
|
||||
overflow: hidden;
|
||||
border-radius: 0 0 20px 20px;
|
||||
}
|
||||
|
||||
.desktop-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 9px 0 10px;
|
||||
border-right: 1px solid #ebe7ed;
|
||||
background: #f7f6f8;
|
||||
}
|
||||
|
||||
.product-brand-mark {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin-bottom: 2px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.desktop-sidebar-item {
|
||||
display: grid;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
place-items: center;
|
||||
border-radius: 9px;
|
||||
color: #8c848f;
|
||||
}
|
||||
|
||||
.desktop-sidebar-item.is-active {
|
||||
background: linear-gradient(135deg, #ead5fe, #f5eafe);
|
||||
color: var(--brand-700);
|
||||
box-shadow: inset 0 0 0 1px rgba(168, 85, 247, 0.1);
|
||||
}
|
||||
|
||||
.desktop-sidebar-item svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.desktop-sidebar-spacer { flex: 1; }
|
||||
|
||||
.desktop-profile {
|
||||
display: grid;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
place-items: center;
|
||||
border-radius: 9px;
|
||||
background: #232027;
|
||||
color: white;
|
||||
font-size: 7px;
|
||||
font-weight: 780;
|
||||
}
|
||||
|
||||
.desktop-transfer-view {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
padding: 14px 16px 12px;
|
||||
background: #fdfcfd;
|
||||
}
|
||||
|
||||
.desktop-view-heading {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
|
||||
.desktop-view-heading div {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.desktop-view-heading small {
|
||||
color: #9a919e;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 6px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.11em;
|
||||
}
|
||||
|
||||
.desktop-view-heading strong {
|
||||
color: #211d24;
|
||||
font-family: var(--font-display);
|
||||
font-size: 15px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.035em;
|
||||
}
|
||||
|
||||
.sharing-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 4px 7px;
|
||||
border: 1px solid #e2d2ee;
|
||||
border-radius: 999px;
|
||||
background: #f6edfe;
|
||||
color: #6f27e9;
|
||||
font-size: 7px;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.sharing-pill i {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: var(--brand-500);
|
||||
box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.1);
|
||||
}
|
||||
|
||||
.file-collection-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 64px 1fr auto;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
padding: 9px;
|
||||
border: 1px solid #e9e5eb;
|
||||
border-radius: 14px;
|
||||
background: #f7f6f8;
|
||||
}
|
||||
|
||||
.file-collage {
|
||||
display: grid;
|
||||
width: 64px;
|
||||
height: 52px;
|
||||
grid-template-columns: 1.15fr 0.85fr;
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
gap: 2px;
|
||||
overflow: hidden;
|
||||
border-radius: 9px;
|
||||
background: #e9e5eb;
|
||||
}
|
||||
|
||||
.file-collage span:first-child {
|
||||
grid-row: 1 / 3;
|
||||
background:
|
||||
radial-gradient(circle at 35% 23%, #ffe6b6 0 8%, transparent 9%),
|
||||
linear-gradient(145deg, #94b8d5 0 38%, #5a8b8f 39% 57%, #efbc7b 58%);
|
||||
}
|
||||
|
||||
.file-collage span:nth-child(2) {
|
||||
background: linear-gradient(150deg, #8a76a8 0 35%, #d4a985 36% 68%, #5c4356 69%);
|
||||
}
|
||||
|
||||
.file-collage span:nth-child(3) {
|
||||
background: linear-gradient(140deg, #ceddca 0 38%, #6d9b77 39% 61%, #d7bd92 62%);
|
||||
}
|
||||
|
||||
.file-collage span:nth-child(4) { display: none; }
|
||||
|
||||
.file-collection-copy {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.file-collection-copy strong {
|
||||
overflow: hidden;
|
||||
color: #27222a;
|
||||
font-size: 10px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.file-collection-copy > span {
|
||||
color: #7f7683;
|
||||
font-size: 7px;
|
||||
}
|
||||
|
||||
.file-collection-copy small {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-top: 5px;
|
||||
color: #85798a;
|
||||
font-size: 6px;
|
||||
}
|
||||
|
||||
.file-collection-copy svg {
|
||||
width: 9px;
|
||||
color: var(--brand-600);
|
||||
}
|
||||
|
||||
.collection-menu {
|
||||
align-self: start;
|
||||
padding: 0 2px;
|
||||
color: #9c929f;
|
||||
font-size: 9px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.receiver-section-heading {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
margin: 11px 2px 6px;
|
||||
}
|
||||
|
||||
.receiver-section-heading span {
|
||||
color: #373039;
|
||||
font-size: 8px;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.receiver-section-heading small {
|
||||
color: #938a96;
|
||||
font-size: 6px;
|
||||
}
|
||||
|
||||
.desktop-receiver-row {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 31px 1fr auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 48px;
|
||||
padding: 8px 9px 10px;
|
||||
border: 1px solid #e8e3ea;
|
||||
border-radius: 12px;
|
||||
background: white;
|
||||
box-shadow: 0 5px 15px rgba(49, 36, 55, 0.035);
|
||||
}
|
||||
|
||||
.receiver-avatar {
|
||||
display: grid;
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
place-items: center;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, #ead2fe, #c084fc);
|
||||
color: #5c1bb8;
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.receiver-identity {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.receiver-identity strong {
|
||||
overflow: hidden;
|
||||
color: #302a33;
|
||||
font-size: 8px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.receiver-identity small {
|
||||
color: #938a96;
|
||||
font-size: 6px;
|
||||
}
|
||||
|
||||
.receiver-state-stack {
|
||||
position: relative;
|
||||
width: 86px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.receiver-row-state {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 3px;
|
||||
font-size: 6px;
|
||||
font-weight: 720;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.receiver-row-state.is-waiting { color: #9a6716; }
|
||||
.receiver-row-state.is-sending { color: var(--brand-700); }
|
||||
.receiver-row-state.is-complete { color: #23855a; }
|
||||
.receiver-row-state svg { width: 9px; }
|
||||
|
||||
.receiver-progress-track {
|
||||
position: absolute;
|
||||
right: 9px;
|
||||
bottom: 5px;
|
||||
left: 48px;
|
||||
height: 2px;
|
||||
overflow: hidden;
|
||||
border-radius: 999px;
|
||||
background: #ede8ef;
|
||||
}
|
||||
|
||||
.receiver-progress-track span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(90deg, var(--brand-400), var(--brand-700));
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.desktop-transfer-foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: auto;
|
||||
padding: 8px 2px 0;
|
||||
color: #8e8492;
|
||||
font-size: 6px;
|
||||
}
|
||||
|
||||
.desktop-transfer-foot span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.desktop-transfer-foot svg {
|
||||
width: 9px;
|
||||
color: var(--brand-600);
|
||||
}
|
||||
|
||||
.approval-knock-card {
|
||||
position: absolute;
|
||||
z-index: 8;
|
||||
right: -8%;
|
||||
bottom: -19%;
|
||||
width: 238px;
|
||||
padding: 12px;
|
||||
border: 1px solid rgba(215, 202, 222, 0.95);
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: 0 24px 65px rgba(55, 31, 66, 0.2);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.approval-knock-top {
|
||||
display: grid;
|
||||
grid-template-columns: 30px 1fr 27px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.approval-knock-top > span:nth-child(2) {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.approval-knock-top small {
|
||||
color: #9a8da0;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 5.5px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.09em;
|
||||
}
|
||||
|
||||
.approval-knock-top strong {
|
||||
color: #27212b;
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.approval-lock {
|
||||
display: grid;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
place-items: center;
|
||||
border-radius: 9px;
|
||||
background: var(--brand-100);
|
||||
color: var(--brand-700);
|
||||
}
|
||||
|
||||
.approval-lock svg { width: 13px; }
|
||||
|
||||
.approval-knock-card p {
|
||||
margin: 9px 0;
|
||||
color: #756c79;
|
||||
font-size: 7px;
|
||||
}
|
||||
|
||||
.approval-knock-card p strong { color: #3b323e; }
|
||||
|
||||
.approval-knock-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.2fr;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.approval-knock-actions > span {
|
||||
display: inline-flex;
|
||||
min-height: 28px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
border: 1px solid #e6e0e9;
|
||||
border-radius: 8px;
|
||||
color: #685f6c;
|
||||
font-size: 7px;
|
||||
font-weight: 720;
|
||||
}
|
||||
|
||||
.approval-knock-actions .approval-accept {
|
||||
border-color: transparent;
|
||||
background: linear-gradient(135deg, var(--brand-500), var(--brand-700));
|
||||
box-shadow: 0 8px 18px rgba(132, 47, 238, 0.22);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.approval-accept svg { width: 10px; }
|
||||
|
||||
.hero-conduit {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -2,24 +2,13 @@ import type { SVGProps } from "react";
|
||||
|
||||
export type IconName =
|
||||
| "arrow"
|
||||
| "check"
|
||||
| "chevron"
|
||||
| "close"
|
||||
| "code"
|
||||
| "devices"
|
||||
| "file"
|
||||
| "folder"
|
||||
| "github"
|
||||
| "globe"
|
||||
| "lock"
|
||||
| "menu"
|
||||
| "nfc"
|
||||
| "qr"
|
||||
| "shield"
|
||||
| "spark"
|
||||
| "stop"
|
||||
| "verified"
|
||||
| "x";
|
||||
| "verified";
|
||||
|
||||
type IconProps = SVGProps<SVGSVGElement> & {
|
||||
name: IconName;
|
||||
@@ -33,21 +22,6 @@ export function Icon({ name, ...props }: IconProps) {
|
||||
<path d="m14 7 5 5-5 5" />
|
||||
</>
|
||||
),
|
||||
check: <path d="m5 12 4 4L19 6" />,
|
||||
chevron: <path d="m9 18 6-6-6-6" />,
|
||||
close: (
|
||||
<>
|
||||
<path d="M6 6l12 12" />
|
||||
<path d="M18 6 6 18" />
|
||||
</>
|
||||
),
|
||||
code: (
|
||||
<>
|
||||
<path d="m8 9-4 3 4 3" />
|
||||
<path d="m16 9 4 3-4 3" />
|
||||
<path d="m14 5-4 14" />
|
||||
</>
|
||||
),
|
||||
devices: (
|
||||
<>
|
||||
<rect x="3" y="5" width="13" height="10" rx="2" />
|
||||
@@ -55,12 +29,6 @@ export function Icon({ name, ...props }: IconProps) {
|
||||
<rect x="17" y="8" width="4" height="10" rx="1" />
|
||||
</>
|
||||
),
|
||||
file: (
|
||||
<>
|
||||
<path d="M6 3h8l4 4v14H6z" />
|
||||
<path d="M14 3v5h4M9 13h6M9 17h4" />
|
||||
</>
|
||||
),
|
||||
folder: (
|
||||
<path d="M3 7.5A2.5 2.5 0 0 1 5.5 5H10l2 2h6.5A2.5 2.5 0 0 1 21 9.5v7a2.5 2.5 0 0 1-2.5 2.5h-13A2.5 2.5 0 0 1 3 16.5z" />
|
||||
),
|
||||
@@ -72,30 +40,12 @@ export function Icon({ name, ...props }: IconProps) {
|
||||
fill="currentColor"
|
||||
/>
|
||||
),
|
||||
globe: (
|
||||
<>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M3 12h18M12 3a15 15 0 0 1 0 18M12 3a15 15 0 0 0 0 18" />
|
||||
</>
|
||||
),
|
||||
lock: (
|
||||
<>
|
||||
<rect x="5" y="10" width="14" height="11" rx="3" />
|
||||
<path d="M8 10V7a4 4 0 0 1 8 0v3M12 14v3" />
|
||||
</>
|
||||
),
|
||||
menu: (
|
||||
<>
|
||||
<path d="M4 7h16M4 12h16M4 17h16" />
|
||||
</>
|
||||
),
|
||||
nfc: (
|
||||
<>
|
||||
<path d="M8 7a7 7 0 0 1 0 10M5 4a11 11 0 0 1 0 16" />
|
||||
<path d="M16 7a7 7 0 0 0 0 10M19 4a11 11 0 0 0 0 16" />
|
||||
<circle cx="12" cy="12" r="1" />
|
||||
</>
|
||||
),
|
||||
qr: (
|
||||
<>
|
||||
<rect x="3" y="3" width="7" height="7" rx="1" />
|
||||
@@ -110,22 +60,12 @@ export function Icon({ name, ...props }: IconProps) {
|
||||
<path d="m8.5 12 2.2 2.2 4.8-5" />
|
||||
</>
|
||||
),
|
||||
spark: (
|
||||
<path d="m12 2 1.6 6.4L20 10l-6.4 1.6L12 18l-1.6-6.4L4 10l6.4-1.6z" />
|
||||
),
|
||||
stop: <rect x="5" y="5" width="14" height="14" rx="3" />,
|
||||
verified: (
|
||||
<>
|
||||
<path d="m12 3 2 2.1 2.9-.1.1 2.9 2 2.1-2 2.1-.1 2.9-2.9-.1-2 2.1-2-2.1-2.9.1-.1-2.9-2-2.1 2-2.1.1-2.9 2.9.1z" />
|
||||
<path d="m9 10 2 2 4-4" />
|
||||
</>
|
||||
),
|
||||
x: (
|
||||
<>
|
||||
<path d="M6 6l12 12" />
|
||||
<path d="M18 6 6 18" />
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,277 +0,0 @@
|
||||
@keyframes scene-orbits { to { transform: rotate(360deg); } }
|
||||
@keyframes laptop-files {
|
||||
0%, 100% { transform: translateY(0) rotate(0); }
|
||||
50% { transform: translateY(-5px) rotate(1deg); }
|
||||
}
|
||||
@keyframes conduit-energy { to { stroke-dashoffset: -38; } }
|
||||
@keyframes gate-hover {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-6px); }
|
||||
}
|
||||
@keyframes received-file {
|
||||
0%, 9% { opacity: 0.55; transform: translateY(12px) scale(0.92); }
|
||||
17%, 70% { opacity: 1; transform: translateY(0) scale(1); }
|
||||
83%, 100% { opacity: 0.55; transform: translateY(12px) scale(0.92); }
|
||||
}
|
||||
@keyframes hero-packet-one {
|
||||
0%, 6% { opacity: 0; transform: translate(245px, 320px) scale(0.55) rotate(-8deg); }
|
||||
14% { opacity: 1; }
|
||||
43% { opacity: 1; transform: translate(380px, 427px) scale(0.82) rotate(5deg); }
|
||||
54% { opacity: 1; transform: translate(415px, 430px) scale(0.82) rotate(2deg); }
|
||||
84% { opacity: 1; transform: translate(556px, 296px) scale(0.65) rotate(10deg); }
|
||||
92%, 100% { opacity: 0; transform: translate(585px, 270px) scale(0.35) rotate(14deg); }
|
||||
}
|
||||
@keyframes hero-packet-two {
|
||||
0%, 6% { opacity: 0; transform: translate(255px, 327px) scale(0.5) rotate(8deg); }
|
||||
15% { opacity: 0.85; }
|
||||
45% { opacity: 0.9; transform: translate(382px, 440px) scale(0.75) rotate(-4deg); }
|
||||
56% { opacity: 0.9; transform: translate(414px, 436px) scale(0.75) rotate(-2deg); }
|
||||
85% { opacity: 0.8; transform: translate(560px, 308px) scale(0.55) rotate(-10deg); }
|
||||
93%, 100% { opacity: 0; transform: translate(588px, 278px) scale(0.25) rotate(-15deg); }
|
||||
}
|
||||
@keyframes hero-spark-one {
|
||||
0%, 12% { opacity: 0; transform: translate(266px, 346px) scale(0); }
|
||||
19% { opacity: 1; transform: translate(285px, 360px) scale(1); }
|
||||
29%, 100% { opacity: 0; transform: translate(310px, 380px) scale(0); }
|
||||
}
|
||||
@keyframes hero-spark-two {
|
||||
0%, 77% { opacity: 0; transform: translate(545px, 307px) scale(0); }
|
||||
87% { opacity: 1; transform: translate(575px, 279px) scale(1); }
|
||||
97%, 100% { opacity: 0; transform: translate(603px, 260px) scale(0); }
|
||||
}
|
||||
@keyframes status-hover {
|
||||
0%, 100% { margin-top: 0; }
|
||||
50% { margin-top: -7px; }
|
||||
}
|
||||
@keyframes status-pulse {
|
||||
0%, 100% { box-shadow: 0 0 0 3px rgba(43, 196, 119, 0.1); }
|
||||
50% { box-shadow: 0 0 0 7px rgba(43, 196, 119, 0); }
|
||||
}
|
||||
@keyframes flow-glow {
|
||||
0%, 100% { transform: scale(0.8); opacity: 0.6; }
|
||||
50% { transform: scale(1.2); opacity: 1; }
|
||||
}
|
||||
@keyframes flow-drop {
|
||||
0%, 7% { offset-distance: 0%; opacity: 0; }
|
||||
13% { opacity: 1; }
|
||||
47% { offset-distance: 51%; opacity: 1; }
|
||||
57% { offset-distance: 51%; opacity: 1; }
|
||||
91% { offset-distance: 100%; opacity: 1; }
|
||||
97%, 100% { offset-distance: 100%; opacity: 0; }
|
||||
}
|
||||
@keyframes gate-breathe {
|
||||
0%, 100% { transform: scale(0.98); }
|
||||
50% { transform: scale(1.04); }
|
||||
}
|
||||
@keyframes complete-ring {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(168, 85, 247, 0.18); }
|
||||
50% { box-shadow: 0 0 0 13px rgba(168, 85, 247, 0); }
|
||||
}
|
||||
@keyframes invite-panel-enter {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
@keyframes qr-card-float {
|
||||
0%, 100% { transform: rotate(2deg) translateY(0); }
|
||||
50% { transform: rotate(-1deg) translateY(-8px); }
|
||||
}
|
||||
@keyframes qr-cell {
|
||||
from { opacity: 0; transform: scale(0.2); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
@keyframes qr-scan {
|
||||
0%, 100% { top: 40px; opacity: 0; }
|
||||
12%, 88% { opacity: 1; }
|
||||
50% { top: 222px; }
|
||||
}
|
||||
@keyframes nfc-dot {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(168, 85, 247, 0.2); }
|
||||
50% { box-shadow: 0 0 0 19px rgba(168, 85, 247, 0); }
|
||||
}
|
||||
@keyframes nfc-wave {
|
||||
0%, 100% { opacity: 0.25; transform: translateY(-50%) scale(0.88); }
|
||||
50% { opacity: 1; transform: translateY(-50%) scale(1.04); }
|
||||
}
|
||||
@keyframes vnd-drop {
|
||||
0%, 8% { transform: translateX(-50%) translateY(-16px) rotate(-5deg); }
|
||||
20%, 78% { transform: translateX(-50%) translateY(0) rotate(-2deg); }
|
||||
88%, 100% { transform: translateX(-50%) translateY(-16px) rotate(-5deg); }
|
||||
}
|
||||
@keyframes avatar-pulse {
|
||||
0% { box-shadow: 0 0 0 0 rgba(168, 85, 247, 0.24); }
|
||||
75%, 100% { box-shadow: 0 0 0 14px rgba(168, 85, 247, 0); }
|
||||
}
|
||||
@keyframes approval-state-enter {
|
||||
from { opacity: 0; transform: translateY(7px); }
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
@keyframes progress-fill {
|
||||
from { width: 0; }
|
||||
to { width: 68%; }
|
||||
}
|
||||
@keyframes network-dash { to { stroke-dashoffset: -44; } }
|
||||
@keyframes relay-ring {
|
||||
0% { opacity: 0.6; transform: translateX(-50%) scale(0.72); }
|
||||
85%, 100% { opacity: 0; transform: translateX(-50%) scale(1.2); }
|
||||
}
|
||||
@keyframes network-packet-top {
|
||||
0%, 8% { left: 15%; top: 40%; opacity: 0; transform: rotate(-7deg) scale(0.7); }
|
||||
15% { opacity: 1; }
|
||||
48% { left: 47%; top: 20%; opacity: 1; transform: rotate(3deg) scale(0.9); }
|
||||
85% { left: 82%; top: 40%; opacity: 1; transform: rotate(8deg) scale(0.72); }
|
||||
94%, 100% { left: 85%; top: 42%; opacity: 0; transform: rotate(10deg) scale(0.4); }
|
||||
}
|
||||
@keyframes network-packet-bottom {
|
||||
0%, 8% { left: 16%; top: 50%; opacity: 0; transform: rotate(8deg) scale(0.65); }
|
||||
15% { opacity: 0.8; }
|
||||
48% { left: 48%; top: 69%; opacity: 0.8; transform: rotate(-3deg) scale(0.8); }
|
||||
84% { left: 82%; top: 50%; opacity: 0.8; transform: rotate(-8deg) scale(0.7); }
|
||||
94%, 100% { left: 85%; top: 48%; opacity: 0; transform: rotate(-10deg) scale(0.4); }
|
||||
}
|
||||
@keyframes orbit-spin {
|
||||
from { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
to { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
@keyframes platform-glow {
|
||||
0%, 100% { opacity: 0.45; transform: scale(0.82); }
|
||||
50% { opacity: 1; transform: scale(1.15); }
|
||||
}
|
||||
@keyframes platform-chip-float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-8px); }
|
||||
}
|
||||
|
||||
@media (max-width: 1120px) {
|
||||
.flow-layout {
|
||||
grid-template-columns: minmax(450px, 1fr) minmax(350px, 0.8fr);
|
||||
gap: 45px;
|
||||
}
|
||||
|
||||
.flow-device { transform: scale(0.88); }
|
||||
.flow-device-sender { transform: scale(0.88) rotate(-6deg); transform-origin: top left; }
|
||||
.flow-device-receiver { transform: scale(0.88) rotate(5deg); transform-origin: bottom right; }
|
||||
|
||||
.invite-panel {
|
||||
grid-template-columns: minmax(0, 0.85fr) minmax(420px, 1.15fr);
|
||||
}
|
||||
|
||||
.invite-copy { padding: 45px; }
|
||||
.network-visual { min-height: 500px; }
|
||||
}
|
||||
|
||||
@media (max-width: 920px) {
|
||||
.hero-visual {
|
||||
max-width: 720px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.flow-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.flow-visual {
|
||||
position: relative;
|
||||
top: auto;
|
||||
}
|
||||
|
||||
.invite-panel {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.invite-art {
|
||||
min-height: 450px;
|
||||
border-top: 1px solid var(--line);
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.network-visual {
|
||||
min-height: 520px;
|
||||
}
|
||||
|
||||
.platform-orbit {
|
||||
max-width: 620px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.hero-stage {
|
||||
width: 112%;
|
||||
margin-left: -6%;
|
||||
}
|
||||
|
||||
.hero-status-secure {
|
||||
top: 0;
|
||||
right: 2%;
|
||||
transform: scale(0.88) rotate(2deg);
|
||||
transform-origin: top right;
|
||||
}
|
||||
|
||||
.hero-status-route { left: 4%; transform: scale(0.88) rotate(-3deg); transform-origin: top left; }
|
||||
.hero-file-label { bottom: 3%; left: 4%; transform: scale(0.88) rotate(-2deg); transform-origin: bottom left; }
|
||||
|
||||
.flow-visual {
|
||||
height: 540px;
|
||||
border-radius: 21px;
|
||||
}
|
||||
|
||||
.flow-device {
|
||||
width: 137px;
|
||||
height: 215px;
|
||||
}
|
||||
|
||||
.flow-device-sender { top: 24px; left: 16px; }
|
||||
.flow-device-receiver { right: 14px; bottom: 20px; }
|
||||
.flow-track { inset: 0 -2%; width: 104%; height: 100%; }
|
||||
.flow-annotation { display: none; }
|
||||
|
||||
.flow-step {
|
||||
grid-template-columns: auto 1fr;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.invite-tabs button {
|
||||
min-height: 59px;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.invite-tabs svg { width: 17px; }
|
||||
.invite-copy { padding: 42px 24px; }
|
||||
.invite-art { min-height: 390px; }
|
||||
.invite-qr-art { transform: scale(0.86) rotate(2deg); }
|
||||
.invite-nfc-art { transform: scale(0.82); }
|
||||
.invite-file-art { transform: scale(0.85); }
|
||||
|
||||
.approval-body { min-height: 370px; padding: 28px 20px; }
|
||||
.approval-heading h3 { font-size: 18px; }
|
||||
.approval-actions { grid-template-columns: 1fr 1.2fr; }
|
||||
|
||||
.network-visual {
|
||||
min-height: 430px;
|
||||
border-radius: 21px;
|
||||
}
|
||||
|
||||
.network-device {
|
||||
top: 150px;
|
||||
width: 77px;
|
||||
height: 112px;
|
||||
}
|
||||
|
||||
.network-device-left { left: 13px; }
|
||||
.network-device-right { right: 13px; }
|
||||
.network-device svg { width: 24px; }
|
||||
.network-relay-node { bottom: 29px; }
|
||||
.network-relay-node small { max-width: 160px; text-align: center; }
|
||||
.network-direct-label { top: 43px; }
|
||||
|
||||
.platform-orbit { width: 112%; margin-left: -6%; }
|
||||
.platform-center { width: 112px; height: 112px; border-radius: 30px; }
|
||||
.platform-center img { width: 68px; height: 68px; }
|
||||
.platform-chip { width: 48px; height: 48px; padding: 0; }
|
||||
.platform-chip .platform-glyph { width: 25px; height: 25px; }
|
||||
.platform-chip-1 { left: 10%; }
|
||||
.platform-chip-2 { right: 0; }
|
||||
.platform-chip-3 { right: -2%; }
|
||||
.platform-chip-4 { left: 17%; }
|
||||
}
|
||||
@@ -1,405 +0,0 @@
|
||||
/* Invitation methods */
|
||||
.invite-showcase {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 25px;
|
||||
background: white;
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.invite-tabs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: #f7f4f8;
|
||||
}
|
||||
|
||||
.invite-tabs button {
|
||||
display: inline-flex;
|
||||
min-height: 66px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
border: 0;
|
||||
border-right: 1px solid var(--line);
|
||||
background: transparent;
|
||||
color: var(--ink-muted);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: color 180ms ease, background 180ms ease;
|
||||
}
|
||||
|
||||
.invite-tabs button:last-child { border-right: 0; }
|
||||
.invite-tabs button:hover { color: var(--ink); }
|
||||
.invite-tabs button.is-active {
|
||||
position: relative;
|
||||
background: white;
|
||||
color: var(--brand-600);
|
||||
}
|
||||
|
||||
.invite-tabs button.is-active::after {
|
||||
position: absolute;
|
||||
right: 20%;
|
||||
bottom: -1px;
|
||||
left: 20%;
|
||||
height: 2px;
|
||||
border-radius: 999px 999px 0 0;
|
||||
background: var(--brand-500);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.invite-tabs svg { width: 19px; }
|
||||
|
||||
.invite-panel {
|
||||
display: grid;
|
||||
min-height: 490px;
|
||||
grid-template-columns: minmax(0, 0.83fr) minmax(480px, 1.17fr);
|
||||
animation: invite-panel-enter 420ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
|
||||
.invite-copy {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 60px;
|
||||
}
|
||||
|
||||
.mono-label {
|
||||
color: var(--brand-600);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.11em;
|
||||
}
|
||||
|
||||
.invite-copy h3 {
|
||||
max-width: 430px;
|
||||
margin: 12px 0 18px;
|
||||
font-size: clamp(2rem, 3vw, 3.15rem);
|
||||
letter-spacing: -0.055em;
|
||||
line-height: 1.06;
|
||||
}
|
||||
|
||||
.invite-copy > p {
|
||||
max-width: 440px;
|
||||
margin-bottom: 30px;
|
||||
color: var(--ink-soft);
|
||||
font-size: 15px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.invite-detail {
|
||||
display: flex;
|
||||
gap: 11px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.invite-detail > svg {
|
||||
width: 19px;
|
||||
flex: 0 0 auto;
|
||||
color: var(--brand-600);
|
||||
}
|
||||
|
||||
.invite-detail strong,
|
||||
.invite-detail small {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.invite-detail strong {
|
||||
margin-bottom: 3px;
|
||||
color: #3c3440;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.invite-detail small {
|
||||
color: var(--ink-muted);
|
||||
font-size: 9px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.invite-art {
|
||||
position: relative;
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
place-items: center;
|
||||
border-left: 1px solid var(--line);
|
||||
background:
|
||||
linear-gradient(rgba(116, 86, 130, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(116, 86, 130, 0.05) 1px, transparent 1px),
|
||||
radial-gradient(circle at center, #fbf7fd, #f2edf5);
|
||||
background-size: 24px 24px, 24px 24px, auto;
|
||||
}
|
||||
|
||||
.invite-art::before {
|
||||
position: absolute;
|
||||
width: 330px;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background: rgba(168, 85, 247, 0.14);
|
||||
content: "";
|
||||
filter: blur(55px);
|
||||
}
|
||||
|
||||
.invite-qr-art {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 265px;
|
||||
height: 265px;
|
||||
place-items: center;
|
||||
border: 1px solid #e0d5e5;
|
||||
border-radius: 25px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 30px 80px rgba(59, 35, 70, 0.18);
|
||||
transform: rotate(2deg);
|
||||
animation: qr-card-float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.invite-qr-grid {
|
||||
display: grid;
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
grid-template-columns: repeat(9, 1fr);
|
||||
grid-template-rows: repeat(9, 1fr);
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.invite-qr-grid span {
|
||||
border-radius: 2px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.invite-qr-grid span.is-filled {
|
||||
background: #2b2230;
|
||||
animation: qr-cell 600ms ease backwards;
|
||||
}
|
||||
|
||||
.invite-qr-grid span:nth-child(3n) { animation-delay: 80ms; }
|
||||
.invite-qr-grid span:nth-child(4n) { animation-delay: 160ms; }
|
||||
.invite-qr-grid span:nth-child(7n) { animation-delay: 230ms; }
|
||||
|
||||
.qr-scan-line {
|
||||
position: absolute;
|
||||
right: 34px;
|
||||
left: 34px;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, transparent, var(--brand-500), transparent);
|
||||
box-shadow: 0 0 13px var(--brand-400);
|
||||
animation: qr-scan 3.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.qr-corner {
|
||||
position: absolute;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-color: var(--brand-500);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.qr-corner-one { top: 21px; left: 21px; border-width: 2px 0 0 2px; border-radius: 7px 0 0; }
|
||||
.qr-corner-two { top: 21px; right: 21px; border-width: 2px 2px 0 0; border-radius: 0 7px 0 0; }
|
||||
.qr-corner-three { right: 21px; bottom: 21px; border-width: 0 2px 2px 0; border-radius: 0 0 7px; }
|
||||
.qr-corner-four { bottom: 21px; left: 21px; border-width: 0 0 2px 2px; border-radius: 0 0 0 7px; }
|
||||
|
||||
.invite-nfc-art {
|
||||
position: relative;
|
||||
width: 440px;
|
||||
height: 330px;
|
||||
}
|
||||
|
||||
.nfc-device {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: 155px;
|
||||
height: 300px;
|
||||
border: 7px solid #d7cfdc;
|
||||
border-radius: 30px;
|
||||
background: #211c25;
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: rotate(-8deg);
|
||||
}
|
||||
|
||||
.nfc-device::after {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
left: 50%;
|
||||
width: 50px;
|
||||
height: 7px;
|
||||
border-radius: 999px;
|
||||
background: #09070a;
|
||||
content: "";
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.nfc-screen-dot {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border: 1px solid rgba(192, 132, 252, 0.55);
|
||||
border-radius: 50%;
|
||||
background: rgba(168, 85, 247, 0.2);
|
||||
transform: translate(-50%, -50%);
|
||||
animation: nfc-dot 2.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.nfc-tag {
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
bottom: 30px;
|
||||
display: grid;
|
||||
width: 138px;
|
||||
height: 138px;
|
||||
place-items: center;
|
||||
border: 1px solid #d9cfe0;
|
||||
border-radius: 28px;
|
||||
background: white;
|
||||
box-shadow: 0 25px 70px rgba(52, 31, 62, 0.18);
|
||||
color: var(--brand-600);
|
||||
transform: rotate(8deg);
|
||||
}
|
||||
|
||||
.nfc-tag svg { width: 46px; }
|
||||
.nfc-tag small {
|
||||
position: absolute;
|
||||
bottom: 18px;
|
||||
color: var(--ink-muted);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 8px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.nfc-waves {
|
||||
position: absolute;
|
||||
top: 75px;
|
||||
left: 145px;
|
||||
width: 185px;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.nfc-waves span {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
border: 2px solid var(--brand-500);
|
||||
border-top-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
border-left-color: transparent;
|
||||
border-radius: 50%;
|
||||
transform: translateY(-50%);
|
||||
animation: nfc-wave 2.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.nfc-waves span:nth-child(1) { width: 55px; height: 70px; }
|
||||
.nfc-waves span:nth-child(2) { width: 98px; height: 112px; animation-delay: 180ms; }
|
||||
.nfc-waves span:nth-child(3) { width: 142px; height: 154px; animation-delay: 360ms; }
|
||||
.nfc-waves i {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 15px;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
background: var(--brand-500);
|
||||
box-shadow: 0 0 0 8px rgba(168, 85, 247, 0.13);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.invite-file-art {
|
||||
position: relative;
|
||||
width: 410px;
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
.vnd-file {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 13px;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
width: 218px;
|
||||
height: 270px;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
border: 1px solid #d8cbe0;
|
||||
border-radius: 23px;
|
||||
background: linear-gradient(145deg, #fff, #f7f0fb);
|
||||
box-shadow: 0 30px 80px rgba(59, 35, 70, 0.2);
|
||||
transform: translateX(-50%) rotate(-2deg);
|
||||
animation: vnd-drop 4.8s cubic-bezier(0.3, 0.75, 0.3, 1) infinite;
|
||||
}
|
||||
|
||||
.vnd-fold {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
right: -1px;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
border-bottom: 1px solid #d1c1dc;
|
||||
border-left: 1px solid #d1c1dc;
|
||||
border-radius: 0 23px 0 16px;
|
||||
background: #e7cdf9;
|
||||
clip-path: polygon(0 0, 100% 100%, 0 100%);
|
||||
}
|
||||
|
||||
.vnd-file > svg {
|
||||
width: 58px;
|
||||
margin-bottom: 20px;
|
||||
color: var(--brand-600);
|
||||
}
|
||||
|
||||
.vnd-file strong {
|
||||
font-family: var(--font-display);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.vnd-file small {
|
||||
color: var(--ink-muted);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.vnd-file code {
|
||||
margin-top: 18px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 7px;
|
||||
background: var(--brand-100);
|
||||
color: var(--brand-600);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.file-tray {
|
||||
position: absolute;
|
||||
right: 37px;
|
||||
bottom: 7px;
|
||||
left: 37px;
|
||||
height: 73px;
|
||||
border: 2px solid #cabbd2;
|
||||
border-radius: 19px;
|
||||
background: rgba(237, 228, 242, 0.85);
|
||||
transform: perspective(250px) rotateX(55deg);
|
||||
}
|
||||
|
||||
.file-tray span {
|
||||
position: absolute;
|
||||
inset: 14px;
|
||||
border: 1px dashed #aa8cbc;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.file-art-orbit {
|
||||
position: absolute;
|
||||
border: 1px solid rgba(168, 85, 247, 0.2);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.file-art-orbit-one { inset: 14px 32px 44px; animation: scene-orbits 24s linear infinite; }
|
||||
.file-art-orbit-two { inset: 52px 70px 80px; border-style: dashed; animation: scene-orbits 18s linear infinite reverse; }
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Icon, type IconName } from "@/components/icons";
|
||||
|
||||
type Method = "qr" | "nfc" | "file";
|
||||
|
||||
const methods: Array<{
|
||||
id: Method;
|
||||
icon: IconName;
|
||||
label: string;
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
text: string;
|
||||
}> = [
|
||||
{
|
||||
id: "qr",
|
||||
icon: "qr",
|
||||
label: "QR code",
|
||||
eyebrow: "IN THE SAME ROOM",
|
||||
title: "Point. Scan. Connected.",
|
||||
text: "Show the invitation on one screen and scan it from the other. No contact exchange required.",
|
||||
},
|
||||
{
|
||||
id: "nfc",
|
||||
icon: "nfc",
|
||||
label: "NFC",
|
||||
eyebrow: "THE QUICKEST TAP",
|
||||
title: "Bring devices together.",
|
||||
text: "Write or open an invitation with an NFC tag for a physical, close-range handoff.",
|
||||
},
|
||||
{
|
||||
id: "file",
|
||||
icon: "file",
|
||||
label: ".vnd file",
|
||||
eyebrow: "SEND IT ANYWHERE",
|
||||
title: "An invitation that travels.",
|
||||
text: "Save the small invitation as a .vnd file, then pass it through the channel you already use.",
|
||||
},
|
||||
];
|
||||
|
||||
function QrArt() {
|
||||
const filled = new Set([
|
||||
0, 1, 2, 3, 4, 6, 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 28, 29, 30, 31, 32,
|
||||
36, 38, 40, 42, 44, 48, 49, 50, 51, 52, 54, 56, 58, 60, 62, 64, 66, 67, 69,
|
||||
72, 73, 74, 76, 78, 79, 80,
|
||||
]);
|
||||
return (
|
||||
<div className="invite-qr-art">
|
||||
<div className="invite-qr-grid">
|
||||
{Array.from({ length: 81 }, (_, index) => (
|
||||
<span key={index} className={filled.has(index) ? "is-filled" : ""} />
|
||||
))}
|
||||
</div>
|
||||
<span className="qr-scan-line" />
|
||||
<span className="qr-corner qr-corner-one" />
|
||||
<span className="qr-corner qr-corner-two" />
|
||||
<span className="qr-corner qr-corner-three" />
|
||||
<span className="qr-corner qr-corner-four" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NfcArt() {
|
||||
return (
|
||||
<div className="invite-nfc-art">
|
||||
<div className="nfc-device nfc-device-left">
|
||||
<span className="nfc-screen-dot" />
|
||||
</div>
|
||||
<div className="nfc-waves">
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
<i />
|
||||
</div>
|
||||
<div className="nfc-tag">
|
||||
<Icon name="nfc" />
|
||||
<small>VniDrop</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FileArt() {
|
||||
return (
|
||||
<div className="invite-file-art">
|
||||
<span className="file-art-orbit file-art-orbit-one" />
|
||||
<span className="file-art-orbit file-art-orbit-two" />
|
||||
<div className="vnd-file">
|
||||
<span className="vnd-fold" />
|
||||
<Icon name="file" />
|
||||
<strong>Weekend trip</strong>
|
||||
<small>VniDrop invitation</small>
|
||||
<code>.vnd</code>
|
||||
</div>
|
||||
<div className="file-tray">
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function InvitationShowcase() {
|
||||
const [active, setActive] = useState<Method>("qr");
|
||||
const current = methods.find((method) => method.id === active) ?? methods[0];
|
||||
|
||||
return (
|
||||
<div className="invite-showcase">
|
||||
<div className="invite-tabs" role="tablist" aria-label="Invitation methods">
|
||||
{methods.map((method) => (
|
||||
<button
|
||||
key={method.id}
|
||||
id={`invite-tab-${method.id}`}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={active === method.id}
|
||||
aria-controls="invite-panel"
|
||||
className={active === method.id ? "is-active" : ""}
|
||||
onClick={() => setActive(method.id)}
|
||||
>
|
||||
<Icon name={method.icon} />
|
||||
{method.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
id="invite-panel"
|
||||
className="invite-panel"
|
||||
role="tabpanel"
|
||||
aria-labelledby={`invite-tab-${active}`}
|
||||
key={active}
|
||||
>
|
||||
<div className="invite-copy">
|
||||
<span className="mono-label">{current.eyebrow}</span>
|
||||
<h3>{current.title}</h3>
|
||||
<p>{current.text}</p>
|
||||
<div className="invite-detail">
|
||||
<Icon name="lock" />
|
||||
<span>
|
||||
<strong>The invitation is a private access link.</strong>
|
||||
<small>Only share it with people you intend to receive the transfer.</small>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="invite-art" aria-hidden="true">
|
||||
{active === "qr" && <QrArt />}
|
||||
{active === "nfc" && <NfcArt />}
|
||||
{active === "file" && <FileArt />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Icon } from "@/components/icons";
|
||||
|
||||
export function NetworkIllustration() {
|
||||
return (
|
||||
<div className="network-visual" aria-hidden="true">
|
||||
<div className="network-grid" />
|
||||
<div className="network-device network-device-left">
|
||||
<span className="network-device-top" />
|
||||
<Icon name="file" />
|
||||
<small>SENDER</small>
|
||||
</div>
|
||||
<div className="network-device network-device-right">
|
||||
<span className="network-device-top" />
|
||||
<Icon name="check" />
|
||||
<small>RECEIVER</small>
|
||||
</div>
|
||||
<svg className="network-routes" viewBox="0 0 760 430">
|
||||
<defs>
|
||||
<linearGradient id="network-route-gradient" x1="115" y1="210" x2="650" y2="210" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="#D08BFF" />
|
||||
<stop offset="0.5" stopColor="#A855F7" />
|
||||
<stop offset="1" stopColor="#7C2AEF" />
|
||||
</linearGradient>
|
||||
<filter id="network-route-glow" x="-30%" y="-100%" width="160%" height="300%">
|
||||
<feGaussianBlur stdDeviation="8" />
|
||||
</filter>
|
||||
</defs>
|
||||
<path className="network-direct-glow" d="M134 227C279 92 491 92 626 227" />
|
||||
<path className="network-direct" d="M134 227C279 92 491 92 626 227" />
|
||||
<path className="network-direct-pulse" d="M134 227C279 92 491 92 626 227" />
|
||||
<path className="network-relay-base" d="M134 247c126 87 371 87 492 0" />
|
||||
<path className="network-relay-line" d="M134 247c126 87 371 87 492 0" />
|
||||
</svg>
|
||||
<div className="network-direct-label">
|
||||
<span className="live-dot" />
|
||||
DIRECT WHEN POSSIBLE
|
||||
</div>
|
||||
<div className="network-relay-node">
|
||||
<span className="relay-ring relay-ring-one" />
|
||||
<span className="relay-ring relay-ring-two" />
|
||||
<div className="relay-core">
|
||||
<Icon name="lock" />
|
||||
</div>
|
||||
<strong>Encrypted relay</strong>
|
||||
<small>Forwards packets · stores no transfer copy</small>
|
||||
</div>
|
||||
<div className="network-packet network-packet-one"><Icon name="file" /></div>
|
||||
<div className="network-packet network-packet-two"><Icon name="file" /></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
/* Direct / relay network scene */
|
||||
.network-visual {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 520px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #302737;
|
||||
border-radius: 27px;
|
||||
background: radial-gradient(circle at center, rgba(168, 85, 247, 0.1), transparent 47%), #151117;
|
||||
}
|
||||
|
||||
.network-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
|
||||
background-size: 30px 30px;
|
||||
mask-image: radial-gradient(circle at center, black, transparent 79%);
|
||||
}
|
||||
|
||||
.network-device {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
top: 178px;
|
||||
display: grid;
|
||||
width: 105px;
|
||||
height: 145px;
|
||||
place-items: center;
|
||||
border: 5px solid #3b3340;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(145deg, #29222d, #171419);
|
||||
box-shadow: 0 22px 60px rgba(0, 0, 0, 0.38);
|
||||
color: #d89bff;
|
||||
}
|
||||
|
||||
.network-device-left { left: 30px; transform: rotate(-5deg); }
|
||||
.network-device-right { right: 30px; transform: rotate(5deg); }
|
||||
.network-device svg { width: 31px; }
|
||||
.network-device small {
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
color: #756b79;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 7px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.09em;
|
||||
}
|
||||
|
||||
.network-device-top {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 50%;
|
||||
width: 35px;
|
||||
height: 5px;
|
||||
border-radius: 999px;
|
||||
background: #080609;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.network-routes {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
inset: 34px 0 auto;
|
||||
width: 100%;
|
||||
height: calc(100% - 40px);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.network-direct-glow,
|
||||
.network-direct,
|
||||
.network-direct-pulse,
|
||||
.network-relay-base,
|
||||
.network-relay-line {
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.network-direct-glow {
|
||||
stroke: rgba(168, 85, 247, 0.26);
|
||||
stroke-width: 20;
|
||||
filter: url(#network-route-glow);
|
||||
}
|
||||
|
||||
.network-direct {
|
||||
stroke: #3d3343;
|
||||
stroke-width: 8;
|
||||
}
|
||||
|
||||
.network-direct-pulse {
|
||||
stroke: url(#network-route-gradient);
|
||||
stroke-width: 3;
|
||||
stroke-dasharray: 4 18;
|
||||
animation: network-dash 2.2s linear infinite;
|
||||
}
|
||||
|
||||
.network-relay-base {
|
||||
stroke: #2e2732;
|
||||
stroke-width: 5;
|
||||
}
|
||||
|
||||
.network-relay-line {
|
||||
stroke: #776681;
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: 4 11;
|
||||
animation: network-dash 3.4s linear infinite reverse;
|
||||
}
|
||||
|
||||
.network-direct-label {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
top: 62px;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
padding: 7px 10px;
|
||||
border: 1px solid #372c3c;
|
||||
border-radius: 999px;
|
||||
background: rgba(25, 20, 28, 0.82);
|
||||
color: #9a8fa0;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 7px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.network-relay-node {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
bottom: 39px;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.relay-core {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: grid;
|
||||
width: 66px;
|
||||
height: 66px;
|
||||
margin-bottom: 11px;
|
||||
place-items: center;
|
||||
border: 1px solid #5c426a;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(145deg, #312139, #1d1721);
|
||||
box-shadow: 0 0 45px rgba(168, 85, 247, 0.18);
|
||||
color: #d294fc;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.relay-core svg { width: 24px; transform: rotate(-45deg); }
|
||||
.network-relay-node strong { color: #d9d1dc; font-size: 10px; }
|
||||
.network-relay-node small { color: #756b79; font-size: 7px; }
|
||||
|
||||
.relay-ring {
|
||||
position: absolute;
|
||||
top: -18px;
|
||||
left: 50%;
|
||||
border: 1px solid rgba(168, 85, 247, 0.3);
|
||||
border-radius: 50%;
|
||||
transform: translateX(-50%);
|
||||
animation: relay-ring 3s ease-out infinite;
|
||||
}
|
||||
|
||||
.relay-ring-one { width: 100px; height: 100px; }
|
||||
.relay-ring-two { width: 136px; height: 136px; animation-delay: 1.2s; }
|
||||
|
||||
.network-packet {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
display: grid;
|
||||
width: 26px;
|
||||
height: 32px;
|
||||
place-items: center;
|
||||
border: 1px solid #aa65d2;
|
||||
border-radius: 6px;
|
||||
background: #2c2231;
|
||||
color: #d69bfa;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.network-packet svg { width: 13px; }
|
||||
.network-packet-one { animation: network-packet-top 6s ease-in-out infinite; }
|
||||
.network-packet-two { animation: network-packet-bottom 7s ease-in-out 2.2s infinite; }
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { BrandMark } from "@/components/brand";
|
||||
import { PlatformIcon, supportedPlatforms } from "@/components/platform-icon";
|
||||
|
||||
export function PlatformOrbit() {
|
||||
return (
|
||||
<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-inner" aria-hidden="true" />
|
||||
<div className="platform-center" aria-hidden="true">
|
||||
<span className="platform-center-glow" />
|
||||
<BrandMark />
|
||||
</div>
|
||||
{supportedPlatforms.map((platform, index) => (
|
||||
<span
|
||||
key={platform}
|
||||
className={`platform-chip platform-chip-${index + 1}`}
|
||||
title={platform}
|
||||
>
|
||||
<PlatformIcon className="platform-glyph" platform={platform} />
|
||||
<span className="sr-only">{platform}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/* Cross-platform orbit */
|
||||
.platform-orbit {
|
||||
position: relative;
|
||||
width: min(100%, 610px);
|
||||
aspect-ratio: 1;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.platform-ring {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
border: 1px solid #e2d8e7;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.platform-ring::before,
|
||||
.platform-ring::after {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: var(--brand-400);
|
||||
box-shadow: 0 0 0 7px rgba(168, 85, 247, 0.08);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.platform-ring-outer {
|
||||
width: 88%;
|
||||
height: 88%;
|
||||
animation: orbit-spin 35s linear infinite;
|
||||
}
|
||||
|
||||
.platform-ring-outer::before { top: 13%; left: 10%; width: 7px; height: 7px; }
|
||||
.platform-ring-outer::after { right: 7%; bottom: 23%; width: 5px; height: 5px; }
|
||||
|
||||
.platform-ring-inner {
|
||||
width: 58%;
|
||||
height: 58%;
|
||||
border-style: dashed;
|
||||
animation: orbit-spin 25s linear infinite reverse;
|
||||
}
|
||||
|
||||
.platform-ring-inner::before { top: 48%; left: -4px; width: 7px; height: 7px; }
|
||||
.platform-ring-inner::after { top: 4%; right: 23%; width: 5px; height: 5px; }
|
||||
|
||||
.platform-center {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: grid;
|
||||
width: 145px;
|
||||
height: 145px;
|
||||
place-items: center;
|
||||
border: 1px solid #e5d8eb;
|
||||
border-radius: 38px;
|
||||
background: white;
|
||||
box-shadow: 0 30px 80px rgba(55, 29, 67, 0.16);
|
||||
transform: translate(-50%, -50%) rotate(-3deg);
|
||||
}
|
||||
|
||||
.platform-center img { position: relative; z-index: 2; width: 87px; height: 87px; }
|
||||
|
||||
.platform-center-glow {
|
||||
position: absolute;
|
||||
inset: -45px;
|
||||
border-radius: 50%;
|
||||
background: rgba(168, 85, 247, 0.18);
|
||||
filter: blur(42px);
|
||||
animation: platform-glow 5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.platform-chip {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
border: 1px solid #e2d9e6;
|
||||
border-radius: 18px;
|
||||
background: rgba(255, 255, 255, 0.93);
|
||||
box-shadow: 0 12px 35px rgba(53, 34, 61, 0.1);
|
||||
color: #4b4350;
|
||||
animation: platform-chip-float 5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.platform-chip .platform-glyph {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.platform-chip-1 { top: 8%; left: 17%; animation-delay: -1s; }
|
||||
.platform-chip-2 { top: 19%; right: 5%; animation-delay: -2.7s; }
|
||||
.platform-chip-3 { right: 1%; bottom: 22%; animation-delay: -4s; }
|
||||
.platform-chip-4 { bottom: 7%; left: 24%; animation-delay: -0.5s; }
|
||||
.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; }
|
||||
@@ -1,278 +0,0 @@
|
||||
/* Five-step transfer flow */
|
||||
.flow-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(520px, 1.1fr) minmax(390px, 0.9fr);
|
||||
align-items: start;
|
||||
gap: 80px;
|
||||
}
|
||||
|
||||
.flow-visual {
|
||||
position: sticky;
|
||||
top: 115px;
|
||||
height: 660px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 27px;
|
||||
background:
|
||||
linear-gradient(rgba(112, 85, 124, 0.06) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(112, 85, 124, 0.06) 1px, transparent 1px),
|
||||
linear-gradient(145deg, #fdfbfe, #f4eff6);
|
||||
background-size: 28px 28px, 28px 28px, auto;
|
||||
box-shadow: inset 0 1px white, var(--shadow-sm);
|
||||
}
|
||||
|
||||
.flow-visual::after {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at center, transparent 20%, rgba(248, 244, 250, 0.65) 78%);
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.flow-visual-glow {
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
left: 32%;
|
||||
width: 40%;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background: rgba(168, 85, 247, 0.18);
|
||||
filter: blur(55px);
|
||||
animation: flow-glow 7s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.flow-track {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
inset: 4% 5% auto;
|
||||
width: 90%;
|
||||
height: 92%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.flow-track-shadow,
|
||||
.flow-track-base,
|
||||
.flow-track-energy {
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.flow-track-shadow {
|
||||
stroke: rgba(168, 85, 247, 0.22);
|
||||
stroke-width: 34;
|
||||
filter: url(#flow-blur);
|
||||
}
|
||||
|
||||
.flow-track-base {
|
||||
stroke: #e4dae9;
|
||||
stroke-width: 18;
|
||||
}
|
||||
|
||||
.flow-track-energy {
|
||||
stroke: url(#flow-gradient);
|
||||
stroke-width: 6;
|
||||
stroke-dasharray: 3 17;
|
||||
animation: conduit-energy 2.1s linear infinite;
|
||||
}
|
||||
|
||||
.flow-moving-drop,
|
||||
.flow-moving-halo {
|
||||
offset-path: path("M132 141c216 14 240 88 153 159-89 73-43 157 81 160");
|
||||
offset-rotate: 0deg;
|
||||
animation: flow-drop 8s cubic-bezier(0.45, 0.08, 0.35, 0.98) infinite;
|
||||
}
|
||||
|
||||
.flow-moving-halo {
|
||||
animation-delay: -0.08s;
|
||||
}
|
||||
|
||||
.flow-gate {
|
||||
transform-origin: 241px 299px;
|
||||
animation: gate-breathe 3.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.flow-device {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
display: grid;
|
||||
width: 168px;
|
||||
height: 258px;
|
||||
place-items: center;
|
||||
border: 7px solid #d7cfdc;
|
||||
border-radius: 28px;
|
||||
background: #211d24;
|
||||
box-shadow: 0 24px 55px rgba(48, 31, 56, 0.18), inset 0 0 0 1px #ada2b3;
|
||||
}
|
||||
|
||||
.flow-device-sender {
|
||||
top: 46px;
|
||||
left: 31px;
|
||||
transform: rotate(-6deg);
|
||||
}
|
||||
|
||||
.flow-device-receiver {
|
||||
right: 25px;
|
||||
bottom: 32px;
|
||||
transform: rotate(5deg);
|
||||
}
|
||||
|
||||
.flow-device-camera {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 50%;
|
||||
width: 54px;
|
||||
height: 7px;
|
||||
border-radius: 999px;
|
||||
background: #0c0a0e;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.flow-device-content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 25px 15px 15px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.flow-mini-label {
|
||||
margin-bottom: 20px;
|
||||
color: #8c7e93;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 7px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
|
||||
.flow-file-stack {
|
||||
position: relative;
|
||||
width: 74px;
|
||||
height: 82px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.flow-file-stack span {
|
||||
position: absolute;
|
||||
display: grid;
|
||||
width: 50px;
|
||||
height: 66px;
|
||||
place-items: center;
|
||||
border: 1px solid rgba(216, 180, 254, 0.5);
|
||||
border-radius: 9px;
|
||||
background: #392e40;
|
||||
color: #d9a8fb;
|
||||
}
|
||||
|
||||
.flow-file-stack span:nth-child(1) { left: 0; transform: rotate(-10deg); }
|
||||
.flow-file-stack span:nth-child(2) { right: 0; transform: rotate(10deg); }
|
||||
.flow-file-stack span:nth-child(3) { left: 12px; background: #f7effc; color: var(--brand-600); }
|
||||
.flow-file-stack svg { width: 22px; }
|
||||
|
||||
.flow-device-content strong {
|
||||
font-family: var(--font-display);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.flow-device-content small {
|
||||
color: #928798;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.flow-complete-ring {
|
||||
display: grid;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
margin-bottom: 18px;
|
||||
place-items: center;
|
||||
border: 1px solid rgba(216, 180, 254, 0.4);
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(168, 85, 247, 0.26), rgba(168, 85, 247, 0.05));
|
||||
color: #d59aff;
|
||||
animation: complete-ring 3.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.flow-complete-ring svg {
|
||||
width: 30px;
|
||||
stroke-width: 2.4;
|
||||
}
|
||||
|
||||
.flow-annotation {
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
padding: 5px 8px;
|
||||
border: 1px solid #ded4e3;
|
||||
border-radius: 7px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
color: var(--brand-600);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 7px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.flow-annotation-one { top: 35%; right: 12%; transform: rotate(4deg); }
|
||||
.flow-annotation-two { bottom: 11%; left: 25%; transform: rotate(-4deg); }
|
||||
|
||||
.flow-steps {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.flow-step {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 18px;
|
||||
padding: 24px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 18px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
transition: transform 200ms ease, border-color 200ms ease, box-shadow 200ms ease;
|
||||
}
|
||||
|
||||
.flow-step:hover {
|
||||
transform: translateX(6px);
|
||||
border-color: var(--brand-300);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.flow-step-icon {
|
||||
display: grid;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
place-items: center;
|
||||
border-radius: 12px;
|
||||
background: var(--brand-100);
|
||||
color: var(--brand-600);
|
||||
}
|
||||
|
||||
.flow-step-icon svg { width: 21px; }
|
||||
|
||||
.flow-step-number {
|
||||
display: inline-block;
|
||||
margin-bottom: 2px;
|
||||
color: var(--brand-500);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.flow-step h3 {
|
||||
margin-bottom: 7px;
|
||||
font-size: 18px;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.flow-step p {
|
||||
margin-bottom: 0;
|
||||
color: var(--ink-muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.58;
|
||||
}
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import { Icon, type IconName } from "@/components/icons";
|
||||
|
||||
const steps: Array<{
|
||||
number: string;
|
||||
icon: IconName;
|
||||
title: string;
|
||||
text: string;
|
||||
}> = [
|
||||
{
|
||||
number: "01",
|
||||
icon: "folder",
|
||||
title: "Pick anything",
|
||||
text: "Choose one file, a whole batch, or a complete folder. Its structure stays intact.",
|
||||
},
|
||||
{
|
||||
number: "02",
|
||||
icon: "qr",
|
||||
title: "Pass a tiny invitation",
|
||||
text: "Share a QR code, tap an NFC tag, or send a portable .vnd invitation file.",
|
||||
},
|
||||
{
|
||||
number: "03",
|
||||
icon: "shield",
|
||||
title: "Let them knock",
|
||||
text: "Every new receiver asks first by default. You choose approve or refuse.",
|
||||
},
|
||||
{
|
||||
number: "04",
|
||||
icon: "verified",
|
||||
title: "Stream and verify",
|
||||
text: "Files move over an authenticated encrypted connection and are verified as they arrive.",
|
||||
},
|
||||
{
|
||||
number: "05",
|
||||
icon: "stop",
|
||||
title: "Close the door",
|
||||
text: "Follow each receiver, cancel in progress, or stop sharing to revoke the invitation.",
|
||||
},
|
||||
];
|
||||
|
||||
export function TransferFlow() {
|
||||
return (
|
||||
<div className="flow-layout">
|
||||
<div className="flow-visual" aria-hidden="true">
|
||||
<div className="flow-visual-glow" />
|
||||
<div className="flow-device flow-device-sender">
|
||||
<span className="flow-device-camera" />
|
||||
<div className="flow-device-content">
|
||||
<span className="flow-mini-label">SELECTED</span>
|
||||
<div className="flow-file-stack">
|
||||
<span><Icon name="file" /></span>
|
||||
<span><Icon name="file" /></span>
|
||||
<span><Icon name="file" /></span>
|
||||
</div>
|
||||
<strong>Project files</strong>
|
||||
<small>18 items · 248 MB</small>
|
||||
</div>
|
||||
</div>
|
||||
<svg className="flow-track" viewBox="0 0 480 600">
|
||||
<defs>
|
||||
<linearGradient id="flow-gradient" x1="90" y1="80" x2="401" y2="520" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="#C778FF" />
|
||||
<stop offset="0.5" stopColor="#A855F7" />
|
||||
<stop offset="1" stopColor="#7628EA" />
|
||||
</linearGradient>
|
||||
<filter id="flow-blur" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="18" />
|
||||
</filter>
|
||||
</defs>
|
||||
<path className="flow-track-shadow" d="M132 141c216 14 240 88 153 159-89 73-43 157 81 160" />
|
||||
<path className="flow-track-base" d="M132 141c216 14 240 88 153 159-89 73-43 157 81 160" />
|
||||
<path className="flow-track-energy" d="M132 141c216 14 240 88 153 159-89 73-43 157 81 160" />
|
||||
<circle className="flow-moving-drop" cx="0" cy="0" r="11" fill="url(#flow-gradient)" />
|
||||
<circle className="flow-moving-halo" cx="0" cy="0" r="24" fill="#A855F7" opacity="0.18" filter="url(#flow-blur)" />
|
||||
<g className="flow-gate">
|
||||
<rect x="197" y="274" width="88" height="50" rx="17" fill="#FFFCFF" stroke="#D8B4FE" />
|
||||
<path d="m241 286 12 5v8.5c0 7.2-5.1 12.1-12 14.2-6.9-2.1-12-7-12-14.2V291Z" fill="url(#flow-gradient)" />
|
||||
<path d="m235.5 299 3.5 3.5 7.5-8" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" />
|
||||
</g>
|
||||
</svg>
|
||||
<div className="flow-device flow-device-receiver">
|
||||
<span className="flow-device-camera" />
|
||||
<div className="flow-device-content flow-received-content">
|
||||
<span className="flow-complete-ring"><Icon name="check" /></span>
|
||||
<strong>All received</strong>
|
||||
<small>18 items verified</small>
|
||||
</div>
|
||||
</div>
|
||||
<span className="flow-annotation flow-annotation-one">E2E</span>
|
||||
<span className="flow-annotation flow-annotation-two">VERIFIED</span>
|
||||
</div>
|
||||
<ol className="flow-steps">
|
||||
{steps.map((step) => (
|
||||
<li key={step.number} className="flow-step">
|
||||
<div className="flow-step-icon">
|
||||
<Icon name={step.icon} />
|
||||
</div>
|
||||
<div>
|
||||
<span className="flow-step-number">{step.number}</span>
|
||||
<h3>{step.title}</h3>
|
||||
<p>{step.text}</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user