diff --git a/.gitignore b/.gitignore index 7a5cee6..e740f88 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ pnpm-debug.log* .idea/ .claude +skills-lock.json + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d21ef7e..f7fc5e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1112,8 +1112,8 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - devalue@5.8.0: - resolution: {integrity: sha512-2zA9pFEsnp7vWBZbXF5JAgAq0fsUIt/1XPbRiAmRV3lp/2C3upzH+sADiyy66aFCihoLEsrQHxNM5w1gIDfsBg==} + devalue@5.8.1: + resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==} devalue@5.8.1: resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==} @@ -2094,7 +2094,7 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': 5.2.0(vite@7.3.2(jiti@2.7.0)(lightningcss@1.32.0)) - devalue: 5.8.0 + devalue: 5.8.1 react: 19.2.5 react-dom: 19.2.5(react@19.2.5) ultrahtml: 1.6.0 @@ -2987,7 +2987,7 @@ snapshots: detect-libc@2.1.2: {} - devalue@5.8.0: {} + devalue@5.8.1: {} devalue@5.8.1: {} diff --git a/public/IPFS-logo.png b/public/IPFS-logo.png new file mode 100644 index 0000000..fcd1f2d Binary files /dev/null and b/public/IPFS-logo.png differ diff --git a/public/IPFS-logo.svg b/public/IPFS-logo.svg new file mode 100644 index 0000000..f285afc --- /dev/null +++ b/public/IPFS-logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/brands/hin.webp b/public/brands/hin.webp new file mode 100644 index 0000000..d1328b4 Binary files /dev/null and b/public/brands/hin.webp differ diff --git a/public/favicon.ico b/public/favicon.ico index 7f48a94..a23747b 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/favicon.svg b/public/favicon.svg index f157bd1..9a8eb37 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,9 +1,8 @@ - - - + + + + + + + diff --git a/public/portraits/georg-grev.jpg b/public/portraits/georg-grev.jpg new file mode 100644 index 0000000..a8f445c Binary files /dev/null and b/public/portraits/georg-grev.jpg differ diff --git a/src/components/Footer.astro b/src/components/Footer.astro index f154237..6123f69 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -28,31 +28,31 @@ const columns: [string, { label: string; href: string }[]][] = [ ]; --- -
- IPFS + IPFS
-

+

An open-source project. Specs live at specs.ipfs.tech. The community gathers at discuss.ipfs.tech. Development is supported by the IPFS Foundation.

{columns.map(([h, items]) => (
-
+
{h}
{items.map((i) => ( -
+
{i.label}
))}
))}
-
+
© IPFS · MIT License
diff --git a/src/components/GetStarted.astro b/src/components/GetStarted.astro index 181c4c9..0bcc0a0 100644 --- a/src/components/GetStarted.astro +++ b/src/components/GetStarted.astro @@ -72,7 +72,7 @@ const row2 = [ const src = (n: number) => `/community/community-${n}.jpg`; --- -
+
-

+

By the community, for the community.

-

+

IPFS is built in the open by a thriving community. Chat with builders, propose changes, and find the next event near you.

@@ -118,11 +118,11 @@ const src = (n: number) => `/community/community-${n}.jpg`;
{c.glyph}
-
+
{c.kind}
-
+
{c.blurb}
@@ -163,7 +163,7 @@ const src = (n: number) => `/community/community-${n}.jpg`; display: flex; align-items: center; justify-content: center; - font-size: 18px; + font-size: var(--text-lg); font-weight: 600; } /* community photo ribbon */ diff --git a/src/components/Hero.astro b/src/components/Hero.astro index c67934c..659160e 100644 --- a/src/components/Hero.astro +++ b/src/components/Hero.astro @@ -4,7 +4,7 @@ const tagline = { pre: 'Building Blocks For ', hl: 'a Better Web', post: '.', - sub: 'A constellation of tools for content addressing.', + sub: 'IPFS is a constellation of open-source tools for content addressing.', }; // Deterministic constellation mesh. Positions come from a jittered grid @@ -124,14 +124,14 @@ for (let i = 0; i < nodes.length; i++) {

{tagline.pre}{tagline.hl}

-

+

{tagline.sub}

- Read the specs → - - open-source + Pick the tool that fits the job → + + The InterPlanetary File System
@@ -139,7 +139,7 @@ for (let i = 0; i < nodes.length; i++) { diff --git a/src/components/NetworkTopologies.astro b/src/components/NetworkTopologies.astro index 95d8c24..b97147e 100644 --- a/src/components/NetworkTopologies.astro +++ b/src/components/NetworkTopologies.astro @@ -5,8 +5,8 @@ import SectionHeader from './SectionHeader.astro';
@@ -100,7 +100,7 @@ import SectionHeader from './SectionHeader.astro';
Public -

Nodes discover peers via the Amino DHT and IPNI. Any compliant node can join and find content, no coordination required.

+

Nodes discover peers via the Amino DHT and IPNI. Any compliant node can join and find content, no coordination required.

@@ -220,7 +220,7 @@ import SectionHeader from './SectionHeader.astro';
Hybrid -

Public peers, private data. Content is encrypted. CIDs route over the Amino DHT while ciphertext stays opaque to every transit node.

+

Public peers, private data. Content is encrypted. CIDs route over the Amino DHT while ciphertext stays opaque to every transit node.

@@ -288,7 +288,7 @@ import SectionHeader from './SectionHeader.astro';
Private -

All peers share a swarm key. Routing and transfer stay within the closed network. Nothing routes in or out without it.

+

All peers share a swarm key. Routing and transfer stay within the closed network. Nothing routes in or out without it.

@@ -390,7 +390,7 @@ import SectionHeader from './SectionHeader.astro';
Trustless Gateways -

Gateways pull content from the IPFS network and serve it over HTTP. Browsers verify data against the CID locally, trust is rooted in CID, not the gateway.

+

Gateways pull content from the IPFS network and serve it over HTTP. Browsers verify data against the CID locally, trust is rooted in CID, not the gateway.

@@ -436,11 +436,10 @@ import SectionHeader from './SectionHeader.astro'; .topo-badge { display: inline-flex; align-items: center; - font-family: var(--font-mono); - font-size: 11px; + font-family: var(--font-retro); + font-style: italic; + font-size: var(--text-xs); font-weight: 600; - letter-spacing: .08em; - text-transform: uppercase; padding: 3px 10px; border-radius: 4px; width: fit-content; @@ -451,10 +450,19 @@ import SectionHeader from './SectionHeader.astro'; .topo-desc { margin: 0; - font-size: 15px; + font-size: var(--text-base); color: var(--ink-2); line-height: 1.6; } + .topo-desc a { + color: var(--turq); + text-decoration: none; + border-bottom: 1px solid color-mix(in srgb, var(--turq) 40%, transparent); + transition: border-color .15s; + } + .topo-desc a:hover { + border-bottom-color: var(--turq); + } /* Public network SVG classes */ .t-line { stroke: var(--line); stroke-width: 1.5; fill: none; } diff --git a/src/components/SectionHeader.astro b/src/components/SectionHeader.astro index 72f0903..71109b3 100644 --- a/src/components/SectionHeader.astro +++ b/src/components/SectionHeader.astro @@ -9,15 +9,15 @@ const { label, title, kicker } = Astro.props;
{label && ( -
+
{label}
)} -

+

{title}

{kicker && ( -

+

{kicker}

)} diff --git a/src/components/SocialProof.astro b/src/components/SocialProof.astro index c5e43fe..4849814 100644 --- a/src/components/SocialProof.astro +++ b/src/components/SocialProof.astro @@ -3,12 +3,12 @@ import { categories } from '../data/stories'; // Brand logos for the trust strip — the subset of stories that ship a real mark. const logos = [ - { src: '/brands/wikipedia.png', alt: 'Wikipedia' }, - { src: '/brands/snapshot.png', alt: 'Snapshot' }, - { src: '/brands/orcestra.svg', alt: 'ORCESTRA' }, - { src: '/brands/anytype.png', alt: 'Anytype' }, - { src: '/brands/weatherxm.png', alt: 'WeatherXM' }, - { src: '/brands/3s.png', alt: '3S Studios' }, + { src: '/brands/wikipedia.png', alt: 'Wikipedia', href: 'https://blog.ipfs.tech/24-uncensorable-wikipedia/' }, + { src: '/brands/snapshot.png', alt: 'Snapshot', href: 'https://snapshot.org/' }, + { src: '/brands/orcestra.svg', alt: 'ORCESTRA', href: 'https://orcestra-campaign.org/' }, + { src: '/brands/anytype.png', alt: 'Anytype', href: 'https://anytype.io/' }, + { src: '/brands/weatherxm.png', alt: 'WeatherXM', href: 'https://weatherxm.com/' }, + { src: '/brands/hin.webp', alt: 'HIN', href: 'https://www.hin.ch/' }, ]; // Single source of truth: lift every testimonial out of the use-case data so @@ -16,7 +16,7 @@ const logos = [ const quotes = categories.flatMap((c) => c.cases .filter((x) => x.quote) - .map((x) => ({ ...x.quote!, brand: x.brand })), + .map((x) => ({ ...x.quote!, brand: x.brand, href: x.href, linkLabel: x.linkLabel })), ); const initials = (name: string) => @@ -25,15 +25,17 @@ const initials = (name: string) =>
-
running in production at
+
running IPFS in production
{logos.map((l) => ( - + + + ))}
-
what builders say
+
what builders say
@@ -88,11 +91,8 @@ const initials = (name: string) => max-width: 1240px; } .proof-label { - font-size: 11px; - letter-spacing: .18em; - text-transform: uppercase; + font-size: var(--text-ui); color: var(--ink-3); - font-weight: 600; text-align: center; margin-bottom: 22px; } @@ -106,6 +106,10 @@ const initials = (name: string) => gap: 32px 52px; margin-bottom: 44px; } + .proof-logo-link { + display: inline-flex; + align-items: center; + } .proof-logo { height: 28px; width: auto; @@ -120,6 +124,14 @@ const initials = (name: string) => filter: grayscale(0); opacity: 1; } + /* Dark mode: invert the muted marks so they read light on the dark surface. */ + :global(html[data-theme='dark']) .proof-logo { + filter: grayscale(1) invert(1) brightness(1.6); + opacity: .65; + } + :global(html[data-theme='dark']) .proof-logo:hover { + opacity: 1; + } /* ── Testimonials: one scrollable row ─────────────────────────────── */ .proof-quotes-head { @@ -130,11 +142,8 @@ const initials = (name: string) => margin-bottom: 14px; } .proof-quotes-label { - font-size: 11px; - letter-spacing: .16em; - text-transform: uppercase; + font-size: var(--text-ui); color: var(--ink-3); - font-weight: 600; } .proof-arrows { display: flex; @@ -147,7 +156,7 @@ const initials = (name: string) => border: 1px solid var(--line); background: var(--paper); color: var(--navy); - font-size: 15px; + font-size: var(--text-base); cursor: pointer; display: flex; align-items: center; @@ -188,10 +197,24 @@ const initials = (name: string) => display: flex; flex-direction: column; gap: 16px; + text-decoration: none; + color: inherit; + transition: border-color .15s, transform .15s, box-shadow .15s; + } + .proof-quote:hover { + border-color: var(--turq); + transform: translateY(-2px); + box-shadow: 0 10px 30px -18px rgba(7, 58, 83, 0.25); + } + .proof-quote-link { + font-size: var(--text-2xs); + letter-spacing: .04em; + color: var(--turq); + font-weight: 600; } .proof-quote-text { margin: 0; - font-size: 15px; + font-size: var(--text-base); line-height: 1.55; color: var(--ink); letter-spacing: -0.005em; @@ -213,7 +236,7 @@ const initials = (name: string) => align-items: center; justify-content: center; font-family: var(--font-mono); - font-size: 12px; + font-size: var(--text-xs); font-weight: 600; } .proof-avatar-photo { @@ -221,23 +244,20 @@ const initials = (name: string) => background: var(--pearl); } .proof-quote-who { - font-size: 13px; + font-size: var(--text-sm); font-weight: 600; color: var(--navy); } .proof-quote-role { - font-size: 12px; + font-size: var(--text-xs); color: var(--ink-3); line-height: 1.3; } .proof-quote-brand { margin-left: auto; align-self: flex-start; - font-size: 10px; - letter-spacing: .12em; - text-transform: uppercase; + font-size: var(--text-xs); color: var(--turq); - font-weight: 600; } @media (max-width: 760px) { diff --git a/src/components/ThreePillars.astro b/src/components/ThreePillars.astro index df39611..796f33b 100644 --- a/src/components/ThreePillars.astro +++ b/src/components/ThreePillars.astro @@ -56,7 +56,7 @@ const pillars = [
{p.num} - {p.label} + {p.label}
→ {p.question}

{p.title}

@@ -112,28 +112,28 @@ const pillars = [ align-items: baseline; } .pillar-num { - font-size: 12px; + font-size: var(--text-xs); color: var(--turq); font-weight: 600; letter-spacing: .1em; } .pillar-label { - font-size: 11px; - text-transform: uppercase; - letter-spacing: .12em; + font-family: var(--font-retro); + font-style: italic; + font-size: var(--text-sm); color: var(--ink-3); font-weight: 600; } .pillar-question { - font-size: 12px; + font-size: var(--text-xs); color: var(--jade); font-weight: 500; letter-spacing: .02em; } .pillar-title { margin: 0; - font-size: 24px; + font-size: var(--text-2xl); font-weight: 600; color: var(--navy); letter-spacing: -0.015em; @@ -141,7 +141,7 @@ const pillars = [ } .pillar-body { margin: 0; - font-size: 15px; + font-size: var(--text-base); color: var(--ink-2); line-height: 1.6; flex: 1; @@ -174,12 +174,12 @@ const pillars = [ } .pillar-chip-name { font-family: var(--font-mono); - font-size: 13px; + font-size: var(--text-sm); color: var(--turq); font-weight: 600; } .pillar-chip-hint { - font-size: 11px; + font-size: var(--text-2xs); color: var(--ink-3); text-align: right; } diff --git a/src/components/Tools.astro b/src/components/Tools.astro index f25f689..5f062fc 100644 --- a/src/components/Tools.astro +++ b/src/components/Tools.astro @@ -7,7 +7,7 @@ import ToolsProblems from './islands/ToolsProblems.tsx';
diff --git a/src/components/UseCases.astro b/src/components/UseCases.astro index 76332f8..2b91a35 100644 --- a/src/components/UseCases.astro +++ b/src/components/UseCases.astro @@ -9,7 +9,7 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; Running in science labs, game studios, art vaults, and low earth orbit.

- Anywhere data needs to be verifiable, CIDs tend to show up. A sample of what teams have built with the IPFS tools at the core. + Anywhere data needs to be verifiable, CIDs are helpful. A sample of what teams have built with the IPFS tools at the core.

@@ -33,7 +33,7 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; } .usecases-title { margin: 0 0 12px; - font-size: 44px; + font-size: var(--text-h2); font-weight: 600; letter-spacing: -0.02em; line-height: 1.08; @@ -42,7 +42,7 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; } .usecases-lede { margin: 0; - font-size: 17px; + font-size: var(--text-lead); color: var(--ink-2); max-width: 680px; line-height: 1.55; @@ -98,33 +98,13 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; } .uc-tabs-rail.is-hydrated .uc-tab.is-active::after { display: none; } - .uc-tab-num { - font-size: 11px; - letter-spacing: .15em; - font-weight: 600; - color: var(--turq); - opacity: .55; - transition: opacity .25s ease; - } - .uc-tab.is-active .uc-tab-num { opacity: 1; } - .uc-tab-label { - font-size: 15px; + font-size: var(--text-base); font-weight: 500; letter-spacing: -0.005em; white-space: nowrap; } - .uc-tab-count { - font-size: 10px; - letter-spacing: .12em; - color: var(--ink-3); - opacity: 0; - transform: translateY(-1px); - transition: opacity .25s ease; - } - .uc-tab.is-active .uc-tab-count { opacity: .7; } - .uc-indicator { position: absolute; left: 0; @@ -157,7 +137,7 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; } .uc-job { margin: 0 0 8px; - font-size: 26px; + font-size: 1.625rem; font-weight: 600; letter-spacing: -0.015em; color: var(--navy); @@ -165,7 +145,7 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; } .uc-framing { margin: 0; - font-size: 15px; + font-size: var(--text-base); color: var(--ink-2); line-height: 1.55; max-width: 560px; @@ -226,7 +206,7 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; justify-content: center; font-family: var(--font-mono); font-weight: 600; - font-size: 14px; + font-size: var(--text-ui); letter-spacing: .03em; box-shadow: inset 0 0 0 1px rgba(255,255,255,0.15); } @@ -241,15 +221,15 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; object-fit: contain; } .story-card-brand { - font-size: 11px; - letter-spacing: .12em; - text-transform: uppercase; + font-family: var(--font-retro); + font-style: italic; + font-size: var(--text-sm); color: var(--ink-3); font-weight: 600; } .story-card-title { margin: 0; - font-size: 17px; + font-size: var(--text-lead); font-weight: 600; color: var(--navy); line-height: 1.25; @@ -257,7 +237,7 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; } .story-card-body { margin: 0; - font-size: 14px; + font-size: var(--text-ui); color: var(--ink-2); line-height: 1.55; flex: 1; @@ -274,7 +254,7 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; } .story-card-quote-text { margin: 0; - font-size: 13px; + font-size: var(--text-sm); color: var(--ink-2); line-height: 1.55; font-style: italic; @@ -294,7 +274,7 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; align-items: center; justify-content: center; font-family: var(--font-mono); - font-size: 10px; + font-size: var(--text-2xs); font-weight: 600; flex-shrink: 0; } @@ -304,18 +284,18 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; background: var(--pearl); } .story-card-quote-who { - font-size: 12px; + font-size: var(--text-xs); font-weight: 600; color: var(--navy); } .story-card-quote-role { - font-size: 11px; + font-size: var(--text-2xs); color: var(--ink-3); } .story-card-footer { display: flex; justify-content: space-between; - font-size: 12px; + font-size: var(--text-xs); color: var(--turq); font-weight: 500; gap: 8px; @@ -329,9 +309,9 @@ import UseCasesTabs from './islands/UseCasesTabs.tsx'; @media (max-width: 760px) { .usecases-section { padding: 72px 24px; } - .usecases-title { font-size: 32px; } + .usecases-title { font-size: var(--text-4xl); } .uc-tabs-rail { gap: 24px; } - .uc-job { font-size: 22px; } + .uc-job { font-size: 1.375rem; } } @media (prefers-reduced-motion: reduce) { diff --git a/src/components/islands/Constellation.tsx b/src/components/islands/Constellation.tsx index c84524a..6918001 100644 --- a/src/components/islands/Constellation.tsx +++ b/src/components/islands/Constellation.tsx @@ -126,7 +126,7 @@ export default function Constellation({ height = 480 }: { height?: number }) { boxShadow: isLocked ? `0 0 0 6px ${KINDS[n.kind].color}33` : 'none', }} />
{n.label}
@@ -134,7 +134,7 @@ export default function Constellation({ height = 480 }: { height?: number }) { ); })}
-
+
{activeNode.role}
-

+

{activeNode.label}

-

{activeNode.blurb}

+

{activeNode.blurb}

{activeNode.link === '—' ? ( - {activeNode.link} + {activeNode.link} ) : ( ↗ {activeNode.link} diff --git a/src/components/islands/FileDropCID.tsx b/src/components/islands/FileDropCID.tsx index 0aa3e06..42e8a0f 100644 --- a/src/components/islands/FileDropCID.tsx +++ b/src/components/islands/FileDropCID.tsx @@ -21,6 +21,12 @@ async function computeCid(bytes: Uint8Array): Promise { return 'bafkreic' + out.slice(0, 48); } +// Detects a pasted CIDv1 (base32, starts with b). CIDv0 (Qm…) is excluded +// because the anatomy labels assume the CIDv1 layout and would be wrong for it. +function looksLikeCid(s: string): boolean { + return /^b[a-z2-7]{40,}$/.test(s.trim()); +} + interface CidPart { key: string; label: string; @@ -63,7 +69,7 @@ function CIDAnatomy({ cid, pulse }: { cid: string; pulse: boolean }) { background: pulse ? 'rgba(107,196,206,0.12)' : 'var(--pearl)', borderRadius: 10, padding: 14, transition: 'background .4s', }}> -
+
{parts.map((p) => { const isHovered = hover === p.key; const isDim = !!hover && !isHovered; @@ -104,14 +110,14 @@ function CIDAnatomy({ cid, pulse }: { cid: string; pulse: boolean }) { transition: 'all .12s', minWidth: 0, borderLeft: `3px solid ${p.color}`, }}> -
{p.label}
{p.sub}
@@ -119,12 +125,12 @@ function CIDAnatomy({ cid, pulse }: { cid: string; pulse: boolean }) { ); })}
-
+
{hovered ? ( - {hovered.label} {hovered.detail} @@ -147,9 +153,14 @@ export default function FileDropCID() { const [size, setSize] = useState(0); const [dragOver, setDragOver] = useState(false); const [pulseCid, setPulseCid] = useState(false); + // When a CID is pasted we hide the input so its computed hash can't be confused + // with the pasted one. `editNonce` lets us recompute from text on returning to edit. + const [showInput, setShowInput] = useState(true); + const [editNonce, setEditNonce] = useState(0); const liveRef = useRef(true); useEffect(() => { + if (!showInput) return; liveRef.current = true; setComputing(true); const bytes = new TextEncoder().encode(text); @@ -166,7 +177,24 @@ export default function FileDropCID() { liveRef.current = false; clearTimeout(t); }; - }, [text]); + }, [text, editNonce]); + + const onPaste = (e: React.ClipboardEvent) => { + const pasted = e.clipboardData.getData('text'); + if (!looksLikeCid(pasted)) return; + e.preventDefault(); + liveRef.current = false; // cancel any pending compute from the textarea + setShowInput(false); + setComputing(false); + setCid(pasted.trim()); + setPulseCid(true); + setTimeout(() => setPulseCid(false), 450); + }; + + const editText = () => { + setShowInput(true); + setEditNonce((n) => n + 1); // re-trigger compute from the current text + }; const onDrop = async (e: React.DragEvent) => { e.preventDefault(); @@ -185,41 +213,67 @@ export default function FileDropCID() { return (
-
- ↓ drop a file or edit +
+ {showInput ? '↓ drop a file, edit, or paste a CID' : 'pasted CID'}
-
- sha-256 · {size} bytes +
+ {showInput ? `sha-256 · ${size} bytes` : 'from clipboard'}
-
{ e.preventDefault(); setDragOver(true); }} - onDragLeave={() => setDragOver(false)} - onDrop={onDrop} - style={{ - border: `1.5px dashed ${dragOver ? 'var(--turq)' : 'var(--stone)'}`, - borderRadius: 8, padding: 12, marginBottom: 12, - background: dragOver ? 'rgba(107,196,206,0.08)' : 'var(--pearl)', - transition: 'all .15s', - }} - > -