From 3ff4be1a6526d284be431be4ea9e284befa7ecfb Mon Sep 17 00:00:00 2001 From: Biswas Khatiwada Date: Fri, 17 Jul 2026 17:32:04 -0400 Subject: [PATCH 1/2] Polish Parcel marketing site and add docs section. Improves nav glass effect, mint mobile CTA, full /docs guides, and landing section polish for production deploy. Co-authored-by: Cursor --- Website/README.md | 34 ++- Website/app/docs/capture/page.tsx | 70 ++++++ Website/app/docs/editor/page.tsx | 71 ++++++ Website/app/docs/getting-started/page.tsx | 81 +++++++ Website/app/docs/layout.tsx | 42 ++++ Website/app/docs/page.tsx | 103 +++++++++ Website/app/docs/privacy/page.tsx | 64 ++++++ Website/app/docs/recording/page.tsx | 54 +++++ Website/app/docs/shortcuts/page.tsx | 65 ++++++ Website/app/docs/supabase/page.tsx | 57 +++++ Website/app/globals.css | 109 ++++++++- Website/app/layout.tsx | 23 +- Website/app/page.tsx | 10 +- Website/components/annotation-tools-strip.tsx | 32 +++ Website/components/docs/docs-shell.tsx | 164 ++++++++++++++ Website/components/editor-preview.tsx | 3 +- Website/components/faq-section.tsx | 71 ++++-- Website/components/feature-bento.tsx | 45 ++-- Website/components/guides-grid.tsx | 102 +++++++-- Website/components/home-hero.tsx | 213 +++++++++--------- Website/components/install-section.tsx | 107 ++++++--- Website/components/mobile-download-bar.tsx | 36 +++ Website/components/primary-button.tsx | 44 ++++ Website/components/principles-section.tsx | 69 ++++++ Website/components/privacy-section.tsx | 30 ++- Website/components/section-header.tsx | 19 +- Website/components/shortcuts-table.tsx | 49 ++-- Website/components/showcase-section.tsx | 58 +++++ Website/components/site-footer.tsx | 157 +++++++++---- Website/components/site-nav.tsx | 153 ++++++++----- Website/components/stats-band.tsx | 50 ++++ Website/components/use-cases-section.tsx | 109 +++++++++ Website/components/workflow-steps.tsx | 34 ++- Website/lib/docs.ts | 82 +++++++ Website/lib/nav.ts | 9 + Website/lib/site.ts | 88 +++++++- Website/lib/theme.ts | 33 +++ 37 files changed, 2206 insertions(+), 334 deletions(-) create mode 100644 Website/app/docs/capture/page.tsx create mode 100644 Website/app/docs/editor/page.tsx create mode 100644 Website/app/docs/getting-started/page.tsx create mode 100644 Website/app/docs/layout.tsx create mode 100644 Website/app/docs/page.tsx create mode 100644 Website/app/docs/privacy/page.tsx create mode 100644 Website/app/docs/recording/page.tsx create mode 100644 Website/app/docs/shortcuts/page.tsx create mode 100644 Website/app/docs/supabase/page.tsx create mode 100644 Website/components/annotation-tools-strip.tsx create mode 100644 Website/components/docs/docs-shell.tsx create mode 100644 Website/components/mobile-download-bar.tsx create mode 100644 Website/components/primary-button.tsx create mode 100644 Website/components/principles-section.tsx create mode 100644 Website/components/showcase-section.tsx create mode 100644 Website/components/stats-band.tsx create mode 100644 Website/components/use-cases-section.tsx create mode 100644 Website/lib/docs.ts create mode 100644 Website/lib/nav.ts create mode 100644 Website/lib/theme.ts diff --git a/Website/README.md b/Website/README.md index be82f36..4e699bf 100644 --- a/Website/README.md +++ b/Website/README.md @@ -13,7 +13,38 @@ Same stack as [Parable](https://github.com/bswxyz/parable): Next.js App Router, | Fonts | Geist Sans, Geist Mono, Instrument Serif | | Theme | next-themes (dark default) | -Visual components (`DitherAurora`, `ShimmerButton`, `VelocityMarquee`) are adapted from the Parable registry. +## Brand colors + +Edit **`lib/theme.ts`** — one file controls accent, secondary, aurora gradient, and backgrounds site-wide: + +```ts +export const theme = { + accent: "#5ee4b5", // mint — CTAs, glow + secondary: "#8b5cf6", // violet — icons, links + tertiary: "#ec4899", // fuchsia — gradient stops + ink: "#070708", // hero/footer background + aurora: ["#8b5cf6", "#5ee4b5", "#ec4899"], +}; +``` + +Variables are injected on `` in `app/layout.tsx` as `--brand-*` CSS custom properties. + + +## Pages + +| Route | Purpose | +|-------|---------| +| `/` | Marketing landing — hero, features, workflow, install, FAQ | +| `/docs` | User documentation index | +| `/docs/getting-started` | Install & first Capture | +| `/docs/capture` | Freeze-then-select, scroll stitch | +| `/docs/editor` | Tools, Layers, Beautify, export | +| `/docs/recording` | MP4, trim, GIF | +| `/docs/shortcuts` | Keyboard reference | +| `/docs/privacy` | On-device Vision, permissions | +| `/docs/supabase` | Optional upload setup | + +Contributor architecture docs remain in the repo [`docs/`](../docs/) folder. ## Develop @@ -47,6 +78,7 @@ Static output lands in `out/` — deployed to [parcel.parable.dev](https://parce | `public/downloads/Parcel.zip` | macOS app download | | `public/appcast.xml` | Sparkle update feed | | `public/og.svg` | Open Graph image | +| `public/assets/hero-marketing.png` | Higgsfield-generated marketing visual | | `public/favicon.svg` | Favicon | Release builds copy `build/Parcel.zip` → `public/downloads/Parcel.zip` via `Scripts/release.sh`. diff --git a/Website/app/docs/capture/page.tsx b/Website/app/docs/capture/page.tsx new file mode 100644 index 0000000..ad36442 --- /dev/null +++ b/Website/app/docs/capture/page.tsx @@ -0,0 +1,70 @@ +import { DocsBreadcrumb, DocsCallout } from "@/components/docs/docs-shell"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Capture & Selection", + description: "Freeze-then-select Capture, window snap, and scroll stitching.", +}; + +export default function CaptureDocPage() { + return ( + <> + +

Capture & Selection

+

+ Parcel freezes every display with ScreenCaptureKit, then lets you choose + a Selection from those pixels — region drag, window snap, + or scroll stitch. +

+ +

Freeze-then-select

+

+ On hotkey, Parcel captures full-resolution images of all displays and + shows them in the Overlay — a borderless full-screen + panel per display. You work on frozen pixels, not a live view. +

+
    +
  • + Region — click and drag any rectangle +
  • +
  • + Window snap — hover a highlighted window and click, or + press Tab to cycle targets +
  • +
  • + CancelEsc dismisses the Overlay with no + Editor +
  • +
+ +

Scroll Capture

+

+ For tall content that does not fit on screen, choose{" "} + Scroll Capture from the menu bar while the Overlay is + active: +

+
    +
  1. Drag a tall Selection region in the Overlay.
  2. +
  3. Scroll the source content and add frames from the menu bar.
  4. +
  5. + Parcel stitches frames with on-device Vision registration — live preview + as frames stack. +
  6. +
  7. Finish to open the stitched Capture in the Editor.
  8. +
+ +

Multi-display

+

+ One Overlay panel appears per display. Your Selection is cropped from the + display where you release — pixels stay at native resolution and retina + scale. +

+ + + ScreenCaptureKit requires Screen Recording TCC. If Capture fails after an + update, re-check System Settings and quit/reopen Parcel. Use a stable + Developer ID signing identity so permission persists across updates. + + + ); +} diff --git a/Website/app/docs/editor/page.tsx b/Website/app/docs/editor/page.tsx new file mode 100644 index 0000000..12caf98 --- /dev/null +++ b/Website/app/docs/editor/page.tsx @@ -0,0 +1,71 @@ +import { DocsBreadcrumb, DocsCallout } from "@/components/docs/docs-shell"; +import { annotationTools } from "@/lib/site"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Editor & Annotations", + description: "Fourteen Tools, Layer stack, Beautify, Adjustments, and export.", +}; + +export default function EditorDocPage() { + return ( + <> + +

Editor & Annotations

+

+ The Editor is where you mark up a Capture, tune output, and copy or save. + One render pipeline drives display and export —{" "} + on screen = saved. +

+ +

Fourteen Tools

+

Select a Tool from the toolbar to create Annotations:

+
    + {annotationTools.map((tool) => ( +
  • {tool}
  • + ))} +
+ +

Click-to-edit

+

+ Switch to Select, click any Annotation, and use the style + bar to change stroke, color, arrow style, censor mode, or fill. Move and + resize with handles. The Layer stack supports reorder via the layers + panel. +

+ +

Censor modes

+
    +
  • + Blur / Pixelate / Solid — standard redaction +
  • +
  • + Erase — samples surrounding Capture pixels +
  • +
  • + Auto-redact — regex PII and detected faces via on-device + Vision +
  • +
+ +

Beautify & Adjustments

+

+ Beautify wraps your Canvas with gradient backgrounds, + window chrome, padding, radius, and shadow — plus saved brand kits. + Adjustments run a Core Image chain on the base Capture + (exposure, contrast, saturation, and more). +

+ + Adjustments and Beautify are document-level — outside the undo stack. Use + each panel's Reset to revert. + + +

Export formats

+

+ Copy or save as PNG, JPEG, HEIC, or TIFF. Format and quality live in the + output panel. Re-open any past Capture from history (⌘⇧H) with + annotations and settings intact. +

+ + ); +} diff --git a/Website/app/docs/getting-started/page.tsx b/Website/app/docs/getting-started/page.tsx new file mode 100644 index 0000000..0e779ae --- /dev/null +++ b/Website/app/docs/getting-started/page.tsx @@ -0,0 +1,81 @@ +import { DocsBreadcrumb, DocsCallout } from "@/components/docs/docs-shell"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Quick start", + description: "Install Parcel, grant Screen Recording, and take your first Capture.", +}; + +export default function GettingStartedPage() { + return ( + <> + +

Quick start

+

+ Parcel lives in your menu bar. After install, grant Screen Recording once, + quit and reopen — then press ⌘⇧2 to Capture. +

+ +

1. Download & install

+

+ Download the signed Release build from the{" "} + direct download or build from source + with Xcode. Drag Parcel.app to Applications. +

+ + Once published to a tap:{" "} + brew install --cask parcel + + +

2. First launch

+
    +
  1. Complete the welcome onboarding flow.
  2. +
  3. + When prompted, open System Settings → Privacy & Security → + Screen Recording and enable Parcel. +
  4. +
  5. + Quit and reopen Parcel — TCC permissions only apply + after restart. +
  6. +
+ +

3. Take your first Capture

+
    +
  1. + Press ⌘⇧2 (configurable in Preferences) from any app. +
  2. +
  3. + Every display freezes. Drag a region, press Tab to snap to + a window, or choose Scroll Capture from the menu bar. +
  4. +
  5. + Release to open the Editor with your Selection cropped + at full resolution. +
  6. +
  7. + Mark up, then ⌘C to copy or ⌘S to save. What you + see on screen is exactly what exports. +
  8. +
+ + + Parcel uses Carbon global hotkeys — not CGEventTap — so you + never need Accessibility access for Capture. + + +

Next steps

+ + + ); +} diff --git a/Website/app/docs/layout.tsx b/Website/app/docs/layout.tsx new file mode 100644 index 0000000..c6d56b4 --- /dev/null +++ b/Website/app/docs/layout.tsx @@ -0,0 +1,42 @@ +import type { Metadata } from "next"; +import { DocsShell } from "@/components/docs/docs-shell"; + +export const metadata: Metadata = { + title: "Documentation", + description: + "User guides for Parcel — install, Capture, annotate, record, and upload on macOS.", +}; + +export default function DocsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+
+
+

+ Parcel · User guides +

+

+ Everything you need to{" "} + + Capture + {" "} + with confidence. +

+

+ Install, permissions, workflows, and integrations — written for + daily macOS use, not just contributors. +

+
+
+ {children} +
+ ); +} diff --git a/Website/app/docs/page.tsx b/Website/app/docs/page.tsx new file mode 100644 index 0000000..18c59e1 --- /dev/null +++ b/Website/app/docs/page.tsx @@ -0,0 +1,103 @@ +import Link from "next/link"; +import { ArrowRight } from "lucide-react"; +import { docHref, docSections } from "@/lib/docs"; + +export default function DocsIndexPage() { + return ( + <> +

+ Parcel is a native macOS Capture studio from the{" "} + Parable ecosystem. These guides cover + what end users need — install, permissions, Capture workflows, and + optional upload. +

+ +

Start here

+
+ +

+ Recommended +

+

Quick start

+

+ Download, grant Screen Recording, and take your first Capture in + under two minutes. +

+ + Read guide + + + +

+ Reference +

+

Keyboard shortcuts

+

+ Global hotkey, Overlay controls, and Editor commands in one place. +

+ + View shortcuts + + +
+ +

All guides

+
+ {docSections.map((section) => ( +
+

+ {section.title} +

+
    + {section.pages.map((page) => ( +
  • + +
    +

    {page.title}

    +

    + {page.description} +

    +
    + + +
  • + ))} +
+
+ ))} +
+ +

Contributor docs

+

+ Architecture, parity checklists, and agent guides live in the GitHub + repository: +

+ + + ); +} diff --git a/Website/app/docs/privacy/page.tsx b/Website/app/docs/privacy/page.tsx new file mode 100644 index 0000000..45bb4de --- /dev/null +++ b/Website/app/docs/privacy/page.tsx @@ -0,0 +1,64 @@ +import { DocsBreadcrumb, DocsCallout } from "@/components/docs/docs-shell"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Privacy & permissions", + description: "On-device Vision, sandbox entitlements, and what leaves your Mac.", +}; + +export default function PrivacyDocPage() { + return ( + <> + +

Privacy & permissions

+

+ Parcel is private by default. OCR, face detection, translation, and regex + redaction use Apple on-device frameworks only — no cloud AI, no telemetry. +

+ +

What stays local

+
    +
  • All Capture and recording pixels
  • +
  • Vision OCR, QR, face finding, and translation (macOS 15+/26+)
  • +
  • Regex PII inspection and auto-redact suggestions
  • +
  • Capture history documents on disk
  • +
+ +

Permissions Parcel uses

+
+
+
+

Screen Recording

+

+ Required — ScreenCaptureKit for Capture and recording +

+
+ + Required + +
+
+
+

Accessibility

+

+ Not used — Carbon hotkeys avoid event taps +

+
+ Not needed +
+
+ +

Sandbox

+

+ Release builds are sandboxed with user-selected file read/write for Save + panels. Upload uses HTTPS to your configured Supabase project only when + you trigger it. +

+ + + Parcel never sends Captures to a network LLM or third-party AI service. + Any future "AI" feature must be provably on-device. + + + ); +} diff --git a/Website/app/docs/recording/page.tsx b/Website/app/docs/recording/page.tsx new file mode 100644 index 0000000..1c1f12a --- /dev/null +++ b/Website/app/docs/recording/page.tsx @@ -0,0 +1,54 @@ +import { DocsBreadcrumb, DocsCallout } from "@/components/docs/docs-shell"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Screen recording", + description: "Region recording, trim editor, GIF export, and audio options.", +}; + +export default function RecordingDocPage() { + return ( + <> + +

Screen recording

+

+ Record a screen region as MP4 with system audio, optional microphone on + macOS 15+, click highlights, trim, and local GIF export. +

+ +

Start a recording

+
    +
  1. + Choose Record Region from the menu bar (or the + equivalent menu item). +
  2. +
  3. Drag a Selection in the Overlay — same freeze-then-select model.
  4. +
  5. + Recording runs at 30, 60, or 120 fps with system audio included. +
  6. +
  7. Stop from the menu bar control or hotkey.
  8. +
+ +

Trim & export

+

+ After stopping, the trim window opens. Set in/out points, then save MP4 + or export a lightweight GIF for sharing. +

+ +

macOS 15+ extras

+
    +
  • Microphone capture alongside system audio
  • +
  • Click highlights during recording
  • +
+ + + On macOS 13, an AVFoundation fallback path exists but is written-but-untested + on the primary dev machine. See the repo's{" "} + + macOS 13 QA notes + + . + + + ); +} diff --git a/Website/app/docs/shortcuts/page.tsx b/Website/app/docs/shortcuts/page.tsx new file mode 100644 index 0000000..b2da83e --- /dev/null +++ b/Website/app/docs/shortcuts/page.tsx @@ -0,0 +1,65 @@ +import { DocsBreadcrumb, DocsCallout } from "@/components/docs/docs-shell"; +import { shortcuts } from "@/lib/site"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Keyboard shortcuts", + description: "Global hotkeys, Overlay controls, and Editor commands for Parcel.", +}; + +export default function ShortcutsDocPage() { + return ( + <> + +

Keyboard shortcuts

+

+ Default bindings below. Change the Capture hotkey in{" "} + Preferences → Hotkey. +

+ +
+ + + + + + + + + {shortcuts.map((row) => ( + + + + + ))} + +
+ Keys + + Action +
+ {row.keys} + {row.action}
+
+ +

While drawing Annotations

+
    +
  • + Shift — constrain proportions (square, circle, straight line) +
  • +
  • + Space — reposition shape while drawing +
  • +
  • + Esc — cancel current draw or exit utility Tool +
  • +
+ + + Undo (⌘Z) covers Annotation content only — + create, delete, move, resize, and restyle. Adjustments and Beautify are + document-level settings with panel Resets. + + + ); +} diff --git a/Website/app/docs/supabase/page.tsx b/Website/app/docs/supabase/page.tsx new file mode 100644 index 0000000..c48f27e --- /dev/null +++ b/Website/app/docs/supabase/page.tsx @@ -0,0 +1,57 @@ +import { DocsBreadcrumb, DocsCallout } from "@/components/docs/docs-shell"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Supabase upload", + description: "Configure optional Storage upload from the Editor.", +}; + +export default function SupabaseDocPage() { + return ( + <> + +

Supabase upload

+

+ Upload is optional. Configure your own Supabase Storage bucket once — + then upload from the Editor and copy a public link. +

+ +

Setup

+
    +
  1. + Create a Supabase project and a public Storage bucket. +
  2. +
  3. + Open Preferences → Upload in Parcel. +
  4. +
  5. + Paste your project URL, anon key, bucket name, and optional custom + public base URL. +
  6. +
  7. Save — Parcel stores credentials locally on your Mac.
  8. +
+ +

Upload from the Editor

+

+ After marking up a Capture, choose Upload from the Editor toolbar. Parcel + writes to your bucket via the Supabase Storage REST API and copies the + public URL to the clipboard. +

+ + + Use a bucket policy appropriate for your content. The anon key is stored + in UserDefaults — treat it like any client-side secret with RLS policies + that match your threat model. + + +

Troubleshooting

+
    +
  • Verify the bucket is public or your base URL resolves correctly.
  • +
  • Check object size limits on your Supabase plan.
  • +
  • + Ensure network access is allowed — sandboxed builds use standard HTTPS. +
  • +
+ + ); +} diff --git a/Website/app/globals.css b/Website/app/globals.css index 2e43d2a..6759fd8 100644 --- a/Website/app/globals.css +++ b/Website/app/globals.css @@ -33,6 +33,8 @@ --pb-fuchsia: #ec4899; --pb-mint: #5ee4b5; --pb-gold: #f5c451; + --pb-ink: #0a0a0b; + --pb-surface: oklch(0.18 0 0); --pb-ease-out: cubic-bezier(0.22, 1, 0.36, 1); --pb-ease-snap: cubic-bezier(0.16, 1, 0.3, 1); @@ -63,13 +65,19 @@ --secondary: oklch(0.24 0 0); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.24 0 0); - --muted-foreground: oklch(0.708 0 0); + --muted-foreground: oklch(0.78 0 0); --accent: oklch(0.24 0 0); --accent-foreground: oklch(0.985 0 0); - --border: oklch(1 0 0 / 10%); + --border: oklch(1 0 0 / 14%); --ring: oklch(0.556 0 0); } +/* Readable copy on dark aurora / ink surfaces */ +.dark-surface { + --surface-muted: oklch(0.82 0 0); + --surface-subtle: oklch(0.68 0 0); +} + @layer base { * { @apply border-border outline-ring/50; @@ -80,6 +88,27 @@ body { @apply bg-background text-foreground; } + ::selection { + background: color-mix(in srgb, var(--brand-secondary) 35%, transparent); + color: inherit; + } +} + +/* Focus visible for keyboard users */ +:focus-visible { + outline: 2px solid color-mix(in srgb, var(--brand-accent) 60%, transparent); + outline-offset: 2px; +} + +/* Subtle film grain — Parable Aether-style texture */ +body::before { + content: ""; + pointer-events: none; + position: fixed; + inset: 0; + z-index: 9999; + opacity: 0.028; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); } @media (prefers-reduced-motion: reduce) { @@ -94,3 +123,79 @@ transition-duration: 0.01ms !important; } } + +/* Parcel accent gradient — edit --brand-* in lib/theme.ts */ +.pb-gradient-text { + background: linear-gradient( + 135deg, + var(--brand-secondary) 0%, + var(--brand-tertiary) 45%, + var(--brand-accent) 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +/* Legibility on busy dither backgrounds */ +.pb-gradient-glow { + filter: drop-shadow(0 2px 16px rgba(0, 0, 0, 0.45)); +} + +/* Documentation prose */ +.docs-prose { + @apply max-w-3xl text-[15px] leading-relaxed text-muted-foreground; +} + +.docs-prose h1 { + @apply scroll-mt-24 text-3xl font-semibold tracking-tight text-foreground md:text-4xl; +} + +.docs-prose h2 { + @apply mt-10 scroll-mt-24 text-xl font-semibold tracking-tight text-foreground; +} + +.docs-prose h3 { + @apply mt-6 text-base font-semibold text-foreground; +} + +.docs-prose p { + @apply mt-4; +} + +.docs-prose .docs-lead { + @apply text-lg leading-relaxed text-foreground/85; +} + +.docs-prose a { + @apply text-violet-400 underline-offset-4 transition-colors hover:text-violet-300 hover:underline; +} + +.docs-prose ul, +.docs-prose ol { + @apply mt-4 space-y-2 pl-5; +} + +.docs-prose ul { + @apply list-disc; +} + +.docs-prose ol { + @apply list-decimal; +} + +.docs-prose li { + @apply pl-1; +} + +.docs-prose strong { + @apply font-medium text-foreground; +} + +.docs-prose code:not(.docs-kbd) { + @apply rounded-md bg-muted px-1.5 py-0.5 font-mono text-[13px] text-foreground/90; +} + +.docs-kbd { + @apply inline-flex rounded-md border border-border bg-muted/60 px-2 py-0.5 font-mono text-xs text-foreground; +} diff --git a/Website/app/layout.tsx b/Website/app/layout.tsx index e9b6793..f07e35d 100644 --- a/Website/app/layout.tsx +++ b/Website/app/layout.tsx @@ -1,9 +1,12 @@ +import type { CSSProperties } from "react"; import type { Metadata } from "next"; import { Geist, Geist_Mono, Instrument_Serif } from "next/font/google"; import "./globals.css"; import { ThemeProvider } from "@/components/theme-provider"; import { SiteNav } from "@/components/site-nav"; import { SiteFooter } from "@/components/site-footer"; +import { MobileDownloadBar } from "@/components/mobile-download-bar"; +import { theme } from "@/lib/theme"; import { SITE_URL } from "@/lib/site"; const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] }); @@ -18,6 +21,16 @@ const instrumentSerif = Instrument_Serif({ style: "italic", }); +const brandStyle = { + "--brand-accent": theme.accent, + "--brand-secondary": theme.secondary, + "--brand-tertiary": theme.tertiary, + "--brand-gold": theme.gold, + "--brand-ink": theme.ink, + "--brand-surface": theme.surface, + "--brand-elevated": theme.elevated, +} as CSSProperties; + export const metadata: Metadata = { title: { default: "Parcel — native macOS Capture studio", @@ -54,8 +67,15 @@ export default function RootLayout({ lang="en" suppressHydrationWarning className={`${geistSans.variable} ${geistMono.variable} ${instrumentSerif.variable} h-full`} + style={brandStyle} > - + + + Skip to content +
{children}
+
diff --git a/Website/app/page.tsx b/Website/app/page.tsx index 5ce5a00..dbc5721 100644 --- a/Website/app/page.tsx +++ b/Website/app/page.tsx @@ -1,6 +1,10 @@ import type { Metadata } from "next"; import { HomeHero } from "@/components/home-hero"; +import { StatsBand } from "@/components/stats-band"; +import { ShowcaseSection } from "@/components/showcase-section"; import { FeatureBento } from "@/components/feature-bento"; +import { UseCasesSection } from "@/components/use-cases-section"; +import { PrinciplesSection } from "@/components/principles-section"; import { WorkflowSteps } from "@/components/workflow-steps"; import { PrivacySection } from "@/components/privacy-section"; import { ShortcutsTable } from "@/components/shortcuts-table"; @@ -14,9 +18,13 @@ export const metadata: Metadata = { export default function HomePage() { return ( -
+
+ + + + diff --git a/Website/components/annotation-tools-strip.tsx b/Website/components/annotation-tools-strip.tsx new file mode 100644 index 0000000..a9120b7 --- /dev/null +++ b/Website/components/annotation-tools-strip.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { motion, useReducedMotion } from "motion/react"; +import { annotationTools } from "@/lib/site"; + +export function AnnotationToolsStrip() { + const reduce = useReducedMotion(); + + return ( + +

+ Fourteen Tools · One toolbar +

+
+ {annotationTools.map((tool) => ( + + {tool} + + ))} +
+
+ ); +} diff --git a/Website/components/docs/docs-shell.tsx b/Website/components/docs/docs-shell.tsx new file mode 100644 index 0000000..2875972 --- /dev/null +++ b/Website/components/docs/docs-shell.tsx @@ -0,0 +1,164 @@ +"use client"; + +import * as React from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { BookOpen, ChevronRight, Menu, X } from "lucide-react"; +import { docHref, docSections } from "@/lib/docs"; +import { cn } from "@/lib/utils"; + +export function DocsShell({ children }: { children: React.ReactNode }) { + const pathname = usePathname(); + const [mobileOpen, setMobileOpen] = React.useState(false); + + const isActive = (slug: string) => { + const href = docHref(slug); + return pathname === href; + }; + + return ( +
+ + +
+
+

+ Docs +

+ +
+ + {mobileOpen && ( + + )} + +
{children}
+
+
+ ); +} + +export function DocsBreadcrumb({ + section, + title, +}: { + section: string; + title: string; +}) { + return ( +
+ + Docs + + + + {section} + + + {title} +
+ ); +} + +export function DocsCallout({ + variant = "note", + title, + children, +}: { + variant?: "note" | "tip" | "warning"; + title?: string; + children: React.ReactNode; +}) { + const styles = { + note: "border-violet-500/25 bg-violet-500/5", + tip: "border-[var(--pb-mint)]/30 bg-[var(--pb-mint)]/5", + warning: "border-amber-500/30 bg-amber-500/5", + }; + + return ( + + ); +} diff --git a/Website/components/editor-preview.tsx b/Website/components/editor-preview.tsx index de2fb03..acc7c27 100644 --- a/Website/components/editor-preview.tsx +++ b/Website/components/editor-preview.tsx @@ -89,9 +89,10 @@ const TOOL_CHIPS = [ { label: "Select", icon: MousePointer2 }, { label: "Arrow", icon: MoveUpRight }, { label: "Rect", icon: Square }, + { label: "Ellipse", icon: Square }, { label: "Text", icon: Type }, { label: "Censor", icon: EyeOff }, - { label: "Beautify", icon: Sparkles }, + { label: "Spotlight", icon: Sparkles }, { label: "Loupe", icon: Search }, ] as const; diff --git a/Website/components/faq-section.tsx b/Website/components/faq-section.tsx index c6dd634..67afdc8 100644 --- a/Website/components/faq-section.tsx +++ b/Website/components/faq-section.tsx @@ -1,11 +1,13 @@ "use client"; import * as React from "react"; +import { Apple } from "lucide-react"; +import { DitherAurora } from "@/components/parable/dither-aurora"; +import { PrimaryButton } from "@/components/primary-button"; +import { DOWNLOAD_URL, faqs, theme } from "@/lib/site"; import { ChevronDown } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { SectionHeader } from "@/components/section-header"; -import { ShimmerButton } from "@/components/parable/shimmer-button"; -import { DOWNLOAD_URL, faqs } from "@/lib/site"; import { cn } from "@/lib/utils"; export function FaqSection() { @@ -13,10 +15,10 @@ export function FaqSection() { const reduce = useReducedMotion(); return ( -
+
-
    +
      {faqs.map((item, i) => { const isOpen = open === i; return ( @@ -25,9 +27,9 @@ export function FaqSection() { type="button" onClick={() => setOpen(isOpen ? null : i)} aria-expanded={isOpen} - className="flex w-full items-center justify-between gap-4 px-5 py-4 text-left transition-colors hover:bg-muted/30" + className="flex w-full items-center justify-between gap-4 px-5 py-4 text-left transition-colors hover:bg-muted/40" > - {item.q} + {item.q} -

      +

      {item.a}

      @@ -61,26 +63,47 @@ export function FaqSection() { export function CtaBanner() { return ( -
      +
      + + + + + {/* Radial scrim — keeps text readable (Linear/Perplexity pattern) */}
      -
      -

      - Ready to Capture? -

      -

      - Free, open source, and built for daily macOS work — private by default. -

      - - Download Parcel - + +
      +
      +

      + Free · Open source · MIT +

      +

      + Ready to{" "} + Capture? +

      +

      + Private by default. Built for daily macOS work — from the Parable + ecosystem. +

      +
      + + + Download Parcel for macOS + +

      + Compatible with Apple Silicon and Intel · macOS 13+ +

      +
      +
      ); diff --git a/Website/components/feature-bento.tsx b/Website/components/feature-bento.tsx index fce1b41..8526e31 100644 --- a/Website/components/feature-bento.tsx +++ b/Website/components/feature-bento.tsx @@ -5,37 +5,40 @@ import { motion, useReducedMotion } from "motion/react"; import { Camera, CloudUpload, + Cpu, Eye, History, + MousePointer2, PenTool, + ScanText, ScrollText, + Shield, Sparkles, Video, type LucideIcon, } from "lucide-react"; import { cn } from "@/lib/utils"; +import { AnnotationToolsStrip } from "@/components/annotation-tools-strip"; import { SectionHeader } from "@/components/section-header"; import { features } from "@/lib/site"; const ICONS: Record = { camera: Camera, + mouse: MousePointer2, pen: PenTool, scroll: ScrollText, video: Video, + shield: Shield, sparkles: Sparkles, + scan: ScanText, eye: Eye, history: History, cloud: CloudUpload, + cpu: Cpu, }; -function hexToRgba(hex: string, alpha: number) { - const n = Number.parseInt(hex.replace("#", ""), 16); - return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`; -} - export function FeatureBento() { const reduce = useReducedMotion(); - const accent = "#8b5cf6"; const onGlowMove = React.useCallback( (e: React.PointerEvent) => { @@ -48,12 +51,23 @@ export function FeatureBento() { ); return ( -
      -
      +
      +
      +
      + Everything you need.{" "} + Nothing you don't. + + } + subtitle="One menu bar app for Capture, annotation, censoring, beautify, recording, scroll-stitching, and optional upload — all on your Mac." /> +
        {Icon && (
        - +
        @@ -113,7 +128,7 @@ export function FeatureBento() {

        {item.title}

        -

        +

        {item.body}

        diff --git a/Website/components/guides-grid.tsx b/Website/components/guides-grid.tsx index 8505873..acdf15e 100644 --- a/Website/components/guides-grid.tsx +++ b/Website/components/guides-grid.tsx @@ -1,9 +1,52 @@ "use client"; -import { BookOpen } from "lucide-react"; +import Link from "next/link"; +import { ArrowUpRight, BookOpen } from "lucide-react"; import { motion, useReducedMotion } from "motion/react"; import { SectionHeader } from "@/components/section-header"; -import { guides } from "@/lib/site"; + +const guideLinks = [ + { + title: "Redact sensitive text locally", + body: "Inspect Capture → Recognize Text → Censor Detected Sensitive Text. Regex runs on-device via Vision.", + href: "/docs/editor", + }, + { + title: "Scroll a long page", + body: "In the Overlay choose Scroll Capture, drag a tall region, then add frames from the menu bar and finish.", + href: "/docs/capture", + }, + { + title: "Click-to-edit any annotation", + body: "Switch to Select, click an Annotation, then use the style bar to change stroke, color, arrow style, or censor mode.", + href: "/docs/editor", + }, + { + title: "Record with system audio", + body: "Choose Record Region from the menu bar, drag a Selection in the Overlay, then save. MP4 includes system audio.", + href: "/docs/recording", + }, + { + title: "Translate text on-device", + body: "Inspect Capture → Recognize Text → Translate On-Device. Uses Apple Translation on macOS 26+ — nothing leaves your Mac.", + href: "/docs/privacy", + }, + { + title: "Save a brand kit", + body: "Enable Beautify, tune padding and gradient, then save a named kit from the Beautify panel.", + href: "/docs/editor", + }, + { + title: "Upload to Supabase", + body: "Paste project URL, anon key, and bucket in Preferences. Upload from the Editor copies the link.", + href: "/docs/supabase", + }, + { + title: "Export a recording as GIF", + body: "After stopping a recording, open the trim window and export GIF for lightweight sharing.", + href: "/docs/recording", + }, +] as const; export function GuidesGrid() { const reduce = useReducedMotion(); @@ -11,31 +54,54 @@ export function GuidesGrid() { return (
        - -
        - {guides.map((guide, i) => ( - + + Recipes for daily{" "} + Capture work. + + } + subtitle="Each card links to the full guide in the docs." + /> + + All documentation + +
        +
        + {guideLinks.map((guide, i) => ( + - -

        {guide.title}

        -

        - {guide.body} -

        - + +
        + + +
        +

        + {guide.title} +

        +

        + {guide.body} +

        + +
        ))}
        diff --git a/Website/components/home-hero.tsx b/Website/components/home-hero.tsx index 1cd884a..7bb9272 100644 --- a/Website/components/home-hero.tsx +++ b/Website/components/home-hero.tsx @@ -1,14 +1,14 @@ "use client"; import Link from "next/link"; -import { ArrowRight } from "lucide-react"; +import { Apple, ArrowRight } from "lucide-react"; import { motion, useReducedMotion } from "motion/react"; import { DitherAurora } from "@/components/parable/dither-aurora"; -import { ShimmerButton } from "@/components/parable/shimmer-button"; import { VelocityMarquee } from "@/components/parable/velocity-marquee"; -import { DeviceFrame, EditorPreview } from "@/components/editor-preview"; +import { PrimaryButton } from "@/components/primary-button"; +import { ParcelMark } from "@/components/site-nav"; import { Badge } from "@/components/section-header"; -import { DOWNLOAD_URL, logos, stats } from "@/lib/site"; +import { DOWNLOAD_URL, GITHUB_URL, logos, theme } from "@/lib/site"; const marqueeItems = logos.map((name) => ( @@ -18,15 +18,15 @@ const marqueeItems = logos.map((name) => ( )); const fadeUp = { - hidden: { opacity: 0, y: 18 }, + hidden: { opacity: 0, y: 20 }, show: (i: number) => ({ opacity: 1, y: 0, transition: { type: "spring" as const, stiffness: 220, - damping: 26, - delay: i * 0.08, + damping: 28, + delay: i * 0.07, }, }), }; @@ -35,126 +35,125 @@ export function HomeHero() { const reduce = useReducedMotion(); return ( -
        +
        + +
        -
        -
        - - + + + - Native macOS · MIT · Open source - - - The{" "} - - Capture - {" "} - studio Apple forgot to{" "} - - ship - - . - - - Freeze your screen, annotate with fourteen tools, censor with local - Vision, beautify for ship-ready output, record, scroll-capture, and - upload when you choose — from the Parable ecosystem. - - - macOS 13+ - On-device only - MIT License - - - - Download for macOS - - - See how it works - - - - {stats.map((s) => ( -
        -
        - {s.value} -
        -
        {s.label}
        -
        - ))} -
        + + v1.0 + + MIT licensed · Quick start guide + +
        + + + + + + Native macOS · From Parable + + + Capture, mark up, and{" "} + + ship + + . + + + The menu bar studio for freeze-then-select Capture, fourteen Tools, + on-device Vision, Beautify, and recording — no cloud AI, ever. + + + On-device only + macOS 13+ + MIT License + -
        - - - + + + Download for macOS + + + + Explore features + -
        + + Requires macOS 13.0+ · Apple Silicon & Intel ·{" "} + + Source on GitHub + + +
        -
        +
        diff --git a/Website/components/install-section.tsx b/Website/components/install-section.tsx index 2d5bc5f..77bbc92 100644 --- a/Website/components/install-section.tsx +++ b/Website/components/install-section.tsx @@ -1,55 +1,106 @@ "use client"; -import { Download } from "lucide-react"; +import Link from "next/link"; +import { Check, Download, Terminal } from "lucide-react"; +import { motion, useReducedMotion } from "motion/react"; import { CopyButton } from "@/components/copy-button"; +import { PrimaryButton } from "@/components/primary-button"; import { SectionHeader } from "@/components/section-header"; -import { ShimmerButton } from "@/components/parable/shimmer-button"; import { DOWNLOAD_URL, HOMEBREW_CMD } from "@/lib/site"; +const requirements = [ + "macOS 13.0 or later", + "Screen Recording permission", + "Quit & reopen after first grant", + "No Accessibility permission needed", +] as const; + export function InstallSection() { + const reduce = useReducedMotion(); + return ( -
        -
        +
        +
        +
        -
        -
        -
        +
        + +

        + Requirements +

        +
          + {requirements.map((req) => ( +
        • + + {req} +
        • + ))} +
        + + Step-by-step guide → + +
        + + +

        Direct download

        -

        - Signed Release builds from GitHub Actions. Unzip and drag Parcel to - Applications. +

        + Open the zip and drag Parcel to Applications. Sparkle handles + updates.

        - + Download Parcel.zip - -
        + + -
        -

        + +

        + +
        +

        Homebrew

        -

        Install via cask

        -

        - Once the cask is published to a tap, install with Homebrew. +

        Install via cask

        +

        + Once published to a tap, one command installs Parcel.

        -
        - +
        + {HOMEBREW_CMD}
        -
        +
        diff --git a/Website/components/mobile-download-bar.tsx b/Website/components/mobile-download-bar.tsx new file mode 100644 index 0000000..72b1c48 --- /dev/null +++ b/Website/components/mobile-download-bar.tsx @@ -0,0 +1,36 @@ +"use client"; + +import * as React from "react"; +import Link from "next/link"; +import { Apple } from "lucide-react"; +import { DOWNLOAD_URL } from "@/lib/site"; + +/** Sticky download bar on mobile after scrolling past hero — Windsurf/Affinity pattern */ +export function MobileDownloadBar() { + const [visible, setVisible] = React.useState(false); + + React.useEffect(() => { + const onScroll = () => setVisible(window.scrollY > 480); + onScroll(); + window.addEventListener("scroll", onScroll, { passive: true }); + return () => window.removeEventListener("scroll", onScroll); + }, []); + + if (!visible) return null; + + return ( +
        + + + Download for macOS + +
        + ); +} diff --git a/Website/components/primary-button.tsx b/Website/components/primary-button.tsx new file mode 100644 index 0000000..e2b834d --- /dev/null +++ b/Website/components/primary-button.tsx @@ -0,0 +1,44 @@ +import type { ReactNode } from "react"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; + +type PrimaryButtonProps = { + href: string; + children: ReactNode; + className?: string; + variant?: "accent" | "white"; + external?: boolean; +}; + +/** High-contrast CTA — Discord/Linear pattern (readable on dark aurora) */ +export function PrimaryButton({ + href, + children, + className, + variant = "accent", + external, +}: PrimaryButtonProps) { + const styles = + variant === "accent" + ? "bg-[var(--brand-accent)] text-[var(--brand-ink)] shadow-[0_0_40px_color-mix(in_srgb,var(--brand-accent)_35%,transparent)] hover:brightness-110" + : "bg-white text-[var(--brand-ink)] shadow-[0_8px_32px_rgba(0,0,0,0.35)] hover:bg-zinc-100"; + + const Comp = external ? "a" : Link; + const extra = external + ? { target: "_blank", rel: "noopener noreferrer" } + : {}; + + return ( + + {children} + + ); +} diff --git a/Website/components/principles-section.tsx b/Website/components/principles-section.tsx new file mode 100644 index 0000000..9c5f4bb --- /dev/null +++ b/Website/components/principles-section.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { Cpu, Keyboard, Shield, Zap } from "lucide-react"; +import { motion, useReducedMotion } from "motion/react"; +import { SectionHeader } from "@/components/section-header"; + +const principles = [ + { + icon: Zap, + title: "Built for speed", + body: "ScreenCaptureKit freeze, Carbon hotkeys, and a native SwiftUI shell — no Electron, no web views.", + }, + { + icon: Keyboard, + title: "Keyboard-first", + body: "Configurable global hotkey, Overlay shortcuts, and Editor commands designed for daily muscle memory.", + }, + { + icon: Shield, + title: "Private by default", + body: "OCR, faces, translation, and regex redaction stay on your Mac. Zero cloud AI calls.", + }, + { + icon: Cpu, + title: "One render pipeline", + body: "Adjustments, censors, annotations, and Beautify compose once — display and export match exactly.", + }, +] as const; + +export function PrinciplesSection() { + const reduce = useReducedMotion(); + + return ( +
        +
        + +
        + {principles.map((item, i) => { + const Icon = item.icon; + return ( + +
        + +
        +

        + {item.title} +

        +

        + {item.body} +

        +
        + ); + })} +
        +
        +
        + ); +} diff --git a/Website/components/privacy-section.tsx b/Website/components/privacy-section.tsx index b212899..3612df6 100644 --- a/Website/components/privacy-section.tsx +++ b/Website/components/privacy-section.tsx @@ -1,5 +1,6 @@ "use client"; +import Link from "next/link"; import { Shield } from "lucide-react"; import { motion, useReducedMotion } from "motion/react"; import { SectionHeader } from "@/components/section-header"; @@ -8,7 +9,7 @@ export function PrivacySection() { const reduce = useReducedMotion(); return ( -
        +
        + Your pixels stay on your{" "} + Mac. + + } subtitle="OCR, face detection, translation, and regex redaction run through Apple on-device frameworks. No cloud AI, no telemetry, no surprise uploads." />
        • - + Vision and Core ML process Captures locally
        • - + Supabase upload is optional and user-configured
        • - + Sandboxed Release builds with minimal entitlements
        + + Privacy documentation → +
        Screen Recording

        Required for Capture

        - - Granted + + Required
        -
        +

        Accessibility

        -

        Not required

        +

        Not used by Parcel

        diff --git a/Website/components/section-header.tsx b/Website/components/section-header.tsx index d9fe7ec..c730360 100644 --- a/Website/components/section-header.tsx +++ b/Website/components/section-header.tsx @@ -1,3 +1,4 @@ +import * as React from "react"; import { cn } from "@/lib/utils"; export function SectionHeader({ @@ -5,14 +6,22 @@ export function SectionHeader({ title, subtitle, className, + align = "left", }: { kicker?: string; - title: string; + title: React.ReactNode; subtitle?: string; className?: string; + align?: "left" | "center"; }) { return ( -
        +
        {kicker && (

        {kicker} @@ -22,7 +31,7 @@ export function SectionHeader({ {title} {subtitle && ( -

        +

        {subtitle}

        )} @@ -42,9 +51,9 @@ export function Badge({ className={cn( "inline-flex items-center rounded-full border px-2.5 py-0.5 font-mono text-[11px] uppercase tracking-wide", variant === "violet" && - "border-violet-500/30 bg-violet-500/10 text-violet-300", + "border-[var(--brand-secondary)]/30 bg-[var(--brand-secondary)]/10 text-violet-300", variant === "mint" && - "border-emerald-500/30 bg-emerald-500/10 text-emerald-300", + "border-[var(--brand-accent)]/30 bg-[var(--brand-accent)]/10 text-[var(--brand-accent)]", variant === "default" && "border-border bg-muted/50 text-muted-foreground" )} > diff --git a/Website/components/shortcuts-table.tsx b/Website/components/shortcuts-table.tsx index 6eae4e7..0ee51bd 100644 --- a/Website/components/shortcuts-table.tsx +++ b/Website/components/shortcuts-table.tsx @@ -1,5 +1,7 @@ "use client"; +import Link from "next/link"; +import { ArrowRight } from "lucide-react"; import { motion, useReducedMotion } from "motion/react"; import { SectionHeader } from "@/components/section-header"; import { shortcuts } from "@/lib/site"; @@ -8,39 +10,60 @@ export function ShortcutsTable() { const reduce = useReducedMotion(); return ( -
        -
        - +
        +
        +
        +
        + + Keyboard-first{" "} + design. + + } + subtitle="Every action has a shortcut. The global Capture hotkey is configurable in Preferences." + /> + + Full reference + +
        - - + - {shortcuts.map((row) => ( - + - diff --git a/Website/components/showcase-section.tsx b/Website/components/showcase-section.tsx new file mode 100644 index 0000000..acb3e51 --- /dev/null +++ b/Website/components/showcase-section.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { motion, useReducedMotion } from "motion/react"; +import { DeviceFrame, EditorPreview } from "@/components/editor-preview"; +import { SectionHeader } from "@/components/section-header"; + +export function ShowcaseSection() { + const reduce = useReducedMotion(); + + return ( +
        +
        +
        + + A Capture studio you'd expect from a{" "} + professional tool. + + } + subtitle="Fourteen annotation Tools, on-device Vision, Beautify, and one render pipeline — what you see is exactly what copies or saves." + /> + +
        + + + +
        + {["On screen = saved", "Capture-point coords", "Sandboxed"].map( + (tag) => ( + + {tag} + + ) + )} +
        + +
        +
        + ); +} diff --git a/Website/components/site-footer.tsx b/Website/components/site-footer.tsx index b1fd671..0d223a0 100644 --- a/Website/components/site-footer.tsx +++ b/Website/components/site-footer.tsx @@ -1,46 +1,127 @@ import Link from "next/link"; import { DOWNLOAD_URL, GITHUB_URL } from "@/lib/site"; +import { ParcelMark } from "@/components/site-nav"; + +const footerLinks = { + Product: [ + { label: "Features", href: "/#features" }, + { label: "Workflow", href: "/#workflow" }, + { label: "Install", href: "/#install" }, + { label: "Download", href: DOWNLOAD_URL, download: true }, + ], + Docs: [ + { label: "Documentation", href: "/docs" }, + { label: "Quick start", href: "/docs/getting-started" }, + { label: "Shortcuts", href: "/docs/shortcuts" }, + { label: "Privacy", href: "/docs/privacy" }, + ], + Ecosystem: [ + { label: "GitHub", href: GITHUB_URL, external: true }, + { label: "Parable UI", href: "https://github.com/bswxyz/parable", external: true }, + { label: "Parable.dev", href: "https://parable.dev", external: true }, + ], +} as const; export function SiteFooter() { return ( -
        -
        -

        - Parcel — the - native macOS Capture studio from{" "} - - Parable - - . MIT licensed. -

        - +
        +
        + +
        +
        +
        + + + Parcel + +

        + The native macOS Capture studio from{" "} + + Parable + + . MIT licensed — no cloud AI, ever. +

        +
        + + + + + + All systems operational + +
        +
        + + +
        + +
        +

        + Parcel +

        +
        + +
        +

        © {new Date().getFullYear()} Parable · MIT License

        +

        + SwiftUI · ScreenCaptureKit · Next.js +

        +
        ); diff --git a/Website/components/site-nav.tsx b/Website/components/site-nav.tsx index 779969d..6bd9311 100644 --- a/Website/components/site-nav.tsx +++ b/Website/components/site-nav.tsx @@ -2,11 +2,12 @@ import * as React from "react"; import Link from "next/link"; +import { usePathname } from "next/navigation"; import { Menu, Moon, Sun, X } from "lucide-react"; import { useTheme } from "next-themes"; import { cn } from "@/lib/utils"; import { DOWNLOAD_URL, GITHUB_URL } from "@/lib/site"; -import { ShimmerButton } from "@/components/parable/shimmer-button"; +import { navLinks } from "@/lib/nav"; function GithubMark() { return ( @@ -16,64 +17,94 @@ function GithubMark() { ); } -const LINKS = [ - { href: "#features", label: "Features" }, - { href: "#workflow", label: "Workflow" }, - { href: "#privacy", label: "Privacy" }, - { href: "#guides", label: "Guides" }, - { href: "#faq", label: "FAQ" }, -]; +const LINKS = navLinks; + +function ParcelMark({ className }: { className?: string }) { + return ( + + + P + + ); +} export function SiteNav() { + const pathname = usePathname(); const [mobileOpen, setMobileOpen] = React.useState(false); + const [scrolled, setScrolled] = React.useState(false); + const isHome = pathname === "/"; + + React.useEffect(() => { + const onScroll = () => setScrolled(window.scrollY > 12); + onScroll(); + window.addEventListener("scroll", onScroll, { passive: true }); + return () => window.removeEventListener("scroll", onScroll); + }, []); return ( -
        -
        - - - P - +
        +
        + + Parcel -
        +
        Shortcut + Action
        - + {row.keys} + {row.action}