diff --git a/Dockerfile b/Dockerfile index 1057535..74e8151 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,9 @@ RUN npm run build FROM node:20-alpine AS runner WORKDIR /app +# Install OpenSSL for Prisma +RUN apk add --no-cache openssl + ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 diff --git a/Dockerfile.migrate b/Dockerfile.migrate index 4ae7126..6923795 100644 --- a/Dockerfile.migrate +++ b/Dockerfile.migrate @@ -2,6 +2,9 @@ FROM node:20-alpine WORKDIR /app +# Install OpenSSL for Prisma +RUN apk add --no-cache openssl + COPY package*.json ./ RUN npm ci diff --git a/prisma/migrations/20260104204941_init/migration.sql b/prisma/migrations/20260104204941_init/migration.sql new file mode 100644 index 0000000..89afcf5 --- /dev/null +++ b/prisma/migrations/20260104204941_init/migration.sql @@ -0,0 +1,34 @@ +-- CreateTable +CREATE TABLE "Event" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT, + "startDate" DATE NOT NULL, + "endDate" DATE NOT NULL, + "shareCode" TEXT NOT NULL, + "adminPasswordHash" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Event_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Response" ( + "id" TEXT NOT NULL, + "eventId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "availableDates" JSONB NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Response_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Event_shareCode_key" ON "Event"("shareCode"); + +-- CreateIndex +CREATE INDEX "Response_eventId_idx" ON "Response"("eventId"); + +-- AddForeignKey +ALTER TABLE "Response" ADD CONSTRAINT "Response_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "Event"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ef3d107..ac5e545 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,5 +1,6 @@ generator client { - provider = "prisma-client-js" + provider = "prisma-client-js" + binaryTargets = ["native", "linux-musl-openssl-3.0.x"] } datasource db { diff --git a/src/app/event/[code]/analytics/page.tsx b/src/app/event/[code]/analytics/page.tsx index 1bab9fe..094bc3b 100644 --- a/src/app/event/[code]/analytics/page.tsx +++ b/src/app/event/[code]/analytics/page.tsx @@ -153,13 +153,13 @@ export default function AnalyticsPage({ params }: { params: Promise<{ code: stri return (
{/* Header */} -
-
+
+
Analytics Dashboard
-

+

{analytics.event.title}

-

+

Created on {new Date(analytics.event.createdAt).toLocaleDateString('en-US', { year: 'numeric', month: 'long', @@ -220,7 +220,7 @@ export default function AnalyticsPage({ params }: { params: Promise<{ code: stri width: 16, height: 16, borderRadius: 3, - background: `rgba(99, 91, 255, ${level * 0.2})` + background: `rgba(224, 108, 80, ${level * 0.2})` // Coral accent }} /> ))} diff --git a/src/app/event/[code]/page.tsx b/src/app/event/[code]/page.tsx index 65378ae..20c1cbe 100644 --- a/src/app/event/[code]/page.tsx +++ b/src/app/event/[code]/page.tsx @@ -170,18 +170,18 @@ export default function EventPage({ params }: { params: Promise<{ code: string } return (

{/* Header */} -
-
+
+
Event Invitation
-

+

{event.title}

{event.description && ( -

+

{event.description}

)} -
+
{event.responseCount} {event.responseCount === 1 ? 'person has' : 'people have'} responded
diff --git a/src/app/globals.css b/src/app/globals.css index edd0473..e3bc13b 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,4 +1,4 @@ -/* Timepickr - Stripe-inspired Design System */ +/* Timepickr - "Calmer Internet" Light Theme */ /* ===== CSS RESET & BASE ===== */ *, @@ -11,79 +11,62 @@ /* ===== CSS VARIABLES ===== */ :root { - /* Primary colors - Stripe's signature gradient */ - --primary-50: #f5f3ff; - --primary-100: #ede9fe; - --primary-200: #ddd6fe; - --primary-300: #c4b5fd; - --primary-400: #a78bfa; - --primary-500: #8b5cf6; - --primary-600: #7c3aed; - --primary-700: #6d28d9; - --primary-800: #5b21b6; - --primary-900: #4c1d95; + /* Palette - Warm, Calm, Earthy */ + --bg-primary: #f2f0e9; + /* Warm cream/beige */ + --bg-secondary: #e6e4dc; + /* Slightly darker beige */ + --bg-tertiary: #dad8cf; + /* Even darker for inputs/borders */ - /* Accent - Stripe blue-to-purple */ - --accent-start: #635bff; - --accent-mid: #a960ee; - --accent-end: #f637cf; + --text-primary: #2d2a26; + /* Soft black/charcoal */ + --text-secondary: #5c5852; + /* Dark taupe */ + --text-tertiary: #8a8680; + /* Light taupe */ + --text-inverse: #f2f0e9; + /* Off-white for dark buttons */ - /* Neutral grays */ - --gray-50: #fafafa; - --gray-100: #f4f4f5; - --gray-200: #e4e4e7; - --gray-300: #d4d4d8; - --gray-400: #a1a1aa; - --gray-500: #71717a; - --gray-600: #52525b; - --gray-700: #3f3f46; - --gray-800: #27272a; - --gray-900: #18181b; + /* Accent - Coral/Orange from reference */ + --accent-primary: #e06c50; + /* Coral orange */ + --accent-hover: #d15a40; + --accent-light: #fcece8; + /* Light coral wash */ - /* Semantic colors */ - --success: #22c55e; - --success-bg: #dcfce7; - --warning: #eab308; - --warning-bg: #fef9c3; - --error: #ef4444; - --error-bg: #fee2e2; + /* Semantic */ + --success: #4a7c59; + /* Muted green */ + --success-bg: #e3efe7; + --error: #d85c5c; + /* Muted red */ + --error-bg: #fadece; + --warning: #cc9c3d; + /* Muted gold */ + --warning-bg: #f9f1d8; - /* Background */ - --bg-primary: #ffffff; - --bg-secondary: #fafafa; - --bg-tertiary: #f4f4f5; + /* Borders & Shadows */ + --border-light: rgba(45, 42, 38, 0.08); + --border-medium: rgba(45, 42, 38, 0.15); - /* Text */ - --text-primary: #18181b; - --text-secondary: #52525b; - --text-tertiary: #71717a; - --text-inverse: #ffffff; - - /* Borders */ - --border-light: #e4e4e7; - --border-medium: #d4d4d8; - - /* Shadows - Stripe style */ - --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); - --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); - --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); - --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); - --shadow-glow: 0 0 60px -15px rgba(99, 91, 255, 0.4); + --shadow-sm: 0 1px 2px rgba(45, 42, 38, 0.05); + --shadow-md: 0 4px 12px rgba(45, 42, 38, 0.08); + --shadow-lg: 0 12px 24px rgba(45, 42, 38, 0.12); /* Radius */ - --radius-sm: 6px; + --radius-sm: 4px; --radius-md: 8px; - --radius-lg: 12px; - --radius-xl: 16px; + --radius-lg: 16px; --radius-full: 9999px; - /* Transitions */ - --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); - --transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1); - --transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1); + /* Fonts */ + --font-serif: 'Times New Roman', serif; + /* Fallback */ + --font-sans: system-ui, sans-serif; + /* Fallback */ - /* Spacing scale */ + /* Spacing */ --space-1: 4px; --space-2: 8px; --space-3: 12px; @@ -95,6 +78,9 @@ --space-12: 48px; --space-16: 64px; --space-20: 80px; + + /* Transitions */ + --transition: 0.2s cubic-bezier(0.2, 0, 0, 1); } /* ===== BASE STYLES ===== */ @@ -105,118 +91,101 @@ html { } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; background: var(--bg-primary); color: var(--text-primary); + font-family: var(--font-sans); line-height: 1.6; min-height: 100vh; } /* ===== TYPOGRAPHY ===== */ -h1, h2, h3, h4, h5, h6 { - font-weight: 600; - line-height: 1.3; +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-serif); + font-weight: 400; + /* Serif looks good regular */ color: var(--text-primary); + line-height: 1.2; } -h1 { font-size: 3rem; letter-spacing: -0.02em; } -h2 { font-size: 2rem; letter-spacing: -0.01em; } -h3 { font-size: 1.5rem; } -h4 { font-size: 1.25rem; } -h5 { font-size: 1rem; } +h1 { + font-size: 4rem; + letter-spacing: -0.03em; +} + +h2 { + font-size: 2.5rem; + letter-spacing: -0.02em; +} + +h3 { + font-size: 1.75rem; + letter-spacing: -0.01em; +} + +h4 { + font-size: 1.25rem; +} + +/* The "italic accent" style from the reference */ +em, +.serif-italic { + font-family: var(--font-serif); + font-style: italic; + font-weight: 400; + color: var(--accent-primary); +} p { color: var(--text-secondary); + font-size: 1.0625rem; + /* Slightly larger for readability */ } /* ===== LAYOUT ===== */ -.container { - max-width: 1200px; - margin: 0 auto; +.container, +.container-sm, +.container-md { + width: 100%; padding: 0 var(--space-6); + margin: 0 auto; } -.container-sm { - max-width: 640px; - margin: 0 auto; - padding: 0 var(--space-6); +.container { + max-width: 1200px; } .container-md { max-width: 800px; - margin: 0 auto; - padding: 0 var(--space-6); } -/* ===== HERO GRADIENT ===== */ -.hero-gradient { - background: linear-gradient( - 135deg, - #0a0a0a 0%, - #1a1a2e 25%, - #16213e 50%, - #1a1a2e 75%, - #0a0a0a 100% - ); - position: relative; - overflow: hidden; -} - -.hero-gradient::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: - radial-gradient(ellipse 80% 50% at 50% -20%, rgba(99, 91, 255, 0.3), transparent), - radial-gradient(ellipse 60% 40% at 80% 50%, rgba(169, 96, 238, 0.2), transparent), - radial-gradient(ellipse 50% 30% at 20% 80%, rgba(246, 55, 207, 0.15), transparent); - pointer-events: none; -} - -.hero-content { - position: relative; - z-index: 1; -} - -/* ===== CARDS ===== */ -.card { - background: var(--bg-primary); - border: 1px solid var(--border-light); - border-radius: var(--radius-lg); - padding: var(--space-6); - box-shadow: var(--shadow-sm); - transition: box-shadow var(--transition-base), transform var(--transition-base); -} - -.card:hover { - box-shadow: var(--shadow-md); -} - -.card-elevated { - background: var(--bg-primary); - border-radius: var(--radius-xl); - padding: var(--space-8); - box-shadow: var(--shadow-xl), var(--shadow-glow); +.container-sm { + max-width: 600px; } /* ===== BUTTONS ===== */ +/* Reset default button styles */ +button { + all: unset; + box-sizing: border-box; +} + .btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); - padding: var(--space-3) var(--space-5); + padding: var(--space-3) var(--space-6); + font-family: var(--font-sans); font-size: 0.9375rem; font-weight: 500; - line-height: 1.5; border-radius: var(--radius-md); - border: none; cursor: pointer; - transition: all var(--transition-fast); + transition: all var(--transition); text-decoration: none; white-space: nowrap; } @@ -227,82 +196,68 @@ p { } .btn-primary { - background: linear-gradient(135deg, var(--accent-start), var(--primary-600)); - color: var(--text-inverse); - box-shadow: 0 1px 2px 0 rgba(99, 91, 255, 0.4), inset 0 1px 0 0 rgba(255, 255, 255, 0.1); + background: var(--text-primary); + /* Dark button */ + color: var(--bg-primary); + border: 1px solid transparent; } .btn-primary:hover:not(:disabled) { - background: linear-gradient(135deg, #5753e8, var(--primary-700)); + background: black; transform: translateY(-1px); - box-shadow: 0 4px 12px 0 rgba(99, 91, 255, 0.4), inset 0 1px 0 0 rgba(255, 255, 255, 0.1); -} - -.btn-primary:active:not(:disabled) { - transform: translateY(0); } .btn-secondary { - background: var(--bg-primary); + background: transparent; color: var(--text-primary); border: 1px solid var(--border-medium); } .btn-secondary:hover:not(:disabled) { background: var(--bg-secondary); - border-color: var(--gray-400); -} - -.btn-ghost { - background: transparent; - color: var(--text-secondary); -} - -.btn-ghost:hover:not(:disabled) { - background: var(--bg-tertiary); - color: var(--text-primary); + border-color: var(--text-primary); } .btn-lg { padding: var(--space-4) var(--space-8); - font-size: 1rem; + font-size: 1.125rem; border-radius: var(--radius-lg); } -.btn-sm { - padding: var(--space-2) var(--space-3); - font-size: 0.875rem; -} - -/* ===== FORM ELEMENTS ===== */ +/* ===== INPUTS & FORMS ===== */ .form-group { - margin-bottom: var(--space-5); + margin-bottom: var(--space-6); } .form-label { display: block; + font-family: var(--font-sans); font-size: 0.875rem; - font-weight: 500; + font-weight: 600; color: var(--text-primary); margin-bottom: var(--space-2); + text-transform: uppercase; + letter-spacing: 0.05em; } .form-input { width: 100%; padding: var(--space-3) var(--space-4); + font-family: var(--font-sans); font-size: 1rem; - line-height: 1.5; color: var(--text-primary); background: var(--bg-primary); + /* Transparent/match bg */ border: 1px solid var(--border-medium); border-radius: var(--radius-md); - transition: border-color var(--transition-fast), box-shadow var(--transition-fast); + transition: all var(--transition); } .form-input:focus { outline: none; - border-color: var(--accent-start); - box-shadow: 0 0 0 3px rgba(99, 91, 255, 0.15); + border-color: var(--text-primary); + background: white; + box-shadow: 0 0 0 4px var(--border-light); } .form-input::placeholder { @@ -310,26 +265,63 @@ p { } textarea.form-input { + min-height: 120px; resize: vertical; - min-height: 100px; } .form-hint { font-size: 0.8125rem; color: var(--text-tertiary); - margin-top: var(--space-1); + margin-top: var(--space-2); } .form-error { - font-size: 0.8125rem; color: var(--error); - margin-top: var(--space-1); + font-size: 0.875rem; + margin-top: var(--space-2); } -/* ===== CALENDAR STYLES ===== */ +/* ===== CARDS & CONTAINERS ===== */ +.card, +.card-elevated { + background: var(--bg-primary); + /* Blend with background, rely on border/shadow */ + border: 1px solid var(--border-medium); + border-radius: var(--radius-lg); + padding: var(--space-6); +} + +.card-elevated { + background: #fbfbf7; + /* Slightly lighter than main bg to pop */ + box-shadow: var(--shadow-md); + border: 1px solid var(--border-light); +} + +/* ===== HERO SECTION ===== */ +.hero-wrapper { + padding: var(--space-20) 0; + text-align: center; +} + +.hero-title { + font-family: var(--font-serif); + font-size: 4.5rem; + color: var(--text-primary); + margin-bottom: var(--space-4); + line-height: 1.1; +} + +.hero-subtitle { + font-size: 1.25rem; + color: var(--text-secondary); + max-width: 600px; + margin: 0 auto var(--space-8); +} + +/* ===== CALENDAR (Customized for new theme) ===== */ .calendar { width: 100%; - user-select: none; } .calendar-header { @@ -337,16 +329,11 @@ textarea.form-input { align-items: center; justify-content: space-between; margin-bottom: var(--space-4); + font-family: var(--font-serif); } .calendar-title { - font-size: 1.125rem; - font-weight: 600; -} - -.calendar-nav { - display: flex; - gap: var(--space-2); + font-size: 1.5rem; } .calendar-nav-btn { @@ -355,40 +342,34 @@ textarea.form-input { display: flex; align-items: center; justify-content: center; - background: var(--bg-secondary); - border: 1px solid var(--border-light); - border-radius: var(--radius-md); + background: transparent; + border: 1px solid var(--border-medium); + border-radius: 50%; + /* Circle buttons */ + color: var(--text-primary); cursor: pointer; - transition: all var(--transition-fast); - color: var(--text-secondary); } .calendar-nav-btn:hover { - background: var(--bg-tertiary); - color: var(--text-primary); -} - -.calendar-weekdays { - display: grid; - grid-template-columns: repeat(7, 1fr); - gap: 4px; - margin-bottom: var(--space-2); + background: var(--bg-secondary); + border-color: var(--text-primary); } .calendar-weekday { text-align: center; + font-family: var(--font-sans); font-size: 0.75rem; font-weight: 600; + letter-spacing: 0.1em; color: var(--text-tertiary); text-transform: uppercase; - letter-spacing: 0.05em; - padding: var(--space-2) 0; + padding-bottom: var(--space-2); } .calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); - gap: 4px; + gap: 2px; } .calendar-day { @@ -396,374 +377,172 @@ textarea.form-input { display: flex; align-items: center; justify-content: center; - font-size: 0.9375rem; - font-weight: 500; - border-radius: var(--radius-md); - cursor: pointer; - transition: all var(--transition-fast); - background: var(--bg-primary); - border: 1px solid transparent; + font-family: var(--font-sans); + font-size: 1rem; + background: transparent; + border-radius: var(--radius-sm); color: var(--text-primary); + cursor: pointer; + transition: all 0.1s; } -.calendar-day:hover:not(.disabled):not(.outside) { +.calendar-day:hover:not(.disabled):not(.selected) { background: var(--bg-tertiary); - border-color: var(--border-medium); } .calendar-day.selected { - background: linear-gradient(135deg, var(--accent-start), var(--primary-600)); - color: var(--text-inverse); - border-color: transparent; - box-shadow: 0 2px 8px rgba(99, 91, 255, 0.3); + background: var(--accent-primary); + color: white; + font-weight: 500; } -.calendar-day.selected:hover { - background: linear-gradient(135deg, #5753e8, var(--primary-700)); +.calendar-day.unavailable { + background: var(--error-bg); + color: var(--error); + text-decoration: line-through; } .calendar-day.today { - border-color: var(--accent-start); + color: var(--accent-primary); + font-weight: bold; + position: relative; } -.calendar-day.outside { - color: var(--text-tertiary); - opacity: 0.4; - cursor: default; +.calendar-day.today::after { + content: ''; + position: absolute; + bottom: 6px; + left: 50%; + transform: translateX(-50%); + width: 4px; + height: 4px; + background: currentColor; + border-radius: 50%; } +.calendar-day.outside, .calendar-day.disabled { color: var(--text-tertiary); opacity: 0.3; - cursor: not-allowed; - background: var(--bg-tertiary); + pointer-events: none; } -/* Heatmap calendar for analytics */ -.calendar-day.heat-1 { background: rgba(99, 91, 255, 0.1); } -.calendar-day.heat-2 { background: rgba(99, 91, 255, 0.25); } -.calendar-day.heat-3 { background: rgba(99, 91, 255, 0.4); } -.calendar-day.heat-4 { background: rgba(99, 91, 255, 0.6); color: var(--text-inverse); } -.calendar-day.heat-5 { background: rgba(99, 91, 255, 0.8); color: var(--text-inverse); } -.calendar-day.heat-max { - background: linear-gradient(135deg, var(--accent-start), var(--primary-600)); - color: var(--text-inverse); - box-shadow: 0 2px 8px rgba(99, 91, 255, 0.3); +/* Heatmap Styles */ +.calendar-day.heat-1 { + background: #ffe4de; } -/* ===== SHARE LINK ===== */ -.share-link-container { - display: flex; - gap: var(--space-2); - align-items: stretch; +.calendar-day.heat-2 { + background: #ffc9bd; } -.share-link-input { - flex: 1; - padding: var(--space-3) var(--space-4); - font-size: 0.875rem; - font-family: 'SF Mono', Monaco, 'Consolas', monospace; - background: var(--bg-tertiary); - border: 1px solid var(--border-light); - border-radius: var(--radius-md); - color: var(--text-primary); +.calendar-day.heat-3 { + background: #ffad9c; } -.share-link-btn { - padding: var(--space-3) var(--space-4); - display: flex; - align-items: center; - gap: var(--space-2); +.calendar-day.heat-4 { + background: #ff927a; } -/* ===== BADGES ===== */ +.calendar-day.heat-5 { + background: #f0765d; +} + +.calendar-day.heat-max { + background: var(--accent-primary); + color: white; +} + +/* ===== UTILITIES & MISC ===== */ .badge { display: inline-flex; - align-items: center; - padding: var(--space-1) var(--space-3); + padding: 4px 10px; + border-radius: var(--radius-full); font-size: 0.75rem; font-weight: 600; - border-radius: var(--radius-full); + letter-spacing: 0.05em; text-transform: uppercase; - letter-spacing: 0.02em; } .badge-primary { - background: var(--primary-100); - color: var(--primary-700); + background: var(--bg-tertiary); + color: var(--text-primary); } .badge-success { background: var(--success-bg); - color: #166534; -} - -/* ===== MODAL ===== */ -.modal-overlay { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.5); - backdrop-filter: blur(4px); - display: flex; - align-items: center; - justify-content: center; - padding: var(--space-4); - z-index: 100; - animation: fadeIn 0.2s ease; -} - -.modal { - background: var(--bg-primary); - border-radius: var(--radius-xl); - padding: var(--space-8); - max-width: 400px; - width: 100%; - box-shadow: var(--shadow-xl); - animation: slideUp 0.3s ease; -} - -@keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } -} - -@keyframes slideUp { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* ===== SUCCESS STATE ===== */ -.success-icon { - width: 64px; - height: 64px; - background: var(--success-bg); - border-radius: var(--radius-full); - display: flex; - align-items: center; - justify-content: center; - margin: 0 auto var(--space-4); -} - -.success-icon svg { - width: 32px; - height: 32px; color: var(--success); } -/* ===== STATS GRID ===== */ -.stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: var(--space-4); -} - -.stat-card { - background: var(--bg-secondary); - border-radius: var(--radius-lg); - padding: var(--space-5); - text-align: center; -} - -.stat-value { - font-size: 2rem; - font-weight: 700; - color: var(--text-primary); - line-height: 1; - margin-bottom: var(--space-1); -} - -.stat-label { - font-size: 0.875rem; - color: var(--text-tertiary); -} - -/* ===== RESPONDENT LIST ===== */ -.respondent-list { - display: flex; - flex-direction: column; - gap: var(--space-3); -} - -.respondent-item { - display: flex; - align-items: center; - gap: var(--space-3); - padding: var(--space-3) var(--space-4); - background: var(--bg-secondary); - border-radius: var(--radius-md); -} - -.respondent-avatar { - width: 40px; - height: 40px; - border-radius: var(--radius-full); - background: linear-gradient(135deg, var(--accent-start), var(--accent-end)); - display: flex; - align-items: center; - justify-content: center; - color: var(--text-inverse); - font-weight: 600; - font-size: 1rem; -} - -.respondent-info { - flex: 1; -} - -.respondent-name { - font-weight: 500; - color: var(--text-primary); -} - -.respondent-dates { - font-size: 0.8125rem; - color: var(--text-tertiary); -} - -/* ===== BEST DATES ===== */ -.best-dates-list { - display: flex; - flex-direction: column; - gap: var(--space-2); -} - -.best-date-item { - display: flex; - align-items: center; - gap: var(--space-3); - padding: var(--space-3) var(--space-4); - background: var(--bg-secondary); - border-radius: var(--radius-md); -} - -.best-date-rank { - width: 28px; - height: 28px; - border-radius: var(--radius-full); - background: var(--primary-100); - color: var(--primary-700); - display: flex; - align-items: center; - justify-content: center; - font-weight: 600; - font-size: 0.875rem; -} - -.best-date-rank.gold { - background: linear-gradient(135deg, #fbbf24, #f59e0b); - color: white; -} - -.best-date-info { - flex: 1; -} - -.best-date-date { - font-weight: 500; - color: var(--text-primary); -} - -.best-date-count { - font-size: 0.8125rem; - color: var(--text-tertiary); -} - -/* ===== LOADING STATES ===== */ .spinner { - width: 20px; - height: 20px; - border: 2px solid transparent; + width: 18px; + height: 18px; + border: 2px solid rgba(0, 0, 0, 0.1); border-top-color: currentColor; border-radius: 50%; animation: spin 0.8s linear infinite; + display: inline-block; } @keyframes spin { - to { transform: rotate(360deg); } -} - -.skeleton { - background: linear-gradient(90deg, var(--bg-tertiary) 25%, var(--bg-secondary) 50%, var(--bg-tertiary) 75%); - background-size: 200% 100%; - animation: shimmer 1.5s infinite; - border-radius: var(--radius-md); -} - -@keyframes shimmer { - 0% { background-position: -200% 0; } - 100% { background-position: 200% 0; } -} - -/* ===== UTILITY CLASSES ===== */ -.text-center { text-align: center; } -.text-left { text-align: left; } -.text-right { text-align: right; } - -.mt-1 { margin-top: var(--space-1); } -.mt-2 { margin-top: var(--space-2); } -.mt-4 { margin-top: var(--space-4); } -.mt-6 { margin-top: var(--space-6); } -.mt-8 { margin-top: var(--space-8); } - -.mb-1 { margin-bottom: var(--space-1); } -.mb-2 { margin-bottom: var(--space-2); } -.mb-4 { margin-bottom: var(--space-4); } -.mb-6 { margin-bottom: var(--space-6); } -.mb-8 { margin-bottom: var(--space-8); } - -.flex { display: flex; } -.flex-col { flex-direction: column; } -.items-center { align-items: center; } -.justify-center { justify-content: center; } -.justify-between { justify-content: space-between; } -.gap-2 { gap: var(--space-2); } -.gap-4 { gap: var(--space-4); } -.gap-6 { gap: var(--space-6); } - -.w-full { width: 100%; } - -/* ===== RESPONSIVE ===== */ -@media (max-width: 768px) { - h1 { font-size: 2.25rem; } - h2 { font-size: 1.75rem; } - - .container, - .container-sm, - .container-md { - padding: 0 var(--space-4); + to { + transform: rotate(360deg); } - +} + +.text-center { + text-align: center; +} + +.mb-2 { + margin-bottom: var(--space-2); +} + +.mb-4 { + margin-bottom: var(--space-4); +} + +.mb-6 { + margin-bottom: var(--space-6); +} + +.mb-8 { + margin-bottom: var(--space-8); +} + +.mt-4 { + margin-top: var(--space-4); +} + +.flex { + display: flex; +} + +.gap-2 { + gap: var(--space-2); +} + +.gap-4 { + gap: var(--space-4); +} + +.justify-center { + justify-content: center; +} + +.w-full { + width: 100%; +} + +/* Mobile */ +@media (max-width: 600px) { + + h1, + .hero-title { + font-size: 2.75rem; + } + .card-elevated { - padding: var(--space-6); + padding: var(--space-4); } - - .calendar-day { - font-size: 0.875rem; - } - - .stats-grid { - grid-template-columns: repeat(2, 1fr); - } -} - -@media (max-width: 480px) { - h1 { font-size: 1.875rem; } - - .btn-lg { - width: 100%; - } - - .share-link-container { - flex-direction: column; - } - - .stats-grid { - grid-template-columns: 1fr; - } -} +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c6cb811..4dea34d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,9 +1,23 @@ import type { Metadata } from "next"; +import { Newsreader, Inter } from "next/font/google"; import "./globals.css"; +const newsreader = Newsreader({ + subsets: ["latin"], + variable: "--font-serif", + display: "swap", + style: ["normal", "italic"], +}); + +const inter = Inter({ + subsets: ["latin"], + variable: "--font-sans", + display: "swap", +}); + export const metadata: Metadata = { - title: "Timepickr - Find the perfect time for your event", - description: "Create events and let your friends pick their available dates. Find the best time that works for everyone.", + title: "Timepickr - Find the perfect time", + description: "Create events and let your friends pick their available dates. A calmer way to schedule.", keywords: ["event planning", "scheduling", "availability", "calendar", "group planning"], }; @@ -13,7 +27,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + {children} ); diff --git a/src/app/page.tsx b/src/app/page.tsx index 39c28c3..1998260 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -56,8 +56,8 @@ export default function HomePage() { if (result) { return ( -
-
+
+
@@ -108,22 +108,23 @@ export default function HomePage() { } return ( -
-
+
+
{/* Header */} -
-
Free & Simple
-

- Find the perfect time +
+

+ welcome to a
+ calmer way to plan

-

- Create an event, share the link, and let everyone pick their available dates. +

+ Beautifully designed, privacy-focused event scheduling.
+ Coordinate with friends without the chaos.

{/* Form Card */} -
+
@@ -229,26 +230,26 @@ export default function HomePage() { gridTemplateColumns: 'repeat(3, 1fr)', gap: 'var(--space-6)', marginTop: 'var(--space-12)', - color: 'white' + color: 'var(--text-secondary)' }}>
📅
-

Easy Scheduling

-

+

Easy Scheduling

+

Visual calendar for picking dates

🔗
-

Share Link

-

+

Share Link

+

No accounts required for guests

📊
-

Analytics

-

+

Analytics

+

See who's available when