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:
@@ -20,3 +20,7 @@ npm run build
|
|||||||
```
|
```
|
||||||
|
|
||||||
The production build is written to `out/` and can be hosted by any static web server.
|
The production build is written to `out/` and can be hosted by any static web server.
|
||||||
|
|
||||||
|
Set `NEXT_PUBLIC_SITE_URL` to the canonical production origin when building for deployment so
|
||||||
|
Open Graph and Twitter image URLs resolve to the public site. Vercel deployment URLs are detected
|
||||||
|
automatically.
|
||||||
|
|||||||
@@ -4,27 +4,19 @@
|
|||||||
--brand-500: #a855f7;
|
--brand-500: #a855f7;
|
||||||
--brand-400: #c084fc;
|
--brand-400: #c084fc;
|
||||||
--brand-300: #d8b4fe;
|
--brand-300: #d8b4fe;
|
||||||
--brand-200: #ead4ff;
|
|
||||||
--brand-100: #f5eaff;
|
|
||||||
--ink: #171419;
|
--ink: #171419;
|
||||||
--ink-soft: #4f4854;
|
--ink-soft: #4f4854;
|
||||||
--ink-muted: #786f7d;
|
--ink-muted: #786f7d;
|
||||||
--paper: #fbfafc;
|
--paper: #fbfafc;
|
||||||
--paper-warm: #f7f4f8;
|
--paper-warm: #f7f4f8;
|
||||||
--surface: #ffffff;
|
--surface: #ffffff;
|
||||||
--surface-soft: #f3eff5;
|
|
||||||
--line: #e5dfe8;
|
--line: #e5dfe8;
|
||||||
--line-strong: #d4cad8;
|
--line-strong: #d4cad8;
|
||||||
--dark: #17131a;
|
|
||||||
--dark-raised: #211a25;
|
|
||||||
--dark-line: #362c3c;
|
--dark-line: #362c3c;
|
||||||
--success: #24a66b;
|
|
||||||
--warning: #f59e0b;
|
--warning: #f59e0b;
|
||||||
--font-sans: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
--font-sans: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
--font-display: "Avenir Next", Avenir, "Segoe UI", ui-sans-serif, sans-serif;
|
--font-display: "Avenir Next", Avenir, "Segoe UI", ui-sans-serif, sans-serif;
|
||||||
--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||||
--shadow-sm: 0 8px 30px rgba(41, 25, 48, 0.07);
|
|
||||||
--shadow-lg: 0 30px 90px rgba(49, 25, 60, 0.14);
|
|
||||||
--shell: 1220px;
|
--shell: 1220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,13 +155,6 @@ p {
|
|||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-small {
|
|
||||||
min-height: 44px;
|
|
||||||
padding-inline: 16px;
|
|
||||||
border-radius: 11px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-primary {
|
.button-primary {
|
||||||
background: linear-gradient(135deg, var(--brand-500), var(--brand-700));
|
background: linear-gradient(135deg, var(--brand-500), var(--brand-700));
|
||||||
box-shadow: 0 12px 28px rgba(132, 47, 238, 0.24), inset 0 1px rgba(255, 255, 255, 0.35);
|
box-shadow: 0 12px 28px rgba(132, 47, 238, 0.24), inset 0 1px rgba(255, 255, 255, 0.35);
|
||||||
@@ -180,35 +165,6 @@ p {
|
|||||||
box-shadow: 0 16px 34px rgba(132, 47, 238, 0.3), inset 0 1px rgba(255, 255, 255, 0.35);
|
box-shadow: 0 16px 34px rgba(132, 47, 238, 0.3), inset 0 1px rgba(255, 255, 255, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-ghost {
|
|
||||||
border-color: var(--line);
|
|
||||||
background: rgba(255, 255, 255, 0.72);
|
|
||||||
color: var(--ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-ghost:hover {
|
|
||||||
border-color: var(--line-strong);
|
|
||||||
background: white;
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-dark {
|
|
||||||
background: var(--ink);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-light {
|
|
||||||
background: white;
|
|
||||||
color: #211925;
|
|
||||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-dark-ghost {
|
|
||||||
border-color: rgba(255, 255, 255, 0.2);
|
|
||||||
background: rgba(255, 255, 255, 0.06);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.motion-ready .reveal {
|
.motion-ready .reveal {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(28px);
|
transform: translateY(28px);
|
||||||
|
|||||||
@@ -10,7 +10,18 @@ import "./privacy/privacy-document.css";
|
|||||||
import "./responsive.css";
|
import "./responsive.css";
|
||||||
import "./privacy/privacy-responsive.css";
|
import "./privacy/privacy-responsive.css";
|
||||||
|
|
||||||
|
const configuredSiteUrl =
|
||||||
|
process.env.NEXT_PUBLIC_SITE_URL ??
|
||||||
|
process.env.VERCEL_PROJECT_PRODUCTION_URL ??
|
||||||
|
process.env.VERCEL_URL ??
|
||||||
|
"http://localhost:3000";
|
||||||
|
|
||||||
|
const metadataBase = new URL(
|
||||||
|
configuredSiteUrl.startsWith("http") ? configuredSiteUrl : `https://${configuredSiteUrl}`,
|
||||||
|
);
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
metadataBase,
|
||||||
title: {
|
title: {
|
||||||
default: "VniDrop — Direct file transfer, on your terms",
|
default: "VniDrop — Direct file transfer, on your terms",
|
||||||
template: "%s · VniDrop",
|
template: "%s · VniDrop",
|
||||||
@@ -31,12 +42,26 @@ export const metadata: Metadata = {
|
|||||||
title: "VniDrop — Direct file transfer, on your terms",
|
title: "VniDrop — Direct file transfer, on your terms",
|
||||||
description:
|
description:
|
||||||
"Move files from your device to theirs, with no account and no hosted transfer copy.",
|
"Move files from your device to theirs, with no account and no hosted transfer copy.",
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: "/og.png",
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
alt: "VniDrop — Your files, a straight line between devices.",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: "summary",
|
card: "summary_large_image",
|
||||||
title: "VniDrop — Direct file transfer, on your terms",
|
title: "VniDrop — Direct file transfer, on your terms",
|
||||||
description:
|
description:
|
||||||
"Move files from your device to theirs, with no account and no hosted transfer copy.",
|
"Move files from your device to theirs, with no account and no hosted transfer copy.",
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: "/og.png",
|
||||||
|
alt: "VniDrop — Your files, a straight line between devices.",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
424
docs/app/og.png/route.tsx
Normal file
424
docs/app/og.png/route.tsx
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
import { ImageResponse } from "next/og";
|
||||||
|
|
||||||
|
export const dynamic = "force-static";
|
||||||
|
|
||||||
|
const imageSize = {
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
};
|
||||||
|
|
||||||
|
function BrandMark() {
|
||||||
|
return (
|
||||||
|
<svg width="54" height="54" viewBox="0 0 1024 1024" fill="none">
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M236.68 148H338.36C366.24 148 387.56 170.96 387.56 198.84V564.56C387.56 597.36 372.8 620.32 372.8 646.56C372.8 725.28 436.76 787.6 522.04 787.6C607.32 787.6 668 725.28 668 646.56C668 620.32 656.52 597.36 656.52 564.56V198.84C656.52 170.96 677.84 148 705.72 148H781.16C817.24 148 846.76 177.52 846.76 213.6V564.56C846.76 738.4 704.08 879.44 522.04 879.44C340 879.44 194.04 738.4 194.04 564.56V374.32H220.28V305.44C195.68 305.44 176 297.24 176 280.84V246.4C176 231.64 187.48 220.16 202.24 220.16H236.68V148ZM256.36 239.84C251.44 239.84 248.16 244.76 248.16 249.68V275.92C248.16 282.48 253.08 285.76 259.64 285.76H282.6C289.16 285.76 292.44 280.84 292.44 274.28V251.32C292.44 244.76 287.52 239.84 280.96 239.84H256.36Z"
|
||||||
|
fill="url(#brand-gradient)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M520.4 431.72C495.8 464.52 443.32 530.12 420.36 577.68C390.84 636.72 403.96 699.04 446.6 731.84C487.6 758.08 549.92 758.08 592.56 730.2C633.56 700.68 646.68 636.72 620.44 577.68C597.48 530.12 546.64 464.52 520.4 431.72Z"
|
||||||
|
fill="url(#drop-gradient)"
|
||||||
|
/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="brand-gradient" x1="176" y1="148" x2="905" y2="816">
|
||||||
|
<stop stopColor="#C084FC" />
|
||||||
|
<stop offset="0.52" stopColor="#A855F7" />
|
||||||
|
<stop offset="1" stopColor="#7C2AEF" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="drop-gradient" x1="404" y1="432" x2="707" y2="649">
|
||||||
|
<stop stopColor="#E9D5FF" />
|
||||||
|
<stop offset="1" stopColor="#A855F7" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileGlyph() {
|
||||||
|
return (
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path
|
||||||
|
d="M6 3h8l4 4v14H6z"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.7"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path d="M14 3v5h4M9 13h6M9 17h4" stroke="currentColor" strokeWidth="1.7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CheckGlyph() {
|
||||||
|
return (
|
||||||
|
<svg width="27" height="27" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path
|
||||||
|
d="m6.5 12.5 3.4 3.4 7.8-8"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2.2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LockGlyph() {
|
||||||
|
return (
|
||||||
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none">
|
||||||
|
<rect x="5" y="10" width="14" height="11" rx="3" stroke="currentColor" strokeWidth="1.8" />
|
||||||
|
<path d="M8 10V7a4 4 0 0 1 8 0v3M12 14v3" stroke="currentColor" strokeWidth="1.8" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TransferIllustration() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 86,
|
||||||
|
right: 72,
|
||||||
|
width: 470,
|
||||||
|
height: 474,
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 24,
|
||||||
|
left: 48,
|
||||||
|
width: 400,
|
||||||
|
height: 400,
|
||||||
|
borderRadius: 999,
|
||||||
|
display: "flex",
|
||||||
|
background:
|
||||||
|
"radial-gradient(circle, rgba(168, 85, 247, 0.24) 0%, rgba(124, 42, 239, 0.09) 44%, rgba(13, 10, 16, 0) 72%)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
right: 2,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
color: "#A99EAD",
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: 2.1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
DIRECT · ENCRYPTED
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="470"
|
||||||
|
height="474"
|
||||||
|
viewBox="0 0 470 474"
|
||||||
|
fill="none"
|
||||||
|
style={{ position: "absolute", inset: 0 }}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M264 229C305 233 310 300 353 313"
|
||||||
|
stroke="url(#route-gradient)"
|
||||||
|
strokeWidth="3"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeDasharray="8 9"
|
||||||
|
/>
|
||||||
|
<circle cx="264" cy="229" r="6" fill="#D8B4FE" />
|
||||||
|
<circle cx="353" cy="313" r="6" fill="#A855F7" />
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="route-gradient" x1="264" y1="229" x2="353" y2="313">
|
||||||
|
<stop stopColor="#D8B4FE" />
|
||||||
|
<stop offset="1" stopColor="#7C2AEF" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 79,
|
||||||
|
left: 1,
|
||||||
|
width: 286,
|
||||||
|
height: 184,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
border: "2px solid #44384A",
|
||||||
|
borderRadius: 18,
|
||||||
|
background: "#18131D",
|
||||||
|
boxShadow: "0 24px 70px rgba(0, 0, 0, 0.38)",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: 34,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "0 14px",
|
||||||
|
borderBottom: "1px solid #382E3D",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: 7, height: 7, borderRadius: 99, background: "#685B6E", display: "flex" }} />
|
||||||
|
<div style={{ width: 7, height: 7, borderRadius: 99, background: "#685B6E", display: "flex", marginLeft: 7 }} />
|
||||||
|
<div style={{ width: 7, height: 7, borderRadius: 99, background: "#A855F7", display: "flex", marginLeft: 7 }} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
padding: "20px 21px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 42,
|
||||||
|
height: 42,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
border: "1px solid #4C3A57",
|
||||||
|
borderRadius: 11,
|
||||||
|
background: "#241A2B",
|
||||||
|
color: "#C084FC",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FileGlyph />
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", marginLeft: 13 }}>
|
||||||
|
<div style={{ color: "#F8F4FA", fontSize: 16, fontWeight: 650 }}>Project files</div>
|
||||||
|
<div style={{ color: "#8E8292", fontSize: 12, marginTop: 3 }}>12 items · 284 MB</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: 5,
|
||||||
|
display: "flex",
|
||||||
|
marginTop: 21,
|
||||||
|
borderRadius: 99,
|
||||||
|
background: "#342A39",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "72%",
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
borderRadius: 99,
|
||||||
|
background: "linear-gradient(90deg, #C084FC, #7C2AEF)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-between", marginTop: 10 }}>
|
||||||
|
<div style={{ color: "#A99EAD", fontSize: 11 }}>Sending directly</div>
|
||||||
|
<div style={{ color: "#D8B4FE", fontSize: 11, fontWeight: 700 }}>72%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 263,
|
||||||
|
left: 112,
|
||||||
|
width: 62,
|
||||||
|
height: 8,
|
||||||
|
display: "flex",
|
||||||
|
borderRadius: 99,
|
||||||
|
background: "#44384A",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 270,
|
||||||
|
left: 87,
|
||||||
|
width: 112,
|
||||||
|
height: 8,
|
||||||
|
display: "flex",
|
||||||
|
borderRadius: 99,
|
||||||
|
background: "#29212E",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 226,
|
||||||
|
left: 286,
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
border: "1px solid #6A477B",
|
||||||
|
borderRadius: 15,
|
||||||
|
background: "#211627",
|
||||||
|
color: "#D8B4FE",
|
||||||
|
boxShadow: "0 12px 32px rgba(124, 42, 239, 0.3)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LockGlyph />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 238,
|
||||||
|
right: 6,
|
||||||
|
width: 127,
|
||||||
|
height: 230,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
border: "2px solid #51415A",
|
||||||
|
borderRadius: 30,
|
||||||
|
background: "#18131D",
|
||||||
|
boxShadow: "0 24px 70px rgba(0, 0, 0, 0.42)",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 48,
|
||||||
|
height: 5,
|
||||||
|
display: "flex",
|
||||||
|
marginTop: 10,
|
||||||
|
borderRadius: 99,
|
||||||
|
background: "#45374C",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 58,
|
||||||
|
height: 58,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
marginTop: 38,
|
||||||
|
border: "1px solid #6F4A82",
|
||||||
|
borderRadius: 99,
|
||||||
|
background: "#27182E",
|
||||||
|
color: "#D8B4FE",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckGlyph />
|
||||||
|
</div>
|
||||||
|
<div style={{ color: "#F8F4FA", fontSize: 15, fontWeight: 700, marginTop: 15 }}>Received</div>
|
||||||
|
<div style={{ color: "#8E8292", fontSize: 10, marginTop: 5 }}>Verified on arrival</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 46,
|
||||||
|
height: 4,
|
||||||
|
display: "flex",
|
||||||
|
marginTop: 32,
|
||||||
|
borderRadius: 99,
|
||||||
|
background: "#45374C",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GET() {
|
||||||
|
return new ImageResponse(
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
overflow: "hidden",
|
||||||
|
background: "#0D0A10",
|
||||||
|
color: "#FAF7FC",
|
||||||
|
fontFamily: "sans-serif",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: 0,
|
||||||
|
display: "flex",
|
||||||
|
background:
|
||||||
|
"radial-gradient(circle at 84% 48%, rgba(168, 85, 247, 0.14), transparent 34%), radial-gradient(circle at 16% 100%, rgba(124, 42, 239, 0.09), transparent 30%)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style={{ position: "absolute", top: 64, bottom: 64, left: 64, display: "flex", borderLeft: "1px dashed #3A3040" }} />
|
||||||
|
<div style={{ position: "absolute", top: 64, bottom: 64, right: 64, display: "flex", borderRight: "1px dashed #3A3040" }} />
|
||||||
|
<div style={{ position: "absolute", top: 64, right: 0, left: 0, display: "flex", borderTop: "1px solid #3A3040" }} />
|
||||||
|
<div style={{ position: "absolute", right: 0, bottom: 64, left: 0, display: "flex", borderBottom: "1px solid #3A3040" }} />
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 89,
|
||||||
|
left: 96,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BrandMark />
|
||||||
|
<div style={{ display: "flex", alignItems: "baseline", marginLeft: 12 }}>
|
||||||
|
<div style={{ fontSize: 28, fontWeight: 750, letterSpacing: -1.1 }}>VniDrop</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginLeft: 18,
|
||||||
|
color: "#8E8292",
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: 1.8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
OPEN SOURCE · LOCAL P2P
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 185,
|
||||||
|
left: 96,
|
||||||
|
width: 625,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
fontSize: 66,
|
||||||
|
fontWeight: 750,
|
||||||
|
lineHeight: 0.99,
|
||||||
|
letterSpacing: -3.4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex" }}>Your files.</div>
|
||||||
|
<div style={{ display: "flex", color: "#C084FC", marginTop: 3 }}>A straight line</div>
|
||||||
|
<div style={{ display: "flex", marginTop: 3 }}>between devices.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
bottom: 101,
|
||||||
|
left: 96,
|
||||||
|
display: "flex",
|
||||||
|
color: "#AAA0AE",
|
||||||
|
fontSize: 25,
|
||||||
|
fontWeight: 450,
|
||||||
|
letterSpacing: -0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Send files directly. Stay in control.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TransferIllustration />
|
||||||
|
</div>,
|
||||||
|
imageSize,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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) {
|
export function Brand() {
|
||||||
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 }) {
|
|
||||||
return (
|
return (
|
||||||
<Link className="brand" href="/" aria-label="VniDrop home">
|
<Link className="brand" href="/" aria-label="VniDrop home">
|
||||||
<span className="brand-primary">
|
<span className="brand-primary">
|
||||||
<BrandMark className="brand-mark-image" />
|
<BrandMark className="brand-mark-image" />
|
||||||
{!compact && <span className="brand-name">VniDrop</span>}
|
<span className="brand-name">VniDrop</span>
|
||||||
</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>
|
</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 =
|
export type IconName =
|
||||||
| "arrow"
|
| "arrow"
|
||||||
| "check"
|
|
||||||
| "chevron"
|
|
||||||
| "close"
|
|
||||||
| "code"
|
|
||||||
| "devices"
|
| "devices"
|
||||||
| "file"
|
|
||||||
| "folder"
|
| "folder"
|
||||||
| "github"
|
| "github"
|
||||||
| "globe"
|
|
||||||
| "lock"
|
| "lock"
|
||||||
| "menu"
|
|
||||||
| "nfc"
|
|
||||||
| "qr"
|
| "qr"
|
||||||
| "shield"
|
| "shield"
|
||||||
| "spark"
|
| "verified";
|
||||||
| "stop"
|
|
||||||
| "verified"
|
|
||||||
| "x";
|
|
||||||
|
|
||||||
type IconProps = SVGProps<SVGSVGElement> & {
|
type IconProps = SVGProps<SVGSVGElement> & {
|
||||||
name: IconName;
|
name: IconName;
|
||||||
@@ -33,21 +22,6 @@ export function Icon({ name, ...props }: IconProps) {
|
|||||||
<path d="m14 7 5 5-5 5" />
|
<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: (
|
devices: (
|
||||||
<>
|
<>
|
||||||
<rect x="3" y="5" width="13" height="10" rx="2" />
|
<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" />
|
<rect x="17" y="8" width="4" height="10" rx="1" />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
file: (
|
|
||||||
<>
|
|
||||||
<path d="M6 3h8l4 4v14H6z" />
|
|
||||||
<path d="M14 3v5h4M9 13h6M9 17h4" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
folder: (
|
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" />
|
<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"
|
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: (
|
lock: (
|
||||||
<>
|
<>
|
||||||
<rect x="5" y="10" width="14" height="11" rx="3" />
|
<rect x="5" y="10" width="14" height="11" rx="3" />
|
||||||
<path d="M8 10V7a4 4 0 0 1 8 0v3M12 14v3" />
|
<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: (
|
qr: (
|
||||||
<>
|
<>
|
||||||
<rect x="3" y="3" width="7" height="7" rx="1" />
|
<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" />
|
<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: (
|
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="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" />
|
<path d="m9 10 2 2 4-4" />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
x: (
|
|
||||||
<>
|
|
||||||
<path d="M6 6l12 12" />
|
|
||||||
<path d="M18 6 6 18" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
69
docs/package-lock.json
generated
69
docs/package-lock.json
generated
@@ -8,7 +8,6 @@
|
|||||||
"name": "vnidrop-site",
|
"name": "vnidrop-site",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"motion": "^12.42.2",
|
|
||||||
"next": "16.2.10",
|
"next": "16.2.10",
|
||||||
"react": "19.2.7",
|
"react": "19.2.7",
|
||||||
"react-dom": "19.2.7"
|
"react-dom": "19.2.7"
|
||||||
@@ -3434,33 +3433,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/framer-motion": {
|
|
||||||
"version": "12.42.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz",
|
|
||||||
"integrity": "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"motion-dom": "^12.42.2",
|
|
||||||
"motion-utils": "^12.39.0",
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@emotion/is-prop-valid": "*",
|
|
||||||
"react": "^18.0.0 || ^19.0.0",
|
|
||||||
"react-dom": "^18.0.0 || ^19.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@emotion/is-prop-valid": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -4517,47 +4489,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/motion": {
|
|
||||||
"version": "12.42.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/motion/-/motion-12.42.2.tgz",
|
|
||||||
"integrity": "sha512-Atvv11yUKIid41cVrRBDVX5m8tF8kNpExRSlbpt6APClhDjtwQssgFHhQzejxw7/7YYbjHSPKBVbHo05BuJT5Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"framer-motion": "^12.42.2",
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@emotion/is-prop-valid": "*",
|
|
||||||
"react": "^18.0.0 || ^19.0.0",
|
|
||||||
"react-dom": "^18.0.0 || ^19.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@emotion/is-prop-valid": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/motion-dom": {
|
|
||||||
"version": "12.42.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.42.2.tgz",
|
|
||||||
"integrity": "sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"motion-utils": "^12.39.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/motion-utils": {
|
|
||||||
"version": "12.39.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
|
|
||||||
"integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"motion": "^12.42.2",
|
|
||||||
"next": "16.2.10",
|
"next": "16.2.10",
|
||||||
"react": "19.2.7",
|
"react": "19.2.7",
|
||||||
"react-dom": "19.2.7"
|
"react-dom": "19.2.7"
|
||||||
|
|||||||
Reference in New Issue
Block a user