From 530ba3218b8a5c8f9d90b30f3d6b5d3ea76475a4 Mon Sep 17 00:00:00 2001 From: tsoumdoa <8blondes.swerves@icloud.com> Date: Wed, 29 Jul 2026 19:27:14 +0800 Subject: [PATCH 1/3] =?UTF-8?q?Rewrite=20landing=20around=20keep=20?= =?UTF-8?q?=E2=86=92=20share=20=E2=86=92=20see.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lead with personal clip library, then frictionless share and DuckerWeb inspect/diff, with light in-page demos instead of the review-first case cards. Co-authored-by: Cursor --- .../components/landing/clip-library-demo.tsx | 159 ++++++++ .../landing/inspect-diff-visual.tsx | 232 +++++++++++ src/routes/index.tsx | 374 +++++++++--------- 3 files changed, 573 insertions(+), 192 deletions(-) create mode 100644 src/app/components/landing/clip-library-demo.tsx create mode 100644 src/app/components/landing/inspect-diff-visual.tsx diff --git a/src/app/components/landing/clip-library-demo.tsx b/src/app/components/landing/clip-library-demo.tsx new file mode 100644 index 0000000..69d10f5 --- /dev/null +++ b/src/app/components/landing/clip-library-demo.tsx @@ -0,0 +1,159 @@ +import { AnimatePresence, motion } from "motion/react"; +import { Check, Copy, Share } from "lucide-react"; +import { useState } from "react"; + +const SAMPLE_CLIPS = [ + { + id: "facade", + name: "ParametricFacade", + description: "Panel divisions with custom depth offsets", + tags: ["architecture", "facade"], + }, + { + id: "curve", + name: "DisplayCurveLength", + description: "Curve length labels with unit formatting", + tags: ["analysis"], + }, + { + id: "voronoi", + name: "VoronoiPattern", + description: "Surface subdivision with attractor points", + tags: ["geometry"], + }, +] as const; + +type DemoMode = "preview" | "copy" | "share"; + +/** + * Tiny in-page product mock: preview, or copy/share one sample clip. + * Not a full tour — one click, one confirmation. + */ +export function ClipLibraryDemo({ + mode = "preview", + featuredId = "facade", +}: { + mode?: DemoMode; + featuredId?: (typeof SAMPLE_CLIPS)[number]["id"]; +}) { + const [copiedId, setCopiedId] = useState(null); + const [sharedId, setSharedId] = useState(null); + + const handleCopy = (id: string) => { + setSharedId(null); + setCopiedId(id); + window.setTimeout(() => setCopiedId((cur) => (cur === id ? null : cur)), 1800); + }; + + const handleShare = (id: string) => { + setCopiedId(null); + setSharedId(id); + window.setTimeout(() => setSharedId((cur) => (cur === id ? null : cur)), 2800); + }; + + return ( +
+
    + {SAMPLE_CLIPS.map((clip) => { + const featured = clip.id === featuredId; + const justCopied = copiedId === clip.id; + const justShared = sharedId === clip.id; + + return ( +
  • +
    +
    +

    + {clip.name} +

    +

    + {clip.description} +

    +
    + {clip.tags.map((tag) => ( + + {tag} + + ))} +
    +
    + {featured && mode === "copy" && ( + + )} + {featured && mode === "share" && ( + + )} +
    +
  • + ); + })} +
+ + + {sharedId && ( + +
+ hopperclip.com/share?… +
+
+ )} +
+
+ ); +} diff --git a/src/app/components/landing/inspect-diff-visual.tsx b/src/app/components/landing/inspect-diff-visual.tsx new file mode 100644 index 0000000..d14b072 --- /dev/null +++ b/src/app/components/landing/inspect-diff-visual.tsx @@ -0,0 +1,232 @@ +import { motion } from "motion/react"; + +/** + * Quiet product frame for the LP: graph + soft diff highlight. + * Dark palette to match the landing page — not the real GH canvas look. + */ +export function InspectDiffVisual() { + return ( +
+ + + + + + + + + + {/* wires */} + + + + + {/* value nodes */} + + + + Number + + + 12.0 + + + + + + + Number + + + 4.0 + + + + {/* component — unmodified */} + + + + + + Divide Curve + + + + + + + {/* component — modified */} + + + + + + Extrude + + + + + + {/* panel — added */} + + + + Panel + + + ok + + + + +
+ + + added + + + ~ modified + + + − removed + +
+
+ ); +} diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 9259dc5..0993e17 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -5,24 +5,20 @@ import { redirect, useNavigate, } from "@tanstack/react-router"; -import { - ArrowRight, - GitCompareArrows, - MessageSquare, - MousePointerClick, - Plug, -} from "lucide-react"; +import { ArrowRight } from "lucide-react"; import { useEffect } from "react"; import { motion, useScroll, useTransform } from "motion/react"; import { AuthLoadingScreen } from "@/app/components/auth-loading-screen"; import Footer from "@/app/components/footer"; import Header from "@/app/components/header"; +import { ClipLibraryDemo } from "@/app/components/landing/clip-library-demo"; +import { InspectDiffVisual } from "@/app/components/landing/inspect-diff-visual"; import { Reveal } from "@/app/components/landing/reveal"; import { fetchClerkAuth } from "./__root"; export const Route = createFileRoute("/")({ head: () => ({ - meta: [{ title: "Hopper Clip — Grasshopper, sorted" }], + meta: [{ title: "Hopper Clip — Grasshopper snippets, ready" }], }), beforeLoad: async () => { const { userId } = await fetchClerkAuth(); @@ -81,220 +77,214 @@ function SignedInLandingContent() { ); } -const cases = [ - { - icon: MessageSquare, - eyebrow: "Share for review", - title: "Send one link instead of an attachment.", - body: "Drop a .gh, get a URL. Reviewers open it in any browser — no Rhino, no plugins, no setup. Comments stay in your existing tools.", - }, - { - icon: MousePointerClick, - eyebrow: "Inspect without Rhino", - title: "Read the whole graph at a glance.", - body: "Pan and zoom every component, wire, slider, and panel. Switch to a flat list for an inventory, or JSON for the raw structure.", - }, - { - icon: GitCompareArrows, - eyebrow: "Diff between versions", - title: "See what changed, not what's there.", - body: "Drop in a second file to compare two versions. Added, removed, modified, and rewired components are highlighted — wire by wire.", - }, -]; - function LandingPageContent() { return ( <> -
-
-
- - - for individuals & teams - - - -
-

- Grasshopper, reviewed. -

-

- Organize your own Grasshopper definitions. Share them with a - team to inspect, compare, and review across versions — without - anyone needing Rhino in front of them. -

-
-
- -
- - - -
-

- - Nothing to install — no Rhino, no plugins, no setup for whoever - opens the link. -

-
-
- -
- - - -
-
- -
- {cases.map((c, i) => ( - - - - ))} -
- -
- -
+
+ + + + +
); } -function BottomFade() { - const { scrollYProgress } = useScroll(); - const opacity = useTransform(scrollYProgress, [0, 0.75, 0.95], [1, 1, 0]); +function Hero() { return ( - +
+
+ +

+ Hopper Clip +

+
+ +

+ Your Grasshopper snippets, ready when you need them. +

+
+ +

+ Keep the definitions you paste over and over. Find them in seconds — + then share or inspect without hunting folders. +

+
+ +
+ + + +
+
+
+ + +
+

+ your library +

+ +
+
+
); } -function DuckerWebSection() { +function PersonalBeat() { return ( -
- - - +
+
+ +
+

+ For you first +

+

+ A personal clip library for the scripts you reuse. +

+

+ Paste or drop a definition, tag it, find it later. One click copies + it back into Grasshopper — no digging through project folders. +

+
+
+ +
+

+ try copy +

+ +
+
+
); } -function DuckerWebCard({ variant }: { variant: "aside" | "banner" }) { - if (variant === "aside") { - return ( -
-
- // also from hopper clip -
-

- Look at a definition without opening Rhino. -

-

- DuckerWeb is our - free browser viewer — drop a{" "} - - .gh - - , no account needed. -

- - Open DuckerWeb - - -

- free · runs locally in your browser -

+function ShareBeat() { + return ( +
+
+ +
+

+ try share +

+ +
+
+ +
+

+ Then the team +

+

+ Share a link. Skip the file ritual. +

+

+ No export, attach, download, open. Hit share, send the URL — your + teammate opens it in a browser. +

+
+
- ); - } +
+ ); +} +function InspectBeat() { return ( -
-
-
-
- // also from hopper clip -
-

- Look at a definition without opening Rhino. -

-

- DuckerWeb is - our free, browser-only viewer. Drop a{" "} - - .gh - - , paste GhXml, or compare two versions side-by-side — everything - runs locally in your browser, nothing uploaded, no account required. -

-
- No Rhino - No Grasshopper - No sign-up - Runs locally +
+
+ +
+

+ See without Rhino +

+

+ Inspect the graph. Diff what changed. +

+

+ DuckerWeb{" "} + reads the graph in your browser — pan, zoom, or diff two versions. + Built into Hopper Clip; free on its own. +

+ + Open DuckerWeb + +
-
-
- - Open DuckerWeb - - - - free · no account needed - -
+ + + +
-
+
); } -function Pill({ children }: { children: React.ReactNode }) { +function Closing() { return ( - - {children} - +
+ +
+

+ Keep. Share. See. +

+

+ Your snippets stay organized. Your team gets a link. Anyone can read + the script without opening Grasshopper. +

+
+ + + +

+ Just need a quick look?{" "} + + Open DuckerWeb + + + {" "} + — free viewer, no account. + +

+
+
+
+
); } -function CaseCard({ - icon: Icon, - eyebrow, - title, - body, -}: { - icon: React.ComponentType<{ className?: string }>; - eyebrow: string; - title: string; - body: string; -}) { +function BottomFade() { + const { scrollYProgress } = useScroll(); + const opacity = useTransform(scrollYProgress, [0, 0.75, 0.95], [1, 1, 0]); return ( -
- -
- {eyebrow} -
-

- {title} -

-

{body}

-
+ ); } From 0183fba8092d551ceeb6a386e295e05d45e43f59 Mon Sep 17 00:00:00 2001 From: tsoumdoa <8blondes.swerves@icloud.com> Date: Wed, 29 Jul 2026 19:37:49 +0800 Subject: [PATCH 2/3] Warm the LP with Hopper Card accents. Use green-300 Shared badge, tag chips, and matching CTAs so the landing feels closer to the product UI. Co-authored-by: Cursor --- .../components/landing/clip-library-demo.tsx | 66 +++++++------------ .../landing/inspect-diff-visual.tsx | 10 +-- src/routes/index.tsx | 26 ++++---- 3 files changed, 44 insertions(+), 58 deletions(-) diff --git a/src/app/components/landing/clip-library-demo.tsx b/src/app/components/landing/clip-library-demo.tsx index 69d10f5..9761c2a 100644 --- a/src/app/components/landing/clip-library-demo.tsx +++ b/src/app/components/landing/clip-library-demo.tsx @@ -1,5 +1,4 @@ import { AnimatePresence, motion } from "motion/react"; -import { Check, Copy, Share } from "lucide-react"; import { useState } from "react"; const SAMPLE_CLIPS = [ @@ -27,7 +26,7 @@ type DemoMode = "preview" | "copy" | "share"; /** * Tiny in-page product mock: preview, or copy/share one sample clip. - * Not a full tour — one click, one confirmation. + * Uses Hopper Card accents (green-300 Shared, tag chips) — not a full tour. */ export function ClipLibraryDemo({ mode = "preview", @@ -58,29 +57,38 @@ export function ClipLibraryDemo({ const featured = clip.id === featuredId; const justCopied = copiedId === clip.id; const justShared = sharedId === clip.id; + const showSharedBadge = + (mode === "preview" && clip.id === "facade") || justShared; return (
  • + {showSharedBadge && ( + + Shared + + )}
    -
    -

    +

    +

    {clip.name}

    -

    +

    {clip.description}

    {clip.tags.map((tag) => ( {tag} @@ -91,46 +99,22 @@ export function ClipLibraryDemo({ )} - {featured && mode === "share" && ( + {featured && mode === "share" && !justShared && ( )}
    @@ -148,7 +132,7 @@ export function ClipLibraryDemo({ transition={{ duration: 0.25, ease: [0.16, 1, 0.3, 1] }} className="absolute inset-x-0 -bottom-14 flex justify-center" > -
    +
    hopperclip.com/share?…
    diff --git a/src/app/components/landing/inspect-diff-visual.tsx b/src/app/components/landing/inspect-diff-visual.tsx index d14b072..55767c6 100644 --- a/src/app/components/landing/inspect-diff-visual.tsx +++ b/src/app/components/landing/inspect-diff-visual.tsx @@ -47,7 +47,7 @@ export function InspectDiffVisual() {
    - + + added - + ~ modified - + − removed
    diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 0993e17..f295d7f 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -98,7 +98,8 @@ function Hero() {

    - Hopper Clip + Hopper{" "} + Clip

    @@ -117,7 +118,7 @@ function Hero() {