diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2b0b2016..15d0d4d3 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -44,6 +44,8 @@ jobs: cd "$DEPLOY_DIR" git fetch origin + git checkout -- . + git clean -fd git checkout "$BRANCH" git reset --hard "origin/$BRANCH" diff --git a/website/app/components/WhatsAppWidget.tsx b/website/app/components/WhatsAppWidget.tsx index 24acf54b..62ad5562 100644 --- a/website/app/components/WhatsAppWidget.tsx +++ b/website/app/components/WhatsAppWidget.tsx @@ -72,19 +72,21 @@ export default function WhatsAppWidget() { const link = `https://wa.me/${number}?text=${encodeURIComponent(message)}`; useEffect(() => { - if (isOpen) { - setIsAnimating(false); - } + if (!isOpen) return; + const id = requestAnimationFrame(() => setIsAnimating(false)); + return () => cancelAnimationFrame(id); }, [isOpen]); // Enter: after panel mounts, trigger transition from opacity-0 to opacity-100 useEffect(() => { if (!isChatPanelOpen || isPanelClosing) return; - setIsPanelEntered(false); const id = requestAnimationFrame(() => { requestAnimationFrame(() => setIsPanelEntered(true)); }); - return () => cancelAnimationFrame(id); + return () => { + cancelAnimationFrame(id); + setIsPanelEntered(false); + }; }, [isChatPanelOpen, isPanelClosing]); // Close: run exit animation then unmount @@ -101,7 +103,7 @@ export default function WhatsAppWidget() { return () => { if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current); }; - }, [isPanelClosing]); + }, [isPanelClosing, closeWidget]); const handleToggle = () => { if (isOpen) { diff --git a/website/app/features/page.tsx b/website/app/features/page.tsx index 3eb6bb7c..07aa5136 100644 --- a/website/app/features/page.tsx +++ b/website/app/features/page.tsx @@ -1,6 +1,7 @@ "use client"; import React from "react"; import Image from "next/image"; +import { useWhatsAppWidget } from "../contexts/WhatsAppWidgetContext"; // Channel icons as SVG components const ChannelIcons = { @@ -193,18 +194,19 @@ function ChannelBadge({ status }: { status: string }) { } export default function FeaturesPage() { + const { openWidget } = useWhatsAppWidget(); + return (
{/* Header */}
-

- Built for capturing your journey +

+ Everything under the hood.

- Voice logging, multi-channel chat, AI-powered insights, and a dashboard that turns - your daily conversations into a visual story of your life. + Channels, integrations, and features that make LogLife work. No app needed.

@@ -212,8 +214,8 @@ export default function FeaturesPage() {
-

Channels

-

Journal from anywhere

+ Channels +

Journal from anywhere

@@ -255,15 +257,8 @@ export default function FeaturesPage() { {/* Integrations Section (replaces Skills) */}
-
-

Integrations

- - - - - Life telemetry - -
+ Integrations +

Life telemetry

Transparently collect the info you want documented on your journey

@@ -290,8 +285,8 @@ export default function FeaturesPage() { {/* Platform Features */}
-

Platform Features

-

Everything you need to capture, reflect, and grow

+ Platform +

Everything you need to capture, reflect, and grow

@@ -308,57 +303,59 @@ export default function FeaturesPage() {
- {/* What LogLife Adds */} + {/* Category Comparison Table */}
-
-
-

What LogLife Adds

-

On top of your existing chat and voice

-
- -
- {[ - { title: "No App Needed", description: "Primary interface is chat-native. E2E encryption built-in. No app to download.", icon: "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" }, - { title: "Privacy by Design", description: "Open-source + self-host/local-first by default. No access by design.", icon: "M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" }, - { title: "Deep Analytics", description: "D/W/M/Q/Y timeline + dashboard + memory graph as the product.", icon: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" }, - { title: "Non-Prescriptive", description: "A capture system, not a coach. Advice only when you ask.", icon: "M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" }, - { title: "Auto-Cancel on Inactivity", description: "Not using it? We\u2019ll cancel your subscription. We only want you paying if you\u2019re getting value.", icon: "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" }, - ].map((item, index) => ( -
-
- - - -
-

{item.title}

-

{item.description}

-
- ))} -
- - +
+ Where LogLife Fits +

+ One tool to replace four. +

+
+ +
+ + + + + + + + + + + {[ + { name: "Habit trackers", context: "x", action: "partial", habit: "check", highlight: false }, + { name: "Health wearables", context: "x", action: "partial", habit: "partial", highlight: false }, + { name: "Digital journaling", context: "check", action: "partial", habit: "partial", highlight: false }, + { name: "AI coaching", context: "check", action: "check", habit: "x", highlight: false }, + { name: "LogLife", context: "check", action: "check", habit: "check", highlight: true }, + ].map((row, index) => ( + + + + + + + ))} + +
CategoryLife contextAction-orientedHabit record
{row.name}
{/* Competitor Comparison Table */}
-

- How LogLife compares + How We Compare +

+ LogLife vs. the leading AI journaling apps

-

- See how LogLife stacks up against the leading AI journaling apps. -

@@ -411,6 +408,23 @@ export default function FeaturesPage() {
+ {/* CTA */} +
+
+

Ready to try it?

+

No card needed.

+ +
+
+
); diff --git a/website/app/globals.css b/website/app/globals.css index d28112b7..24370d3f 100644 --- a/website/app/globals.css +++ b/website/app/globals.css @@ -44,14 +44,16 @@ body { font-family: Arial, Helvetica, sans-serif; } -/* Animation keyframes for hero section */ +/* Dot-grid background overlay */ +.dot-grid { + background-image: radial-gradient(circle, rgba(148, 163, 184, 0.12) 1px, transparent 1px); + background-size: 24px 24px; +} + +/* Animation keyframes */ @keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } + from { opacity: 0; } + to { opacity: 1; } } @keyframes slide-up { @@ -89,3 +91,15 @@ body { animation: fade-in-on-scroll 0.6s ease-out; animation-fill-mode: both; } + +/* Scroll-triggered reveal */ +.reveal { + opacity: 0; + transform: translateY(24px); + transition: opacity 0.7s ease-out, transform 0.7s ease-out; +} + +.reveal.visible { + opacity: 1; + transform: translateY(0); +} diff --git a/website/app/hero/hero.tsx b/website/app/hero/hero.tsx index a978d867..bb063ec4 100644 --- a/website/app/hero/hero.tsx +++ b/website/app/hero/hero.tsx @@ -1,93 +1,122 @@ "use client"; -import React from "react"; +import React, { useState, useEffect, useRef } from "react"; +import { useWhatsAppWidget } from "../contexts/WhatsAppWidgetContext"; + +function useInView(threshold = 0.15) { + const ref = useRef(null); + const [visible, setVisible] = useState(false); + useEffect(() => { + const el = ref.current; + if (!el) return; + const obs = new IntersectionObserver( + ([entry]) => { if (entry.isIntersecting) { setVisible(true); obs.unobserve(el); } }, + { threshold } + ); + obs.observe(el); + return () => obs.disconnect(); + }, [threshold]); + return { ref, visible }; +} + +function SectionLabel({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} -// Modern Hero Section with Dark Theme function Hero() { + const { openWidget } = useWhatsAppWidget(); + return ( -
- +
- {/* Left Column - Text Content */}
-

- Your Life,{" "} - Captured. +

+ Effortless tracking,{" "} + + in chat. +

- +

- Nobody can tell you what they did last Tuesday. It's not a memory issue—it's a capture issue. LogLife turns your daily conversations into a timeline of your life. + Turns your chat into a life log with habit tracking and wearable + integration—AI listens, remembers, and surfaces patterns, + without coaching you.

- - - - {/* Platform badges */} -
+ +

+ No card needed. +

+ +
+ {[ + "Built for people who want to remember their life.", + "Built for people who don\u2019t have time for journaling apps.", + "Built for people who already talk to their phone.", + ].map((line) => ( +

{line}

+ ))}
- - {/* Right Column - Illustration */} + + {/* Chat mock */}
-
- {/* AI Assistant visualization */} -
- - {/* Floating cards */} -
-
- - - - Voice logging -
-
- -
-

"Ran 5K this morning"

-
- -
-
Weekly Highlights
-
3 of 5
+
+
+
+ LogLife + 8:15 PM
- -
-
-
- - - + +
+ {/* User message */} +
+
+
+ + + + Voice note +
+

+ I have a lot of work and I feel like eating some chips. +

- Patterns found
-
- - {/* Central AI icon */} -
-
- - - + + {/* LogLife response */} +
+
+

+ I hear you. Let's slow it down. The facts: less sleep last night, you're hungry and tired. + If you get the chips—how will you feel after? Whatever you decide, log so we learn from it! +

+
@@ -98,68 +127,50 @@ function Hero() { ); } -// Features Section with Cards -function Features() { - const features = [ +function TheProblem() { + const { ref, visible } = useInView(); + + const cards = [ { - icon: ( - - - - ), - title: "No app to download", - description: "LogLife lives where you already chat. WhatsApp, Telegram, or plain SMS. No new app, no new habit to build." + title: "Habits without context.", + description: "Habit trackers give you streaks and checkboxes, but no idea why you fell off. Rigid, binary, guilt-inducing.", + icon: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2", }, { - icon: ( - - - - ), - title: "Just talk", - description: "Send a voice note about your day. Or type a few words. AI handles the rest — transcription, tagging, organizing. Zero friction." + title: "Biometrics without meaning.", + description: "Your wearable says you slept 5 hours. But was it the late meeting, the coffee, or the argument? Numbers without context are just noise.", + icon: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z", }, { - icon: ( - - - - ), - title: "Your highlights, every week", - description: "Every day, week, month, quarter, and year, LogLife surfaces your 3–5 most important moments. A timeline of what actually mattered." + title: "Journals that gather dust.", + description: "You start strong, then life gets busy. Journaling apps feel like homework. By March, you\u2019ve stopped.", + icon: "M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253", }, - { - icon: ( - - - - ), - title: "Private by default", - description: "Open source. Self-hostable. We designed it so we can't see your data. Export everything, delete anytime, no questions asked." - } ]; return ( -
+
-

- A simpler way to know yourself + The Problem +

+ You're already tracking. It's just scattered everywhere.

-

Four ideas that make LogLife different

- -
- {features.map((feature, index) => ( -
+ {cards.map((card, i) => ( +
-
- {feature.icon} +
+ + +
-

{feature.title}

-

{feature.description}

+

{card.title}

+

{card.description}

))}
@@ -168,79 +179,180 @@ function Features() { ); } -// How It Works Section with Visual Steps +function Quote() { + const { ref, visible } = useInView(); + + return ( +
+
+

+ “You do not rise to the level of your goals. You fall to the level of your systems.” +

+

— James Clear

+

LogLife is the system.

+
+
+ ); +} + +function TheDifference() { + const { ref, visible } = useInView(); + const [activeTab, setActiveTab] = useState(0); + + const tabs = [ + { + label: "Habit Trackers", + left: { + title: "Habit Trackers", + text: "Good at streaks and records, but rigid, binary, guilt-inducing. No idea why you fell off or what conditions led to consistency.", + }, + right: { + title: "LogLife", + text: "Log habits naturally in chat. AI spots what leads to consistency vs drop-off\u2014no streak pressure. Easy & guilt-free.", + }, + }, + { + label: "Health Wearables", + left: { + title: "Health Wearables", + text: "Great at collecting biometrics\u2014sleep, activity, stress\u2014but missing what was actually happening in your life. Numbers without context.", + }, + right: { + title: "LogLife", + text: "Integrates wearable data and adds life context (meeting vs workout vs cooking) so those numbers become interpretable and useful.", + }, + }, + { + label: "Digital Journaling", + left: { + title: "Digital Journaling", + text: "Captures thoughts, but tends to be present-focused and disconnected from habits and health signals. Not very actionable.", + }, + right: { + title: "LogLife", + text: "Combines journaling with habit and wearable data. Summaries across past\u2013present\u2013future. Open-source, user-owned data.", + }, + }, + { + label: "AI Coaching", + left: { + title: "AI Coaching", + text: "Tries to guide your decisions and tell you what to do. Prescriptive by design.", + }, + right: { + title: "LogLife", + text: "A listener, not a coach. Low-friction capture, reliable memory, pattern recognition\u2014no unsolicited advice.", + }, + }, + ]; + + return ( +
+
+
+ The Difference +

+ One tool to replace four. +

+
+ + {/* Pill tabs */} +
+ {tabs.map((tab, i) => ( + + ))} +
+ + {/* Comparison card */} +
+
+
+ + + +

{tabs[activeTab].left.title}

+
+

{tabs[activeTab].left.text}

+
+ +
+
+ + + +

{tabs[activeTab].right.title}

+
+

{tabs[activeTab].right.text}

+
+
+
+
+ ); +} + function HowItWorks() { + const { ref, visible } = useInView(); + const steps = [ { number: "1", title: "Talk to LogLife", - description: "Send a voice note or text about your day through your favorite chat app. A short reflection at the end of the day is all you need.", - icon: ( - - - - ) + description: "Voice note or text. WhatsApp, Telegram, SMS, webchat. A short reflection is all you need.", + icon: "M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z", }, { number: "2", - title: "AI captures everything", - description: "LogLife transcribes, tags, and organizes. No buttons, no mood ratings, no structured input. Just your words.", - icon: ( - - - - ) + title: "AI captures and organizes", + description: "Transcription, tagging, highlighting. Your 3\u20135 highlights emerge daily, weekly, monthly, yearly.", + icon: "M13 10V3L4 14h7v7l9-11h-7z", }, { number: "3", - title: "Your highlights emerge", - description: "AI surfaces the 3\u20135 most important things from your day, week, month, quarter, and year.", - icon: ( - - - - ) - }, - { - number: "4", title: "Explore your dashboard", - description: "Visualize patterns, track habits, and see your progress over time. Chat is the input; the dashboard is the output.", - icon: ( - - - - ) + description: "Patterns, habits, progress. Chat is the input; the dashboard is the output.", + icon: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z", }, { - number: "5", + number: "4", title: "Ask anything", - description: "Query your life data in plain language: \u2018How many on-time bedtimes last week?\u2019 \u2018What were my top 3 achievements this month?\u2019", - icon: ( - - - - ) - } + description: "\u2018How did I sleep this week?\u2019 Your life, queryable in plain language.", + icon: "M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z", + }, ]; return ( -
+
-

How it works

-

Capture your life in five simple steps

+ How It Works +

+ Capture your life in four steps. +

- -
- {steps.map((step, index) => ( -
+ {steps.map((step, i) => ( +
- Step {step.number} -
{step.icon}
+ + {step.number} + + + +

{step.title}

{step.description}

@@ -252,106 +364,63 @@ function HowItWorks() { ); } -// Before/After Comparison Section -function BeforeAfter() { - return ( -
-
-
-

- The old way vs. LogLife -

-

Scattered notes and forgotten goals — or one place that actually gets you

-
- -
- {/* Before */} -
-

- Without LogLife -

-
    - {[ - "Can't remember what you did last Tuesday", - "Resolutions that don't survive January", - "Thoughts in 10 different apps, none connected", - "Voice memos you never listen to again", - "No idea if you're actually improving", - "Journaling that feels like homework" - ].map((item, i) => ( -
  • - - - - {item} -
  • - ))} -
-
- - {/* After */} -
-

- With LogLife -

-
    - {[ - "Your whole week, reconstructed from one conversation", - "Weekly highlights that keep your goals alive", - "Everything in one place, linked by AI", - "Voice notes transcribed, tagged, searchable", - "Charts and patterns that show real progress", - "Just talk. That's the whole journal." - ].map((item, i) => ( -
  • - - - - {item} -
  • - ))} -
-
-
-
-
- ); -} +function WhatYouGet() { + const { ref, visible } = useInView(); -// Quotes Section -function Quotes() { - const quotes = [ + const cards = [ + { + title: "Chat-native", + description: "Lives where you already chat. No new app, no new habit to build.", + icon: "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z", + }, + { + title: "Life context", + description: "The missing piece. Habits and biometrics mean something when connected to what was actually happening.", + icon: "M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1", + }, { - text: "You do not rise to the level of your goals. You fall to the level of your systems.", - author: "James Clear", - tagline: "LogLife is the system." + title: "A listener, not a coach", + description: "Captures with low friction, remembers reliably, surfaces patterns\u2014without pushing advice.", + icon: "M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z", }, { - text: "The unexamined life is not worth living.", - author: "Socrates", - tagline: "We think he'd have liked voice notes." + title: "Wearable integration", + description: "Oura, Garmin, and more. Your body data + your words = the full picture.", + icon: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z", }, { - text: "Every action you take is a vote for the type of person you wish to become.", - author: "James Clear", - tagline: "LogLife helps you count the votes." - } + title: "Open source", + description: "Your data, your rules. Export everything, delete anytime, self-host if you want.", + icon: "M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4", + }, ]; return ( -
-
-
- {quotes.map((quote, index) => ( -
+
+
+ What You Get +

+ No friction or advice. Just awareness. +

+

+ This is not a therapy app or a productivity coach. This is a listener that remembers everything. +

+
+ +
+ {cards.map((card, i) => ( +
-

- “{quote.text}” -

-

— {quote.author}

-

{quote.tagline}

+
+ + + +
+

{card.title}

+

{card.description}

))}
@@ -360,16 +429,83 @@ function Quotes() { ); } +function FinalCTA() { + const { ref, visible } = useInView(); + const { openWidget } = useWhatsAppWidget(); + + return ( +
+
+
+

+ You've never known yourself this well. +

+

+ Your words. Your data. Your patterns. AI that listens, remembers, and surfaces what matters. +

+ + {/* Provider pills */} +
+ + +
+ + +

No card needed.

+ + +
+
+
+ ); +} -// Main export component export default function LogLifeHero() { return (
- + + + - - + +
); } diff --git a/website/app/layout.tsx b/website/app/layout.tsx index fa4896fe..795a76c5 100644 --- a/website/app/layout.tsx +++ b/website/app/layout.tsx @@ -9,9 +9,9 @@ import WhatsAppWidget from "./components/WhatsAppWidget"; import { WebVitals } from "./components/WebVitals"; export const metadata: Metadata = { - title: "LogLife — Your Life, Captured", + title: "LogLife — Effortless Tracking, in Chat", description: - "Chat-native AI journaling powered by voice. Track your progress, find patterns, and build better habits through the chat apps you already use. No app to download. Open source. Your data stays yours.", + "Turns your chat into a life log with habit tracking and wearable integration. AI listens, remembers, and surfaces patterns — without coaching you. Open source. No app to download.", }; export default function RootLayout({ diff --git a/website/app/pricing/page.tsx b/website/app/pricing/page.tsx index 59765560..aed524fd 100644 --- a/website/app/pricing/page.tsx +++ b/website/app/pricing/page.tsx @@ -1,5 +1,6 @@ "use client"; -import React from "react"; +import React, { useState, useEffect, useRef, useCallback } from "react"; +import { useWhatsAppWidget } from "../contexts/WhatsAppWidgetContext"; function CheckIcon({ className = "w-5 h-5" }: { className?: string }) { return ( @@ -9,29 +10,217 @@ function CheckIcon({ className = "w-5 h-5" }: { className?: string }) { ); } +const SETUP_STEPS = [ + "Configure API keys", + "Provision server", + "Set up storage", + "Deploy application", + "Verify & monitor", +]; + +function AnimatedComparison() { + const ref = useRef(null); + const [started, setStarted] = useState(false); + const [selfHostedCompleted, setSelfHostedCompleted] = useState(new Array(SETUP_STEPS.length).fill(false)); + const [hostedCompleted, setHostedCompleted] = useState(new Array(SETUP_STEPS.length).fill(false)); + const [selfHostedTime, setSelfHostedTime] = useState(0); + const [hostedTime, setHostedTime] = useState(0); + const [selfHostedDone, setSelfHostedDone] = useState(false); + const [hostedDone, setHostedDone] = useState(false); + + useEffect(() => { + const el = ref.current; + if (!el) return; + const obs = new IntersectionObserver( + ([entry]) => { if (entry.isIntersecting) { setStarted(true); obs.unobserve(el); } }, + { threshold: 0.3 } + ); + obs.observe(el); + return () => obs.disconnect(); + }, []); + + const selfHostedTarget = 9000; + const hostedTarget = 300; + + const formatTime = useCallback((ms: number, isSelfHosted: boolean) => { + if (isSelfHosted) { + const totalMinutes = Math.floor((ms / selfHostedTarget) * 150); + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + return `${hours}h ${String(minutes).padStart(2, "0")}m`; + } + const totalSeconds = Math.floor((ms / hostedTarget) * 300); + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + return `${minutes}m ${String(seconds).padStart(2, "0")}s`; + }, []); + + useEffect(() => { + if (!started) return; + + const stepDelay = selfHostedTarget / SETUP_STEPS.length; + const timers: ReturnType[] = []; + + SETUP_STEPS.forEach((_, i) => { + timers.push(setTimeout(() => { + setSelfHostedCompleted(prev => { const next = [...prev]; next[i] = true; return next; }); + }, stepDelay * (i + 1))); + }); + + timers.push(setTimeout(() => setSelfHostedDone(true), selfHostedTarget)); + + return () => timers.forEach(clearTimeout); + }, [started, selfHostedTarget]); + + useEffect(() => { + if (!started) return; + + const timers: ReturnType[] = []; + SETUP_STEPS.forEach((_, i) => { + timers.push(setTimeout(() => { + setHostedCompleted(prev => { const next = [...prev]; next[i] = true; return next; }); + }, 80 * (i + 1))); + }); + + timers.push(setTimeout(() => setHostedDone(true), hostedTarget)); + + return () => timers.forEach(clearTimeout); + }, [started, hostedTarget]); + + useEffect(() => { + if (!started || selfHostedDone) return; + const interval = setInterval(() => { + setSelfHostedTime(prev => { + if (prev >= selfHostedTarget) { clearInterval(interval); return selfHostedTarget; } + return prev + 50; + }); + }, 50); + return () => clearInterval(interval); + }, [started, selfHostedDone, selfHostedTarget]); + + useEffect(() => { + if (!started || hostedDone) return; + const interval = setInterval(() => { + setHostedTime(prev => { + if (prev >= hostedTarget) { clearInterval(interval); return hostedTarget; } + return prev + 50; + }); + }, 50); + return () => clearInterval(interval); + }, [started, hostedDone, hostedTarget]); + + return ( +
+
+ The Difference +

+ Same product. Two paths. +

+
+ +
+ {/* Self-Hosted panel */} +
+
+
+ + + +
+
+

Self-Hosted

+
+
+ +
+ + {started ? formatTime(selfHostedTime, true) : "0h 00m"} + +
+ +
+ {SETUP_STEPS.map((step, i) => ( +
+
+ {selfHostedCompleted[i] && ( + + + + )} +
+ {step} +
+ ))} +
+
+ + {/* Hosted panel */} +
+
+
+ + + +
+
+

Hosted

+
+
+ +
+ + {started ? formatTime(hostedTime, false) : "0m 00s"} + +
+ +
+ {SETUP_STEPS.map((step, i) => ( +
+
+ {hostedCompleted[i] && ( + + + + )} +
+ {step} +
+ ))} +
+
+
+ +

+ Same product. Same features. Different setup time. +

+
+ ); +} + export default function PricingPage() { + const { openWidget } = useWhatsAppWidget(); + return (
- + {/* Header */}
-

- Pricing that adapts to you +

+ Start for free. Scale when you're ready.

- Start with our open-source self-hosted version, - or let LogLife handle the infrastructure for you. + Early access is free with no card needed. When we launch paid plans, self-hosting stays free forever.

- {/* 2-Column Pricing Grid */} -
+ {/* Pricing Cards */} +
- {/* Column 1: Self-Hosted (Free) */} + {/* Self-Hosted (Free) */}
- +
@@ -50,10 +239,8 @@ export default function PricingPage() { {[ "Open source", "BYO API keys", - "Self-hosted (Docker)", "Own your data 100%", "Community support", - "Clone from LogLife repo", ].map((item) => (
@@ -62,8 +249,8 @@ export default function PricingPage() { ))}
-
- {/* Column 2: Hosted ($19/mo) -- MOST POPULAR */} + {/* Hosted */}
- {/* Top gradient bar */} - - {/* Billing Principles Section */} -
+ + {/* Animated Time Comparison */} + + + {/* How We Bill */} +
-

- How we bill -

-

+ How We Bill +

Transparent, fair, and designed to keep you in control. -

+

- +
{[ { - icon: ( - - - - ), - title: "No API Markup", - description: "We don\u2019t resell API keys or add margin on model usage. You pay providers directly at their rates." + icon: "M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4", + title: "Subscription Covers", + description: "Web hosting, development, monitoring, alerts, audit logs, and support. All infrastructure costs included.", }, { - icon: ( - - - - ), + icon: "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2", title: "Itemized Usage", - description: "Monthly receipts show usage by provider\u2014tokens, minutes, calls\u2014plus our subscription fee. No surprises." - }, - { - icon: ( - - - - ), - title: "Keys & Data Control", - description: "BYO keys stay in your environment. For Hosted, keys can be yours or ours\u2014rotate anytime." - }, - { - icon: ( - - - - ), - title: "No Content Logging", - description: "We don\u2019t store message content in logs by default. Opt in explicitly only if you need debugging." + description: "Monthly receipts show usage by provider\u2014tokens, minutes, calls\u2014plus our subscription fee. No surprises.", }, { - icon: ( - - - - ), + icon: "M6 18L18 6M6 6l12 12", title: "Clear Cancellation", - description: "Cancel any time. No lock-in, no hidden fees. All data automatically deleted 30 days after cancellation." + description: "Cancel any time. No lock-in, no hidden fees. All data automatically deleted 30 days after cancellation.", }, { - icon: ( - - - - ), + icon: "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9", title: "Inactivity Nudges", - description: "No usage for a while? We\u2019ll warn you, then auto-cancel the subscription if the service isn\u2019t being used. We only want you paying if you\u2019re getting value." - }, - { - icon: ( - - - - ), - title: "Always Your Data", - description: "Host anywhere. Export or delete with one click. We can\u2019t see or change your message content." - }, - { - icon: ( - - - - ), - title: "Subscription Covers", - description: "Web hosting, development, monitoring, alerts, audit logs, and support. All infrastructure costs included." + description: "No usage for a while? We\u2019ll warn you, then auto-cancel the subscription if the service isn\u2019t being used.", }, ].map((item, index) => ( -
- {item.icon} + + +

{item.title}

{item.description}

@@ -233,16 +367,17 @@ export default function PricingPage() {
{/* Privacy Section */} -
+
-

- Your privacy, by design + Privacy +

+ Your data, your rules.

- Different deployment options, same commitment to your privacy. + Different deployment options, same commitment to transparency.

- +
{/* Private Deploy */}
@@ -254,7 +389,7 @@ export default function PricingPage() {

Private Deploy

-

Self-hosted or your infrastructure

+

Self-hosted on your infrastructure

@@ -293,12 +428,12 @@ export default function PricingPage() {

- We don't sell data. We design it so we don't have access to it by design. However, in theory, since we operate the server, we could access it. But we don't. We only care about usage metadata. + We don't sell or train models on your data. We do not access private data—but since we operate the server, theoretical access exists. For zero-access guarantees, self-host.

- Users can export/delete/reset anytime. All data is automatically deleted 30 days after a subscription is canceled. It's possible to delete all data without deleting the account (called a brain reset). + Users can export/delete/reset data anytime. All data is deleted 30 days after a subscription is canceled.

@@ -306,33 +441,33 @@ export default function PricingPage() { - Goal: “no access by design” & no model training on user data + No access by design & no model training on user data
{/* CTA Footer */} -
+
-

Ready to start capturing your life?

-

We handle the infrastructure. You focus on what matters.

- Ready to try it? +

No card needed.

+
{/* Trust Indicators */}

- Powered by LogLife · Open Source · Your Data, Your Control + Powered by LogLife · Open Source · Your Data, Your Rules

diff --git a/website/package.json b/website/package.json index 8e66639b..d15adad6 100644 --- a/website/package.json +++ b/website/package.json @@ -27,6 +27,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.5.4", + "eslint-plugin-react-hooks": "^7.0.1", "tailwindcss": "^4", "typescript": "^5" } diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml index c5a30cb2..5fc9f167 100644 --- a/website/pnpm-lock.yaml +++ b/website/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: '@clerk/nextjs': specifier: ^6.37.2 - version: 6.37.2(next@16.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 6.37.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@vapi-ai/client-sdk-react': specifier: ^0.1.1 version: 0.1.1(@types/react@19.2.11)(@vapi-ai/web@2.5.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -19,7 +19,7 @@ importers: version: 2.5.2 next: specifier: ^16.1.6 - version: 16.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 16.1.6(@babel/core@7.29.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: specifier: 19.1.0 version: 19.1.0 @@ -54,6 +54,9 @@ importers: eslint-config-next: specifier: 15.5.4 version: 15.5.4(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.2(jiti@2.6.1)) tailwindcss: specifier: ^4 version: 4.1.18 @@ -67,10 +70,77 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/runtime@7.28.6': resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + '@clerk/backend@2.30.0': resolution: {integrity: sha512-4DJBisLmA6pAtvTLJb1EshWCVfQRpuIxSO+pIkhGOC2DvNc85EkMq2G3a/jPOzUFzZPMmIJp99sKo7JF9MoOcg==} engines: {node: '>=18.17.0'} @@ -875,6 +945,11 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -929,6 +1004,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1004,6 +1082,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + electron-to-chromium@1.5.286: + resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -1043,6 +1124,10 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1115,6 +1200,12 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} engines: {node: '>=4'} @@ -1238,6 +1329,10 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1312,6 +1407,12 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} @@ -1482,6 +1583,11 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -1495,6 +1601,11 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -1601,6 +1712,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1750,6 +1864,9 @@ packages: sass: optional: true + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2156,6 +2273,12 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -2198,10 +2321,22 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -2209,8 +2344,108 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + '@babel/runtime@7.28.6': {} + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@clerk/backend@2.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@clerk/shared': 3.44.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -2228,13 +2463,13 @@ snapshots: react-dom: 19.1.0(react@19.1.0) tslib: 2.8.1 - '@clerk/nextjs@6.37.2(next@16.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@clerk/nextjs@6.37.2(next@16.1.6(@babel/core@7.29.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@clerk/backend': 2.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@clerk/clerk-react': 5.60.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@clerk/shared': 3.44.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@clerk/types': 4.101.14(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - next: 16.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 16.1.6(@babel/core@7.29.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) server-only: 0.0.1 @@ -2958,6 +3193,14 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001767 + electron-to-chromium: 1.5.286 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -3006,6 +3249,8 @@ snapshots: concat-map@0.0.1: {} + convert-source-map@2.0.0: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3080,6 +3325,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + electron-to-chromium@1.5.286: {} + emoji-regex@9.2.2: {} enhanced-resolve@5.19.0: @@ -3188,6 +3435,8 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} eslint-config-next@15.5.4(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): @@ -3296,6 +3545,17 @@ snapshots: dependencies: eslint: 9.39.2(jiti@2.6.1) + eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)): + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + eslint: 9.39.2(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@2.6.1)): dependencies: array-includes: 3.1.9 @@ -3455,6 +3715,8 @@ snapshots: generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3550,6 +3812,12 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + html-url-attributes@3.0.1: {} ignore@5.3.2: {} @@ -3719,6 +3987,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -3729,6 +3999,8 @@ snapshots: dependencies: minimist: 1.2.8 + json5@2.2.3: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -3812,6 +4084,10 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4065,7 +4341,7 @@ snapshots: natural-compare@1.4.0: {} - next@16.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next@16.1.6(@babel/core@7.29.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@next/env': 16.1.6 '@swc/helpers': 0.5.15 @@ -4074,7 +4350,7 @@ snapshots: postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.6(react@19.1.0) + styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.1.0) optionalDependencies: '@next/swc-darwin-arm64': 16.1.6 '@next/swc-darwin-x64': 16.1.6 @@ -4089,6 +4365,8 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-releases@2.0.27: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -4499,10 +4777,12 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-jsx@5.1.6(react@19.1.0): + styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.1.0): dependencies: client-only: 0.0.1 react: 19.1.0 + optionalDependencies: + '@babel/core': 7.29.0 supports-color@7.2.0: dependencies: @@ -4651,6 +4931,12 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -4718,6 +5004,14 @@ snapshots: word-wrap@1.2.5: {} + yallist@3.1.1: {} + yocto-queue@0.1.0: {} + zod-validation-error@4.0.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} + zwitch@2.0.4: {}