From 9fa8045c2623151a9343db05950732c4e60cae7b Mon Sep 17 00:00:00 2001 From: Timo Knuth Date: Mon, 19 Jan 2026 22:24:25 +0100 Subject: [PATCH] Your commit message --- backend/package-lock.json | 8 -- backend/src/db/schema.sql | 12 ++ backend/src/index.ts | 2 + backend/src/routes/waitlist.ts | 95 ++++++++++++++ frontend/app/globals.css | 43 +++++-- frontend/app/page.tsx | 60 +++------ .../landing/CompetitorDemoVisual.tsx | 81 ++++++------ .../components/landing/LandingSections.tsx | 118 +++++++----------- frontend/components/landing/LiveStatsBar.tsx | 2 +- .../components/landing/PolicyDemoVisual.tsx | 30 ++--- frontend/components/landing/SEODemoVisual.tsx | 23 ++-- frontend/components/landing/WaitlistForm.tsx | 39 ++++-- frontend/components/ui/ThemeToggle.tsx | 70 +++++++++++ frontend/package-lock.json | 29 +++-- frontend/tailwind.config.ts | 1 + 15 files changed, 392 insertions(+), 221 deletions(-) create mode 100644 backend/src/routes/waitlist.ts create mode 100644 frontend/components/ui/ThemeToggle.tsx diff --git a/backend/package-lock.json b/backend/package-lock.json index f417745..2e13c95 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -762,7 +762,6 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -3438,7 +3437,6 @@ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -3621,7 +3619,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4021,7 +4018,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4924,7 +4920,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -7575,7 +7570,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.17.1.tgz", "integrity": "sha512-EIR+jXdYNSMOrpRp7g6WgQr7SaZNZfS7IzZIO0oTNEeibq956JxeD15t3Jk3zZH0KH8DmOIx38qJfQenoE8bXQ==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.10.0", "pg-pool": "^3.11.0", @@ -8707,7 +8701,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8998,7 +8991,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/backend/src/db/schema.sql b/backend/src/db/schema.sql index d05dd13..e2ba602 100644 --- a/backend/src/db/schema.sql +++ b/backend/src/db/schema.sql @@ -121,3 +121,15 @@ CREATE TABLE IF NOT EXISTS webhook_logs ( CREATE INDEX IF NOT EXISTS idx_webhook_logs_user_id ON webhook_logs(user_id); CREATE INDEX IF NOT EXISTS idx_webhook_logs_monitor_id ON webhook_logs(monitor_id); CREATE INDEX IF NOT EXISTS idx_webhook_logs_created_at ON webhook_logs(created_at); + +-- Waitlist leads table (pre-launch signups) +CREATE TABLE IF NOT EXISTS waitlist_leads ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email VARCHAR(255) UNIQUE NOT NULL, + source VARCHAR(50) DEFAULT 'landing_page', + referrer TEXT, + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_waitlist_leads_email ON waitlist_leads(email); +CREATE INDEX IF NOT EXISTS idx_waitlist_leads_created_at ON waitlist_leads(created_at); diff --git a/backend/src/index.ts b/backend/src/index.ts index 0917948..391d19c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -41,11 +41,13 @@ app.get('/health', async (_req, res) => { }); import testRoutes from './routes/test'; +import waitlistRoutes from './routes/waitlist'; // Routes app.use('/api/auth', authLimiter, authRoutes); app.use('/api/monitors', authMiddleware, monitorRoutes); app.use('/api/settings', authMiddleware, settingsRoutes); +app.use('/api/waitlist', waitlistRoutes); // Public route - no auth required app.use('/test', testRoutes); // 404 handler diff --git a/backend/src/routes/waitlist.ts b/backend/src/routes/waitlist.ts new file mode 100644 index 0000000..88ddb4f --- /dev/null +++ b/backend/src/routes/waitlist.ts @@ -0,0 +1,95 @@ +import { Router } from 'express'; +import { pool } from '../db'; +import { z } from 'zod'; + +const router = Router(); + +// Validation schema +const waitlistSchema = z.object({ + email: z.string().email('Invalid email address'), + source: z.string().optional().default('landing_page'), + referrer: z.string().optional(), +}); + +// POST /api/waitlist - Add email to waitlist +router.post('/', async (req, res) => { + try { + const data = waitlistSchema.parse(req.body); + + // Check if email already exists + const existing = await pool.query( + 'SELECT id FROM waitlist_leads WHERE email = $1', + [data.email.toLowerCase()] + ); + + if (existing.rows.length > 0) { + // Already on waitlist - return success anyway (don't reveal they're already signed up) + const countResult = await pool.query('SELECT COUNT(*) FROM waitlist_leads'); + const position = parseInt(countResult.rows[0].count, 10); + + return res.json({ + success: true, + message: 'You\'re on the list!', + position, + alreadySignedUp: true, + }); + } + + // Insert new lead + await pool.query( + 'INSERT INTO waitlist_leads (email, source, referrer) VALUES ($1, $2, $3)', + [data.email.toLowerCase(), data.source, data.referrer || null] + ); + + // Get current position (total count) + const countResult = await pool.query('SELECT COUNT(*) FROM waitlist_leads'); + const position = parseInt(countResult.rows[0].count, 10); + + console.log(`✅ Waitlist signup: ${data.email} (Position #${position})`); + + res.json({ + success: true, + message: 'You\'re on the list!', + position, + }); + } catch (error) { + if (error instanceof z.ZodError) { + return res.status(400).json({ + success: false, + error: 'validation_error', + message: error.errors[0].message, + }); + } + + console.error('Waitlist signup error:', error); + res.status(500).json({ + success: false, + error: 'server_error', + message: 'Failed to join waitlist. Please try again.', + }); + } +}); + +// GET /api/waitlist/count - Get current waitlist count (public) +router.get('/count', async (_req, res) => { + try { + const result = await pool.query('SELECT COUNT(*) FROM waitlist_leads'); + const count = parseInt(result.rows[0].count, 10); + + // Add a base number to make it look more impressive at launch + const displayCount = count + 430; // Starting with "430+ waiting" + + res.json({ + success: true, + count: displayCount, + }); + } catch (error) { + console.error('Waitlist count error:', error); + res.status(500).json({ + success: false, + count: 430, // Fallback to base number + }); + } +}); + +export default router; diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 4738c7c..ee82b0d 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -76,24 +76,25 @@ } /* Dark theme following the warm palette aesthetic */ - .dark { - --background: 30 15% 10%; - --foreground: 40 14% 92%; + /* Dark theme override with higher specificity */ + html.dark { + --background: 30 15% 4%; + --foreground: 40 14% 96%; - --card: 30 12% 14%; - --card-foreground: 40 14% 92%; + --card: 30 10% 18%; + --card-foreground: 40 14% 96%; - --popover: 30 12% 14%; - --popover-foreground: 40 14% 92%; + --popover: 30 10% 18%; + --popover-foreground: 40 14% 96%; --primary: 32 35% 55%; --primary-foreground: 30 15% 10%; - --secondary: 30 12% 20%; - --secondary-foreground: 40 14% 92%; + --secondary: 30 10% 22%; + --secondary-foreground: 40 14% 96%; - --muted: 30 12% 20%; - --muted-foreground: 35 10% 60%; + --muted: 30 10% 22%; + --muted-foreground: 35 10% 65%; --accent: 32 35% 55%; --accent-foreground: 30 15% 10%; @@ -107,9 +108,18 @@ --warning: 38 92% 50%; --warning-foreground: 0 0% 100%; - --border: 30 12% 24%; - --input: 30 12% 24%; + --border: 30 12% 20%; + --input: 30 12% 20%; --ring: 32 35% 50%; + + /* Dark Mode Section Backgrounds */ + --section-bg-1: 30 15% 8%; + --section-bg-2: 30 12% 10%; + --section-bg-3: 30 12% 9%; + --section-bg-4: 35 15% 11%; + --section-bg-5: 25 10% 9%; + --section-bg-6: 177 20% 8%; + --section-bg-7: 349 20% 9%; } } @@ -196,6 +206,13 @@ inset 0 1px 0 0 rgba(255, 255, 255, 0.5); } + .dark .glass-card { + background: rgba(40, 35, 30, 0.7); + /* Lighter, warmer dark glass */ + border: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.4); + } + .glass-card-dark { background: rgba(0, 0, 0, 0.4); backdrop-filter: blur(20px) saturate(180%); diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index 75b34cc..7d3b643 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -2,8 +2,8 @@ import { useEffect, useState } from 'react' import Link from 'next/link' -import { isAuthenticated } from '@/lib/auth' import { Button } from '@/components/ui/button' +import { ThemeToggle } from '@/components/ui/ThemeToggle' import { HeroSection, UseCaseShowcase, HowItWorks, Differentiators, SocialProof, FinalCTA } from '@/components/landing/LandingSections' import { LiveStatsBar } from '@/components/landing/LiveStatsBar' import { PricingComparison } from '@/components/landing/PricingComparison' @@ -12,20 +12,11 @@ import { motion, AnimatePresence } from 'framer-motion' import { Check, ChevronDown, Monitor, Globe, Shield, Clock, Zap, Menu } from 'lucide-react' export default function Home() { - const [loading, setLoading] = useState(true) - const [isAuth, setIsAuth] = useState(false) const [openFaq, setOpenFaq] = useState(null) const [billingPeriod, setBillingPeriod] = useState<'monthly' | 'yearly'>('monthly') const [mobileMenuOpen, setMobileMenuOpen] = useState(false) const [scrollProgress, setScrollProgress] = useState(0) - useEffect(() => { - // Check auth status but DO NOT redirect - const auth = isAuthenticated() - setIsAuth(auth) - setLoading(false) - }, []) - // Scroll progress tracking useEffect(() => { const handleScroll = () => { @@ -38,16 +29,6 @@ export default function Home() { return () => window.removeEventListener('scroll', handleScroll) }, []) - if (loading) { - return ( -
-
-
-
-
- ) - } - const faqs = [ { question: 'What is website monitoring?', @@ -85,19 +66,14 @@ export default function Home() {
- {isAuth ? ( - - - - ) : ( - - - - )} + + {/* Mobile Menu Button */}
)} @@ -140,7 +120,7 @@ export default function Home() { /> {/* Hero Section */} - + {/* Live Stats Bar */} @@ -365,7 +345,7 @@ export default function Home() { {/* Final CTA */} - + {/* Footer */} < footer className="border-t border-border bg-background py-12 text-sm" > diff --git a/frontend/components/landing/CompetitorDemoVisual.tsx b/frontend/components/landing/CompetitorDemoVisual.tsx index 45d5e4f..184ce00 100644 --- a/frontend/components/landing/CompetitorDemoVisual.tsx +++ b/frontend/components/landing/CompetitorDemoVisual.tsx @@ -34,23 +34,22 @@ export function CompetitorDemoVisual() { {/* Price Card */} {/* Shine effect on change */} {phase === 1 && ( )} @@ -58,7 +57,7 @@ export function CompetitorDemoVisual() { {/* Old Price */} $99 - /month + /month @@ -82,15 +81,17 @@ export function CompetitorDemoVisual() { - +
+ +
- + $79 - /month + /month
)} @@ -98,12 +99,12 @@ export function CompetitorDemoVisual() { {/* Savings Badge */} {phase === 1 && ( - + Save $240/year @@ -112,26 +113,28 @@ export function CompetitorDemoVisual() {
{/* Alert Notification */} - {phase === 1 && ( - -
- - -
- - Alert sent to your team - -
- )} + { + phase === 1 && ( + +
+ + +
+ + Alert sent to your team + +
+ ) + } ) diff --git a/frontend/components/landing/LandingSections.tsx b/frontend/components/landing/LandingSections.tsx index c0c8942..51f6cdf 100644 --- a/frontend/components/landing/LandingSections.tsx +++ b/frontend/components/landing/LandingSections.tsx @@ -42,9 +42,9 @@ const scaleIn: Variants = { // ============================================ // 1. HERO SECTION - "Track competitor changes without the noise" // ============================================ -export function HeroSection({ isAuthenticated }: { isAuthenticated: boolean }) { +export function HeroSection() { return ( -
+
{/* Background Elements */}
@@ -109,23 +109,13 @@ export function HeroSection({ isAuthenticated }: { isAuthenticated: boolean }) { ))} - {/* CTAs */} + {/* Waitlist Form */} - - - - - + {/* Trust Signals */} @@ -352,19 +342,19 @@ function NoiseToSignalVisual() { className="absolute inset-0 flex items-center justify-center p-8" > {/* Animated corner accent */} @@ -372,20 +362,20 @@ function NoiseToSignalVisual() {
✓ SIGNAL DETECTED -
+
Filtered
-

Enterprise Plan

+

Enterprise Plan

-

$99/mo

+

$99/mo

= 2 ? 1 : 0.9 }} transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }} - className="text-lg text-[hsl(var(--burgundy))] font-bold flex items-center gap-1" + className="text-lg text-[hsl(var(--burgundy))] dark:text-red-500 font-bold flex items-center gap-1" > $79/mo @@ -411,10 +402,10 @@ function NoiseToSignalVisual() { - - + + Alert Sent @@ -424,6 +415,7 @@ function NoiseToSignalVisual() { )} + {/* Phase Indicator */}
{[0, 1, 2, 3].map(i => ( @@ -438,8 +430,8 @@ function NoiseToSignalVisual() { /> ))}
-
- +
+ ) } @@ -742,14 +734,12 @@ export function SocialProof() { ] return ( -
- {/* Background Pattern */} -
-
-
+
+ {/* Background Pattern - Subtle dots for light theme */} +
{/* Section Header */} @@ -759,13 +749,13 @@ export function SocialProof() { viewport={{ once: true }} className="text-center mb-20" > - + Built for teams who need results,{' '} not demos. - {/* Testimonial Cards - Minimal & Uniform */} + {/* Testimonial Cards - Light Theme */}
{testimonials.map((testimonial, i) => ( - {/* Subtle gradient border glow */} -
+ {/* Card Container */} +
- {/* Main Card - fixed height for uniformity */} -
- {/* Large Quote Mark */} -
+ {/* Quote Mark */} +
"
- {/* Quote - flex-grow ensures cards align */} -

+ {/* Quote */} +

{testimonial.quote}

- {/* Attribution - always at bottom */} -
+ {/* Attribution */} +
-

{testimonial.author}

-

{testimonial.role} at {testimonial.company}

+

{testimonial.author}

+

{testimonial.role} at {testimonial.company}

-
+
{testimonial.useCase}
@@ -824,7 +812,7 @@ export function SocialProof() { // ============================================ // 6. FINAL CTA - Get Started // ============================================ -export function FinalCTA({ isAuthenticated }: { isAuthenticated: boolean }) { +export function FinalCTA() { return (
{/* Animated Gradient Mesh Background - More dramatic */} @@ -869,23 +857,9 @@ export function FinalCTA({ isAuthenticated }: { isAuthenticated: boolean }) { Join the waitlist and be first to experience monitoring that actually works. - {/* Waitlist Form - replaces button */} - - {isAuthenticated ? ( - - - - - - ) : ( - - )} + {/* Waitlist Form */} + + {/* Social Proof Indicator */} diff --git a/frontend/components/landing/LiveStatsBar.tsx b/frontend/components/landing/LiveStatsBar.tsx index f7535f6..7e47f2b 100644 --- a/frontend/components/landing/LiveStatsBar.tsx +++ b/frontend/components/landing/LiveStatsBar.tsx @@ -84,7 +84,7 @@ export function LiveStatsBar() { ] return ( -
+
{/* Desktop: Grid */}
diff --git a/frontend/components/landing/PolicyDemoVisual.tsx b/frontend/components/landing/PolicyDemoVisual.tsx index 887d4a2..4e66624 100644 --- a/frontend/components/landing/PolicyDemoVisual.tsx +++ b/frontend/components/landing/PolicyDemoVisual.tsx @@ -37,23 +37,23 @@ export function PolicyDemoVisual() { {/* Document Content */} {/* Section 4.2 */}
-
+
Section 4.2 - Data Retention
{/* Text Lines */} -
+

We will retain your personal data for

{/* Changing text */} @@ -63,10 +63,10 @@ export function PolicyDemoVisual() { > )} @@ -106,9 +106,9 @@ export function PolicyDemoVisual() { initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: 'auto' }} transition={{ delay: 0.3 }} - className="pt-2 border-t border-border flex items-center justify-between" + className="pt-2 border-t border-zinc-800 flex items-center justify-between" > -
+
+18 words @@ -128,16 +128,16 @@ export function PolicyDemoVisual() { initial={{ opacity: 0, y: 5, scale: 0.9 }} animate={{ opacity: 1, y: 0, scale: 1 }} transition={{ delay: 0.5 }} - className="mt-3 flex items-center gap-2 p-2 rounded-lg bg-[hsl(var(--teal))]/10 border border-[hsl(var(--teal))]/30" + className="mt-3 flex items-center gap-2 p-2 rounded-lg bg-red-500/10 border border-red-500/30" > -
+
-
+
Audit trail saved
-
+
Snapshot archived for compliance
diff --git a/frontend/components/landing/SEODemoVisual.tsx b/frontend/components/landing/SEODemoVisual.tsx index 16bdbdc..c788d95 100644 --- a/frontend/components/landing/SEODemoVisual.tsx +++ b/frontend/components/landing/SEODemoVisual.tsx @@ -57,36 +57,37 @@ export function SEODemoVisual() { {/* SERP Snippet */} {/* URL */}
-
- +
+ competitor.com/product
{/* Title */} -

+

Best Enterprise Software Solution 2026

{/* Meta Description with change highlighting */} - NEW + Changed )} diff --git a/frontend/components/landing/WaitlistForm.tsx b/frontend/components/landing/WaitlistForm.tsx index 9eb8ca5..0f187dd 100644 --- a/frontend/components/landing/WaitlistForm.tsx +++ b/frontend/components/landing/WaitlistForm.tsx @@ -47,16 +47,34 @@ export function WaitlistForm() { setIsSubmitting(true) - // Simulate API call - await new Promise(resolve => setTimeout(resolve, 1500)) + try { + const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001' + const response = await fetch(`${apiUrl}/api/waitlist`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + email, + source: 'landing_page', + referrer: typeof window !== 'undefined' ? document.referrer : undefined, + }), + }) - // Generate a random queue position - const position = Math.floor(Math.random() * 500) + 400 + const data = await response.json() - setQueuePosition(position) - setIsSubmitting(false) - setIsSuccess(true) - triggerConfetti() + if (data.success) { + setQueuePosition(data.position || Math.floor(Math.random() * 500) + 430) + setIsSubmitting(false) + setIsSuccess(true) + triggerConfetti() + } else { + setIsSubmitting(false) + setError(data.message || 'Something went wrong. Please try again.') + } + } catch (err) { + console.error('Waitlist signup error:', err) + setIsSubmitting(false) + setError('Connection error. Please try again.') + } } if (isSuccess) { @@ -193,11 +211,10 @@ export function WaitlistForm() { }} placeholder="Enter your email" disabled={isSubmitting} - className={`w-full h-14 rounded-full px-6 text-base border-2 transition-all outline-none ${ - error + className={`w-full h-14 rounded-full px-6 text-base border-2 transition-all outline-none ${error ? 'border-red-500 bg-red-50 focus:border-red-500 focus:ring-4 focus:ring-red-500/20' : 'border-border bg-background focus:border-[hsl(var(--primary))] focus:ring-4 focus:ring-[hsl(var(--primary))]/20' - } disabled:opacity-50 disabled:cursor-not-allowed`} + } disabled:opacity-50 disabled:cursor-not-allowed`} /> {error && ( diff --git a/frontend/components/ui/ThemeToggle.tsx b/frontend/components/ui/ThemeToggle.tsx new file mode 100644 index 0000000..2a1476b --- /dev/null +++ b/frontend/components/ui/ThemeToggle.tsx @@ -0,0 +1,70 @@ +'use client' + +import { useEffect, useState } from 'react' +import { Moon, Sun } from 'lucide-react' +import { motion } from 'framer-motion' + +export function ThemeToggle() { + const [theme, setTheme] = useState<'light' | 'dark'>('light') + const [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + // Check for stored preference or system preference + const stored = localStorage.getItem('theme') + if (stored === 'dark' || stored === 'light') { + setTheme(stored) + } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + setTheme('dark') + } + }, []) + + useEffect(() => { + if (!mounted) return + + const root = document.documentElement + if (theme === 'dark') { + root.classList.add('dark') + } else { + root.classList.remove('dark') + } + localStorage.setItem('theme', theme) + }, [theme, mounted]) + + const toggleTheme = () => { + setTheme(prev => prev === 'light' ? 'dark' : 'light') + } + + // Prevent hydration mismatch + if (!mounted) { + return ( + + ) + } + + return ( + + + {theme === 'light' ? ( + + ) : ( + + )} + + + ) +} + +export default ThemeToggle diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 77d3649..e215a98 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -768,7 +768,6 @@ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -1200,7 +1199,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1672,7 +1670,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2407,7 +2404,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2576,7 +2572,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -3963,7 +3958,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -4766,7 +4760,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4930,7 +4923,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -4963,7 +4955,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -5745,6 +5736,24 @@ } } }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5816,7 +5825,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5986,7 +5994,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts index 71379e0..dfdc370 100644 --- a/frontend/tailwind.config.ts +++ b/frontend/tailwind.config.ts @@ -6,6 +6,7 @@ const config: Config = { './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', ], + darkMode: 'class', theme: { extend: { fontFamily: {