feat(docs): refine product showcase and branding

This commit is contained in:
2026-07-16 18:11:06 +02:00
parent d61d6f3c5b
commit 0a185077af
20 changed files with 1658 additions and 663 deletions

View File

@@ -137,107 +137,6 @@ p {
transform: translateY(0);
}
.site-header {
position: fixed;
z-index: 100;
top: 16px;
right: 0;
left: 0;
width: min(calc(100% - 32px), 1260px);
margin-inline: auto;
border: 1px solid rgba(220, 212, 224, 0.82);
border-radius: 18px;
background: rgba(251, 250, 252, 0.82);
box-shadow: 0 12px 40px rgba(42, 26, 49, 0.08);
backdrop-filter: blur(20px) saturate(1.4);
-webkit-backdrop-filter: blur(20px) saturate(1.4);
}
.header-inner {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
height: 64px;
padding: 0 10px 0 17px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 10px;
width: max-content;
font-family: var(--font-display);
font-size: 19px;
font-weight: 750;
letter-spacing: -0.04em;
}
.brand-mark-shell {
display: grid;
width: 38px;
height: 38px;
place-items: center;
border: 1px solid #eadcf1;
border-radius: 11px;
background: white;
box-shadow: 0 5px 15px rgba(105, 42, 145, 0.08);
}
.brand-mark-shell svg {
width: 27px;
height: 27px;
}
.brand-name {
transform: translateY(-1px);
}
.desktop-nav {
display: flex;
align-items: center;
gap: 3px;
padding: 4px;
border-radius: 13px;
background: rgba(239, 235, 241, 0.7);
}
.desktop-nav a {
padding: 7px 14px;
border-radius: 10px;
color: var(--ink-soft);
font-size: 13px;
font-weight: 650;
transition: background 160ms ease, color 160ms ease;
}
.desktop-nav a:hover {
background: white;
color: var(--ink);
}
.header-github {
justify-self: end;
}
.menu-button {
display: none;
width: 42px;
height: 42px;
place-items: center;
border: 0;
border-radius: 11px;
background: var(--surface-soft);
cursor: pointer;
}
.menu-button svg {
width: 21px;
}
.mobile-nav {
display: none;
}
.button {
display: inline-flex;
min-height: 52px;
@@ -322,4 +221,3 @@ p {
opacity: 1;
transform: translateY(0);
}

View File

@@ -27,7 +27,7 @@
background: white;
}
.footer-mark svg {
.footer-mark img {
width: 32px;
height: 32px;
}
@@ -100,4 +100,3 @@
.footer-bottom p {
margin: 0;
}

View File

@@ -1,10 +1,29 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="64" rx="16" fill="#FBF7FF"/>
<path d="M16 12h8.2c2.1 0 3.8 1.7 3.8 3.8v20.4c0 7 4.8 12.2 11.5 12.2S51 43.2 51 36.2V15.8c0-2.1 1.7-3.8 3.8-3.8H58v24.4C58 47.7 50 56 39.5 56S21 47.7 21 36.4V24h-3v-5h-2v-7Z" fill="url(#a)"/>
<path d="M39.5 26.5c-2.5 3.3-6.5 8.6-6.5 12.4 0 4 2.8 7.1 6.5 7.1s6.5-3.1 6.5-7.1c0-3.8-4-9.1-6.5-12.4Z" fill="url(#b)"/>
<path d="M51 12h7v7l-7-7Z" fill="#E7CBFF"/>
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="40" y="40" width="944" height="944" rx="210" fill="#FFFFFF" stroke="#E9E7F0" stroke-width="8"/>
<path fill-rule="evenodd" clip-rule="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(#paint0_linear_11_12)"/>
<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(#paint1_linear_11_12)"/>
<mask id="mask0_11_12" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="176" y="148" width="671" height="732">
<path 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="white"/>
</mask>
<g mask="url(#mask0_11_12)">
<path d="M688 148H782C832 148 842 171.8 847.48 210V303.68L688 148Z" fill="url(#paint2_linear_11_12)"/>
</g>
<defs>
<linearGradient id="a" x1="16" y1="12" x2="59" y2="51" gradientUnits="userSpaceOnUse"><stop stop-color="#B45CFA"/><stop offset="1" stop-color="#7828ED"/></linearGradient>
<linearGradient id="b" x1="33" y1="27" x2="47" y2="43" gradientUnits="userSpaceOnUse"><stop stop-color="#B45CFA"/><stop offset="1" stop-color="#7828ED"/></linearGradient>
<linearGradient id="paint0_linear_11_12" x1="176" y1="148" x2="904.706" y2="816.252" gradientUnits="userSpaceOnUse">
<stop stop-color="#A855F7"/>
<stop offset="0.48" stop-color="#9D4DF4"/>
<stop offset="1" stop-color="#7C2AEF"/>
</linearGradient>
<linearGradient id="paint1_linear_11_12" x1="404.439" y1="431.72" x2="707.314" y2="649.286" gradientUnits="userSpaceOnUse">
<stop stop-color="#A855F7"/>
<stop offset="0.48" stop-color="#9D4DF4"/>
<stop offset="1" stop-color="#7C2AEF"/>
</linearGradient>
<linearGradient id="paint2_linear_11_12" x1="683.48" y1="144.6" x2="793.636" y2="306.833" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2DDFF"/>
<stop offset="1" stop-color="#C084FC"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 893 B

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -3,10 +3,12 @@ import type { ReactNode } from "react";
import { SiteFooter } from "@/components/site-footer";
import { SiteHeader } from "@/components/site-header";
import "./base.css";
import "../components/header.css";
import "./home.css";
import "./footer.css";
import "./privacy/privacy-document.css";
import "../components/hero-scene.css";
import "../components/hero-device.css";
import "../components/transfer-flow.css";
import "../components/invitation-scene.css";
import "../components/approval-scene.css";

View File

@@ -140,7 +140,7 @@ export default function HomePage() {
</div>
</section>
<section className="section invitation-section">
<section id="invitations" className="section invitation-section">
<div className="page-shell">
<Reveal className="section-heading section-heading-split">
<div>

View File

@@ -175,7 +175,7 @@
backdrop-filter: blur(14px);
}
.privacy-mark-card > svg {
.privacy-mark-card > img {
position: relative;
z-index: 2;
width: 112px;
@@ -657,4 +657,3 @@
color: white;
text-decoration: none;
}

View File

@@ -37,51 +37,6 @@
}
@media (max-width: 920px) {
.desktop-nav,
.header-github {
display: none;
}
.header-inner {
grid-template-columns: 1fr auto;
}
.menu-button {
display: grid;
}
.mobile-nav {
display: grid;
max-height: 0;
overflow: hidden;
padding: 0 12px;
opacity: 0;
visibility: hidden;
transition: max-height 250ms ease, padding 250ms ease, opacity 180ms ease, visibility 250ms;
}
.mobile-nav.is-open {
max-height: 280px;
padding: 4px 12px 12px;
opacity: 1;
visibility: visible;
}
.mobile-nav a {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 10px;
border-top: 1px solid var(--line);
color: var(--ink-soft);
font-size: 13px;
font-weight: 650;
}
.mobile-nav svg {
width: 17px;
}
.hero-section {
padding-top: 128px;
}
@@ -155,26 +110,6 @@
width: min(calc(100% - 32px), var(--shell));
}
.site-header {
top: 10px;
width: calc(100% - 20px);
border-radius: 15px;
}
.header-inner {
height: 58px;
padding-left: 11px;
}
.brand-name {
font-size: 17px;
}
.brand-mark-shell {
width: 36px;
height: 36px;
}
.hero-section {
min-height: auto;
padding: 115px 0 72px;

View File

@@ -1,64 +1,43 @@
import Link from "next/link";
import Image from "next/image";
type BrandMarkProps = {
type BrandAssetProps = {
className?: string;
title?: string;
};
export function BrandMark({ className, title }: BrandMarkProps) {
export function BrandMark({ className, title }: BrandAssetProps) {
return (
<svg
<Image
className={className}
viewBox="0 0 64 64"
fill="none"
role={title ? "img" : undefined}
src="/brand-mark.svg"
width={1024}
height={1024}
alt={title ?? ""}
aria-hidden={title ? undefined : true}
aria-label={title}
>
<path
d="M11 8h10.6a4.8 4.8 0 0 1 4.8 4.8v25.4c0 8.7 6.1 15.2 14.5 15.2 8.3 0 14.1-6.5 14.1-15.2V12.8A4.8 4.8 0 0 1 59.8 8H62v30.4C62 52.5 53.3 61 40.9 61 28.3 61 19 52.5 19 38.4V23h-3.5v-6.4H11V8Z"
fill="url(#brand-mark-gradient)"
unoptimized
/>
<path
d="M40.8 25.9c-3.1 4.1-8.2 10.8-8.2 15.5 0 5.1 3.6 8.9 8.2 8.9 4.8 0 8.3-3.8 8.3-8.9 0-4.7-5.2-11.4-8.3-15.5Z"
fill="url(#brand-drop-gradient)"
);
}
export function AppIcon({ className, title }: BrandAssetProps) {
return (
<Image
className={className}
src="/icon.svg"
width={1024}
height={1024}
alt={title ?? ""}
aria-hidden={title ? undefined : true}
unoptimized
/>
<path d="M54.9 8h7v7l-7-7Z" fill="#E8CDFF" />
<defs>
<linearGradient
id="brand-mark-gradient"
x1="11"
y1="8"
x2="64"
y2="55"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#B45CFA" />
<stop offset="0.5" stopColor="#9D4DF4" />
<stop offset="1" stopColor="#7828ED" />
</linearGradient>
<linearGradient
id="brand-drop-gradient"
x1="32"
y1="26"
x2="51"
y2="47"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#C06CFF" />
<stop offset="1" stopColor="#7828ED" />
</linearGradient>
</defs>
</svg>
);
}
export function Brand({ compact = false }: { compact?: boolean }) {
return (
<Link className="brand" href="/" aria-label="VniDrop home">
<span className="brand-mark-shell">
<BrandMark />
</span>
<BrandMark className="brand-mark-image" />
{!compact && <span className="brand-name">VniDrop</span>}
</Link>
);

115
docs/components/header.css Normal file
View File

@@ -0,0 +1,115 @@
.site-header {
position: fixed;
z-index: 220;
top: 0;
right: 0;
left: 0;
height: 72px;
border-bottom: 1px solid rgba(224, 217, 228, 0.76);
background: rgba(251, 250, 252, 0.88);
box-shadow: 0 8px 30px rgba(42, 26, 49, 0.035);
backdrop-filter: blur(20px) saturate(1.3);
-webkit-backdrop-filter: blur(20px) saturate(1.3);
}
.header-inner {
display: flex;
height: 72px;
align-items: center;
justify-content: space-between;
}
.brand {
display: inline-flex;
align-items: center;
gap: 3px;
width: max-content;
}
.brand-mark-image {
width: 48px;
height: 48px;
object-fit: contain;
filter: drop-shadow(0 7px 14px rgba(126, 42, 189, 0.14));
}
.brand-name {
color: var(--ink);
font-family: var(--font-display);
font-size: 19px;
font-weight: 780;
line-height: 1;
letter-spacing: -0.045em;
}
.header-github-button {
display: inline-flex;
min-height: 40px;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 14px;
border: 1px solid #28232b;
border-radius: 10px;
background: var(--ink);
box-shadow: 0 8px 22px rgba(23, 20, 25, 0.13), inset 0 1px rgba(255, 255, 255, 0.1);
color: white;
font-size: 12px;
font-weight: 680;
line-height: 1;
transition: background 160ms ease, border-color 160ms ease, box-shadow 160ms ease,
transform 160ms ease;
}
.header-github-button:hover {
border-color: #403944;
background: #29242d;
box-shadow: 0 10px 26px rgba(23, 20, 25, 0.17), inset 0 1px rgba(255, 255, 255, 0.12);
transform: translateY(-1px);
}
.header-github-button svg {
width: 17px;
height: 16px;
flex: 0 0 auto;
}
@media (max-width: 680px) {
.site-header,
.header-inner {
height: 64px;
}
.brand-mark-image {
width: 44px;
height: 44px;
}
.brand-name {
font-size: 17px;
}
.header-github-button {
min-height: 38px;
gap: 7px;
padding-inline: 12px;
font-size: 11px;
}
}
@media (max-width: 360px) {
.brand-mark-image {
width: 41px;
height: 41px;
}
.brand-name {
font-size: 16px;
}
.header-github-button {
min-height: 36px;
padding-inline: 10px;
font-size: 10px;
}
}

View File

@@ -0,0 +1,490 @@
/* 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;
}
}

View File

@@ -2,6 +2,8 @@
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 & {
@@ -9,12 +11,16 @@ type StageStyle = CSSProperties & {
"--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 || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
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;
@@ -27,6 +33,8 @@ export function HeroIllustration() {
stageRef.current?.style.setProperty("--pointer-y", "0");
};
const repeating = { duration: cycle, repeat: Infinity, repeatDelay: rest };
return (
<figure className="hero-visual">
<div
@@ -37,179 +45,341 @@ export function HeroIllustration() {
onPointerLeave={resetParallax}
aria-hidden="true"
>
<div className="hero-grid" />
<div className="hero-glow hero-glow-one" />
<div className="hero-glow hero-glow-two" />
<div className="hero-status hero-status-secure">
<span className="status-icon">
<Icon name="lock" />
</span>
<span>
<strong>End-to-end encrypted</strong>
<small>Authenticated connection</small>
</span>
<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="hero-status hero-status-route">
<span className="status-pulse" />
Direct route found
<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>
<svg className="hero-scene" viewBox="0 0 760 620">
<defs>
<linearGradient id="hero-purple" x1="240" y1="90" x2="620" y2="520">
<stop stopColor="#C16BFF" />
<stop offset="0.48" stopColor="#A855F7" />
<stop offset="1" stopColor="#6F27E9" />
</linearGradient>
<linearGradient id="hero-screen" x1="88" y1="118" x2="342" y2="345">
<stop stopColor="#25202C" />
<stop offset="1" stopColor="#151319" />
</linearGradient>
<linearGradient id="hero-phone" x1="540" y1="160" x2="692" y2="465">
<stop stopColor="#2A2530" />
<stop offset="1" stopColor="#141217" />
</linearGradient>
<radialGradient id="hero-orb">
<stop stopColor="#E3BDFF" stopOpacity="0.9" />
<stop offset="0.45" stopColor="#A855F7" stopOpacity="0.45" />
<stop offset="1" stopColor="#A855F7" stopOpacity="0" />
</radialGradient>
<filter id="hero-shadow" x="-30%" y="-30%" width="160%" height="180%">
<feDropShadow dx="0" dy="20" stdDeviation="18" floodColor="#3F2454" floodOpacity="0.16" />
</filter>
<filter id="hero-glow-filter" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="12" />
</filter>
<clipPath id="laptop-screen-clip">
<rect x="82" y="132" width="264" height="176" rx="12" />
</clipPath>
<clipPath id="phone-screen-clip">
<rect x="548" y="171" width="136" height="279" rx="25" />
</clipPath>
</defs>
<g className="scene-orbits">
<ellipse cx="385" cy="325" rx="315" ry="216" fill="none" stroke="#B969F8" strokeOpacity="0.14" />
<ellipse cx="385" cy="325" rx="245" ry="169" fill="none" stroke="#B969F8" strokeOpacity="0.12" strokeDasharray="8 14" />
<circle cx="670" cy="215" r="5" fill="#A855F7" />
<circle cx="117" cy="455" r="4" fill="#C98AFD" />
<path d="m704 345 4 10 10 4-10 4-4 10-4-10-10-4 10-4Z" fill="#D9AEFF" />
</g>
<g className="scene-laptop" filter="url(#hero-shadow)">
<rect x="69" y="117" width="290" height="207" rx="19" fill="#E8E4EB" stroke="#D6CFDA" strokeWidth="2" />
<rect x="80" y="129" width="268" height="183" rx="13" fill="url(#hero-screen)" />
<g clipPath="url(#laptop-screen-clip)">
<circle cx="208" cy="235" r="108" fill="#A855F7" opacity="0.07" />
<path d="M80 278c62-45 99-24 141-60 39-34 70-39 127-31v125H80Z" fill="#A855F7" opacity="0.09" />
<rect x="101" y="151" width="101" height="13" rx="6.5" fill="#3E3745" />
<rect x="101" y="175" width="67" height="7" rx="3.5" fill="#5E5366" />
<g className="laptop-files">
<rect x="118" y="205" width="76" height="79" rx="10" fill="#F8F2FC" stroke="#C98AFD" />
<path d="M173 205h21v21Z" fill="#E3C3FA" />
<rect x="129" y="219" width="24" height="24" rx="6" fill="#E8D0FB" />
<path d="m135 238 7-8 5 5 4-4 6 7Z" fill="#A855F7" opacity="0.75" />
<rect x="129" y="253" width="46" height="6" rx="3" fill="#C3B6CB" />
<rect x="129" y="265" width="34" height="5" rx="2.5" fill="#DDD4E2" />
<rect x="181" y="223" width="82" height="68" rx="10" fill="#2F2935" stroke="#574D5E" />
<path d="M242 223h21v21Z" fill="#4F4557" />
<circle cx="205" cy="245" r="9" fill="#A855F7" opacity="0.75" />
<rect x="195" y="264" width="49" height="6" rx="3" fill="#63586A" />
<rect x="195" y="276" width="34" height="5" rx="2.5" fill="#4D4453" />
</g>
</g>
<path d="M46 324h336l-20 23a20 20 0 0 1-15 6H81a20 20 0 0 1-15-7Z" fill="#D9D3DD" stroke="#CAC1CE" strokeWidth="2" />
<path d="M183 324h62l-7 12h-48Z" fill="#C1B8C6" />
</g>
<g className="transfer-conduit">
<path
className="conduit-glow"
d="M284 358c23 134 225 151 279 35"
fill="none"
stroke="#B65DF9"
strokeOpacity="0.2"
strokeWidth="34"
strokeLinecap="round"
filter="url(#hero-glow-filter)"
/>
<path
d="M284 358c23 134 225 151 279 35"
fill="none"
stroke="#ECE3F1"
strokeWidth="19"
strokeLinecap="round"
/>
<path
className="conduit-energy"
d="M284 358c23 134 225 151 279 35"
fill="none"
stroke="url(#hero-purple)"
strokeWidth="7"
strokeLinecap="round"
strokeDasharray="2 17"
/>
<circle cx="284" cy="358" r="25" fill="#F8F2FC" stroke="#C98AFD" strokeWidth="2" />
<circle cx="284" cy="358" r="13" fill="#A855F7" opacity="0.15" />
<path d="m276 358 7 7 11-14" fill="none" stroke="#9333EA" strokeWidth="3" strokeLinecap="round" />
</g>
<g className="approval-gate" filter="url(#hero-shadow)">
<rect x="365" y="414" width="151" height="58" rx="18" fill="#FEFCFF" stroke="#DFCDEB" />
<circle cx="393" cy="443" r="15" fill="#F0DDFE" />
<path d="M393 433.5 385.5 437v5.3c0 4.5 3.2 7.6 7.5 8.9 4.3-1.3 7.5-4.4 7.5-8.9V437Z" fill="#A855F7" />
<path d="m389.5 442 2.2 2.2 4.5-4.6" fill="none" stroke="white" strokeWidth="1.8" strokeLinecap="round" />
<text x="418" y="439" fill="#201B24" fontSize="12" fontWeight="700">Approved</text>
<text x="418" y="455" fill="#74697B" fontSize="10">Mayas phone</text>
</g>
<g className="scene-phone" filter="url(#hero-shadow)">
<rect x="536" y="157" width="160" height="309" rx="36" fill="#E4DFE7" stroke="#CFC6D3" strokeWidth="2" />
<rect x="546" y="168" width="140" height="285" rx="28" fill="url(#hero-phone)" />
<rect x="587" y="177" width="58" height="8" rx="4" fill="#0B090D" />
<g clipPath="url(#phone-screen-clip)">
<circle cx="616" cy="271" r="76" fill="#A855F7" opacity="0.1" />
<circle cx="616" cy="275" r="49" fill="url(#hero-orb)" opacity="0.55" />
<g className="received-file">
<rect x="579" y="224" width="74" height="92" rx="12" fill="#FCF9FD" stroke="#C98AFD" strokeWidth="1.5" />
<path d="M630 224h23v23Z" fill="#DEC0F6" />
<rect x="590" y="239" width="26" height="26" rx="7" fill="#E9D2FA" />
<path d="m595 259 8-9 5 5 5-5 6 9Z" fill="#9E47EF" />
<rect x="590" y="278" width="46" height="6" rx="3" fill="#C6BACD" />
<rect x="590" y="291" width="34" height="5" rx="2.5" fill="#DFD6E4" />
</g>
<rect x="564" y="337" width="104" height="42" rx="12" fill="#2F2935" stroke="#4B4251" />
<circle cx="581" cy="358" r="8" fill="#A855F7" />
<path d="m577.5 358 2.2 2.2 4.5-4.6" fill="none" stroke="white" strokeWidth="1.5" />
<text x="595" y="355" fill="#FCF9FD" fontSize="10" fontWeight="700">Received</text>
<text x="595" y="368" fill="#A89EAF" fontSize="8">Verified safely</text>
<rect x="564" y="393" width="104" height="5" rx="2.5" fill="#3F3745" />
<rect x="564" y="393" width="104" height="5" rx="2.5" fill="#A855F7" />
</g>
</g>
<g className="transfer-packets">
<g className="packet packet-one">
<rect x="0" y="0" width="28" height="34" rx="6" fill="#FCF9FE" stroke="#A855F7" />
<path d="M19 0h9v9Z" fill="#DDBCF7" />
</g>
<g className="packet packet-two">
<rect x="0" y="0" width="22" height="27" rx="5" fill="#EEE0F9" stroke="#B869F4" />
<path d="M14 0h8v8Z" fill="#CAA0E9" />
</g>
<circle className="packet-spark packet-spark-one" cx="0" cy="0" r="5" fill="#D795FF" />
<circle className="packet-spark packet-spark-two" cx="0" cy="0" r="3" fill="#A855F7" />
</g>
</svg>
<div className="hero-file-label">
<span className="file-type-icon">JPG</span>
<span>
<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>
<small>42 files · structure preserved</small>
<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&apos;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&apos;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 &amp; 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">
An illustration of encrypted files moving from a laptop to a phone after receiver approval.
A VniDrop transfer moves from a desktop sender to a phone after receiver approval, then is
verified on arrival.
</figcaption>
</figure>
);

View File

@@ -1,7 +1,7 @@
/* Hero transfer scene */
/* Coordinated product transfer stage */
.hero-visual {
position: relative;
width: min(100%, 730px);
width: min(100%, 760px);
margin: 0;
}
@@ -10,238 +10,597 @@
--pointer-y: 0;
position: relative;
width: 100%;
aspect-ratio: 1.13;
aspect-ratio: 1.17;
isolation: isolate;
perspective: 1200px;
perspective: 1400px;
}
.hero-grid {
.hero-stage-grid {
position: absolute;
z-index: -3;
inset: 9% 2% 4% 7%;
border: 1px solid rgba(168, 85, 247, 0.14);
border-radius: 50%;
background-image:
linear-gradient(rgba(121, 82, 138, 0.07) 1px, transparent 1px),
linear-gradient(90deg, rgba(121, 82, 138, 0.07) 1px, transparent 1px);
background-size: 32px 32px;
transform: rotateX(64deg) rotateZ(-8deg) translateZ(-70px);
mask-image: radial-gradient(circle, black 10%, transparent 70%);
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-glow {
.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%;
filter: blur(60px);
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;
}
.hero-glow-one {
top: 22%;
right: 8%;
width: 37%;
aspect-ratio: 1;
background: rgba(168, 85, 247, 0.23);
transform: translate(calc(var(--pointer-x) * 12px), calc(var(--pointer-y) * 12px));
}
.hero-glow-two {
bottom: 8%;
left: 28%;
width: 30%;
aspect-ratio: 1;
background: rgba(126, 58, 236, 0.16);
transform: translate(calc(var(--pointer-x) * -9px), calc(var(--pointer-y) * -9px));
}
.hero-scene {
position: absolute;
z-index: 1;
inset: 1% -3% auto -1%;
width: 105%;
overflow: visible;
transform: rotateY(calc(var(--pointer-x) * 1.7deg)) rotateX(calc(var(--pointer-y) * -1.7deg));
transform-origin: center;
transition: transform 180ms ease-out;
}
.scene-laptop {
transform: translate(calc(var(--pointer-x) * -4px), calc(var(--pointer-y) * -4px));
transition: transform 200ms ease-out;
}
.scene-phone {
transform: translate(calc(var(--pointer-x) * 7px), calc(var(--pointer-y) * 5px));
transition: transform 200ms ease-out;
}
.scene-orbits {
transform-origin: 385px 325px;
animation: scene-orbits 30s linear infinite;
}
.laptop-files {
transform-origin: 190px 245px;
animation: laptop-files 6s ease-in-out infinite;
}
.conduit-energy {
animation: conduit-energy 2s linear infinite;
}
.approval-gate {
transform-origin: 440px 443px;
animation: gate-hover 5s ease-in-out infinite;
}
.received-file {
transform-origin: 616px 270px;
animation: received-file 7s cubic-bezier(0.34, 1.2, 0.55, 1) infinite;
}
.packet,
.packet-spark {
opacity: 0;
transform-box: fill-box;
transform-origin: center;
}
.packet-one {
animation: hero-packet-one 7s cubic-bezier(0.45, 0.05, 0.35, 1) 0.6s infinite;
}
.packet-two {
animation: hero-packet-two 7s cubic-bezier(0.45, 0.05, 0.35, 1) 3s infinite;
}
.packet-spark-one {
animation: hero-spark-one 7s ease 0.6s infinite;
}
.packet-spark-two {
animation: hero-spark-two 7s ease 3s infinite;
}
.hero-status {
position: absolute;
z-index: 5;
display: flex;
align-items: center;
border: 1px solid rgba(222, 211, 228, 0.95);
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 15px 45px rgba(49, 27, 61, 0.11);
backdrop-filter: blur(12px);
transition: transform 180ms ease-out;
}
.hero-status-secure {
top: 2%;
right: 5%;
gap: 10px;
padding: 10px 13px 10px 10px;
border-radius: 14px;
transform: translate(calc(var(--pointer-x) * 12px), calc(var(--pointer-y) * 8px)) rotate(2deg);
animation: status-hover 6s ease-in-out infinite;
}
.status-icon {
.desktop-window-bar {
position: relative;
z-index: 2;
display: grid;
width: 34px;
grid-template-columns: 1fr auto 1fr;
align-items: center;
height: 34px;
place-items: center;
border-radius: 10px;
background: var(--brand-100);
color: var(--brand-600);
padding: 0 11px;
border-bottom: 1px solid #e9e5eb;
border-radius: 20px 20px 0 0;
background: rgba(248, 247, 249, 0.92);
}
.status-icon svg {
width: 17px;
.window-controls {
display: flex;
gap: 5px;
}
.hero-status strong,
.hero-status small {
display: block;
}
.hero-status strong {
color: #2b2530;
font-size: 10px;
line-height: 1.4;
}
.hero-status small {
color: #897f8e;
font-size: 8px;
}
.hero-status-route {
top: 18%;
left: 9%;
gap: 8px;
padding: 7px 11px;
border-radius: 999px;
color: #534b57;
font-family: var(--font-mono);
font-size: 8px;
font-weight: 700;
letter-spacing: 0.04em;
transform: translate(calc(var(--pointer-x) * -8px), calc(var(--pointer-y) * -6px)) rotate(-3deg);
}
.status-pulse,
.live-dot {
display: inline-block;
.window-controls i {
width: 7px;
height: 7px;
border-radius: 50%;
background: #2bc477;
box-shadow: 0 0 0 4px rgba(43, 196, 119, 0.13);
animation: status-pulse 2.4s ease-in-out infinite;
background: #d8d2dc;
}
.hero-file-label {
position: absolute;
z-index: 4;
bottom: 7%;
left: 7%;
.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: 10px 13px;
border: 1px solid rgba(222, 211, 228, 0.95);
padding: 9px;
border: 1px solid #e9e5eb;
border-radius: 14px;
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 15px 45px rgba(49, 27, 61, 0.11);
backdrop-filter: blur(12px);
transform: translate(calc(var(--pointer-x) * -10px), calc(var(--pointer-y) * -7px)) rotate(-2deg);
transition: transform 180ms ease-out;
background: #f7f6f8;
}
.file-type-icon {
.file-collage {
display: grid;
width: 37px;
height: 37px;
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, var(--brand-400), var(--brand-600));
color: white;
font-family: var(--font-mono);
font-size: 8px;
background: linear-gradient(135deg, #ead2fe, #c084fc);
color: #5c1bb8;
font-size: 9px;
font-weight: 800;
}
.hero-file-label strong,
.hero-file-label small {
.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;
}
.hero-file-label strong {
font-size: 10px;
.desktop-transfer-foot {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: auto;
padding: 8px 2px 0;
color: #8e8492;
font-size: 6px;
}
.hero-file-label small {
color: var(--ink-muted);
.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;
}

View File

@@ -65,7 +65,12 @@ export function Icon({ name, ...props }: IconProps) {
<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" />
),
github: (
<path d="M12 2a10 10 0 0 0-3.2 19.5c.5.1.7-.2.7-.5v-1.9c-2.8.6-3.4-1.2-3.4-1.2-.5-1.1-1.1-1.4-1.1-1.4-.9-.6.1-.6.1-.6 1 0 1.5 1 1.5 1 .9 1.5 2.3 1.1 2.9.8.1-.6.3-1.1.6-1.3-2.2-.3-4.6-1.1-4.6-5a3.9 3.9 0 0 1 1-2.7c-.1-.3-.4-1.3.1-2.7 0 0 .9-.3 2.8 1a9.6 9.6 0 0 1 5.1 0c2-1.3 2.8-1 2.8-1 .6 1.4.2 2.4.1 2.7a3.9 3.9 0 0 1 1 2.7c0 3.9-2.4 4.7-4.6 5 .4.3.7.9.7 1.8V21c0 .3.2.6.7.5A10 10 0 0 0 12 2Z" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.5 2.22168C5.23312 2.22168 2.58496 4.87398 2.58496 8.14677C2.58496 10.7642 4.27962 12.9853 6.63026 13.7684C6.92601 13.8228 7.03366 13.6401 7.03366 13.4827C7.03366 13.3425 7.02893 12.9693 7.02597 12.4754C5.38041 12.8333 5.0332 11.681 5.0332 11.681C4.76465 10.996 4.37663 10.8139 4.37663 10.8139C3.83954 10.4471 4.41744 10.4542 4.41744 10.4542C5.01072 10.4956 5.32303 11.0647 5.32303 11.0647C5.85065 11.9697 6.70774 11.7082 7.04431 11.5568C7.09873 11.1741 7.25134 10.9132 7.42051 10.7654C6.10737 10.6157 4.72621 10.107 4.72621 7.83683C4.72621 7.19031 4.95689 6.66092 5.33486 6.24686C5.27394 6.09721 5.07105 5.49447 5.39283 4.67938C5.39283 4.67938 5.88969 4.51967 7.01947 5.28626C7.502 5.15466 7.99985 5.08763 8.5 5.08692C9.00278 5.08929 9.50851 5.15495 9.98113 5.28626C11.1103 4.51967 11.606 4.67879 11.606 4.67879C11.9289 5.49447 11.7255 6.09721 11.6651 6.24686C12.0437 6.66092 12.2732 7.19031 12.2732 7.83683C12.2732 10.1129 10.8897 10.6139 9.5724 10.7606C9.78475 10.9434 9.97344 11.3048 9.97344 11.8579C9.97344 12.6493 9.96634 13.2887 9.96634 13.4827C9.96634 13.6413 10.0728 13.8258 10.3733 13.7678C11.5512 13.3728 12.5751 12.6175 13.3003 11.6089C14.0256 10.6002 14.4155 9.38912 14.415 8.14677C14.415 4.87398 11.7663 2.22168 8.5 2.22168Z"
fill="currentColor"
/>
),
globe: (
<>
@@ -125,9 +130,9 @@ export function Icon({ name, ...props }: IconProps) {
return (
<svg
viewBox="0 0 24 24"
viewBox={name === "github" ? "0 0 17 16" : "0 0 24 24"}
fill="none"
stroke="currentColor"
stroke={name === "github" ? "none" : "currentColor"}
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"

View File

@@ -59,7 +59,7 @@
transform: translate(-50%, -50%) rotate(-3deg);
}
.platform-center svg { position: relative; z-index: 2; width: 87px; height: 87px; }
.platform-center img { position: relative; z-index: 2; width: 87px; height: 87px; }
.platform-center-glow {
position: absolute;
@@ -100,4 +100,3 @@
.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; }

View File

@@ -25,7 +25,7 @@ export function SiteFooter() {
<div>
<p className="footer-label">Project</p>
<a href="https://github.com/vnidrop/vnidrop" target="_blank" rel="noreferrer">
GitHub <Icon name="arrow" />
GitHub <Icon name="github" />
</a>
<a
href="https://github.com/vnidrop/vnidrop/blob/master/LICENSE"

View File

@@ -1,71 +1,22 @@
"use client";
import Link from "next/link";
import { useState } from "react";
import { Brand } from "@/components/brand";
import { Icon } from "@/components/icons";
const navigation = [
{ href: "/#how-it-works", label: "How it works" },
{ href: "/#privacy", label: "Privacy" },
{ href: "/#platforms", label: "Platforms" },
];
export function SiteHeader() {
const [open, setOpen] = useState(false);
return (
<header className="site-header">
<div className="header-inner">
<div className="header-inner page-shell">
<Brand />
<nav className="desktop-nav" aria-label="Main navigation">
{navigation.map((item) => (
<Link key={item.href} href={item.href}>
{item.label}
</Link>
))}
</nav>
<a
className="button button-small button-dark header-github"
<Link
className="header-github-button"
href="https://github.com/vnidrop/vnidrop"
target="_blank"
rel="noreferrer"
>
<Icon name="github" />
GitHub
</a>
<button
className="menu-button"
type="button"
aria-expanded={open}
aria-controls="mobile-navigation"
aria-label={open ? "Close navigation" : "Open navigation"}
onClick={() => setOpen((value) => !value)}
>
<Icon name={open ? "close" : "menu"} />
</button>
View on GitHub
</Link>
</div>
<nav
id="mobile-navigation"
className={`mobile-nav${open ? " is-open" : ""}`}
aria-label="Mobile navigation"
>
{navigation.map((item) => (
<Link key={item.href} href={item.href} onClick={() => setOpen(false)}>
{item.label}
<Icon name="arrow" />
</Link>
))}
<a
href="https://github.com/vnidrop/vnidrop"
target="_blank"
rel="noreferrer"
onClick={() => setOpen(false)}
>
Explore on GitHub
<Icon name="github" />
</a>
</nav>
</header>
);
}

69
docs/package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "vnidrop-site",
"version": "0.1.0",
"dependencies": {
"motion": "^12.42.2",
"next": "16.2.10",
"react": "19.2.7",
"react-dom": "19.2.7"
@@ -3433,6 +3434,33 @@
"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": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -4489,6 +4517,47 @@
"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": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

View File

@@ -12,6 +12,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"motion": "^12.42.2",
"next": "16.2.10",
"react": "19.2.7",
"react-dom": "19.2.7"

View File

@@ -1,21 +1,26 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="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(#paint0)"/>
<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(#paint1)"/>
<mask id="fold" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="176" y="148" width="671" height="732">
<path 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.68V148Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="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(#paint0_linear_11_12)"/>
<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(#paint1_linear_11_12)"/>
<mask id="mask0_11_12" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="176" y="148" width="671" height="732">
<path 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="white"/>
</mask>
<g mask="url(#fold)">
<path d="M688 148H782C832 148 842 171.8 847.48 210V303.68L688 148Z" fill="url(#paint2)"/>
<g mask="url(#mask0_11_12)">
<path d="M688 148H782C832 148 842 171.8 847.48 210V303.68L688 148Z" fill="url(#paint2_linear_11_12)"/>
</g>
<defs>
<linearGradient id="paint0" x1="176" y1="148" x2="904.706" y2="816.252" gradientUnits="userSpaceOnUse">
<stop stop-color="#A855F7"/><stop offset="0.48" stop-color="#9D4DF4"/><stop offset="1" stop-color="#7C2AEF"/>
<linearGradient id="paint0_linear_11_12" x1="176" y1="148" x2="904.706" y2="816.252" gradientUnits="userSpaceOnUse">
<stop stop-color="#A855F7"/>
<stop offset="0.48" stop-color="#9D4DF4"/>
<stop offset="1" stop-color="#7C2AEF"/>
</linearGradient>
<linearGradient id="paint1" x1="404.439" y1="431.72" x2="707.314" y2="649.286" gradientUnits="userSpaceOnUse">
<stop stop-color="#A855F7"/><stop offset="0.48" stop-color="#9D4DF4"/><stop offset="1" stop-color="#7C2AEF"/>
<linearGradient id="paint1_linear_11_12" x1="404.439" y1="431.72" x2="707.314" y2="649.286" gradientUnits="userSpaceOnUse">
<stop stop-color="#A855F7"/>
<stop offset="0.48" stop-color="#9D4DF4"/>
<stop offset="1" stop-color="#7C2AEF"/>
</linearGradient>
<linearGradient id="paint2" x1="683.48" y1="144.6" x2="793.636" y2="306.833" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2DDFF"/><stop offset="1" stop-color="#C084FC"/>
<linearGradient id="paint2_linear_11_12" x1="683.48" y1="144.6" x2="793.636" y2="306.833" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2DDFF"/>
<stop offset="1" stop-color="#C084FC"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -8,7 +8,7 @@
"theme_color": "#a855f7",
"icons": [
{
"src": "/brand-mark.svg",
"src": "/icon.svg",
"sizes": "any",
"type": "image/svg+xml"
}