From 720d0014fb8049cc4a704243e6386ad493e19ec4 Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Sat, 27 Dec 2025 11:45:25 -0800 Subject: [PATCH] feat: backup current state before external storage --- .env.example | 2 + .eslintrc.json | 3 + app/globals.css | 154 + app/layout.tsx | 81 + app/page.tsx | 281 + next-env.d.ts | 5 + next.config.mjs | 9 + package-lock.json | 6169 +++++++++++++++++ package.json | 30 + postcss.config.js | 6 + public/.gitkeep | 0 spec-kit/accessibility-log.md | 7 + spec-kit/checklist.md | 40 +- spec-kit/decisions/0002-brand-guidelines.md | 350 +- spec-kit/decisions/0003-compliance-roadmap.md | 62 + spec-kit/decisions/0004-design-system.md | 84 + spec-kit/decisions/0005-performance-budget.md | 39 + .../decisions/0006-accessibility-approach.md | 36 + spec-kit/decisions/0007-analytics-stack.md | 33 + spec-kit/decisions/0008-messaging-strategy.md | 22 + spec-kit/project-plan.md | 8 + tailwind.config.ts | 23 + tsconfig.json | 26 + 23 files changed, 7157 insertions(+), 313 deletions(-) create mode 100644 .env.example create mode 100644 .eslintrc.json create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 next-env.d.ts create mode 100644 next.config.mjs create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/.gitkeep create mode 100644 spec-kit/accessibility-log.md create mode 100644 spec-kit/decisions/0003-compliance-roadmap.md create mode 100644 spec-kit/decisions/0004-design-system.md create mode 100644 spec-kit/decisions/0005-performance-budget.md create mode 100644 spec-kit/decisions/0006-accessibility-approach.md create mode 100644 spec-kit/decisions/0007-analytics-stack.md create mode 100644 spec-kit/decisions/0008-messaging-strategy.md create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4a78d17 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +NEXT_PUBLIC_PLAUSIBLE_DOMAIN=1000planets.cloud +NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXX diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..957cd15 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["next/core-web-vitals"] +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..5e50cfc --- /dev/null +++ b/app/globals.css @@ -0,0 +1,154 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground: 255 255 255; + --background: 8 10 24; + --accent: 139 92 246; + --soft: 226 232 240; + --glow: 148 163 184; + --font-body: var(--font-body, 'Space Grotesk', system-ui, sans-serif); + --font-heading: var(--font-heading, 'Dela Gothic One', system-ui, sans-serif); + --font-data: var(--font-data, 'JetBrains Mono', monospace); +} + +body { + font-family: var(--font-body); + color: rgb(var(--foreground)); + background: radial-gradient(circle at 10% 20%, rgba(79, 70, 229, 0.25), transparent 50%), + radial-gradient(circle at 80% 10%, rgba(239, 68, 68, 0.25), transparent 60%), + rgb(var(--background)); + min-height: 100vh; +} + +.font-heading { + font-family: var(--font-heading); +} + +.font-data { + font-family: var(--font-data); +} + +.hero-prism { + position: relative; + overflow: visible; +} + +.hero-prism::after { + content: ''; + position: absolute; + inset: -60px auto auto -40px; + width: 320px; + height: 320px; + border-radius: 999px; + background: radial-gradient(circle, rgba(59, 130, 246, 0.35), transparent 70%); + filter: blur(30px); + opacity: 0.75; +} + +.module-card { + background: rgba(15, 23, 42, 0.8); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 28px; + padding: 1.5rem; + backdrop-filter: blur(14px); + box-shadow: 0 25px 40px rgba(2, 6, 23, 0.7); + transition: transform 0.4s ease, border-color 0.4s ease; +} + +.module-card:hover { + border-color: rgba(139, 92, 246, 0.7); + transform: translateY(-4px); +} + +.floating-ring { + position: absolute; + width: 420px; + height: 420px; + border-radius: 999px; + background: radial-gradient(circle, rgba(139, 92, 246, 0.25), transparent 70%); + filter: blur(30px); + opacity: 0.35; + top: -120px; + right: 20%; + animation: orbitRing 10s ease-in-out infinite; +} + +.floating-ring--secondary { + width: 320px; + height: 320px; + background: radial-gradient(circle, rgba(59, 130, 246, 0.3), transparent 65%); + top: 200px; + left: 10%; + animation-duration: 12s; +} + +.shimmer { + position: relative; + overflow: hidden; +} + +.shimmer::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.22), transparent); + animation: shimmer 1.6s infinite; +} + +@keyframes shimmer { + 0% { + transform: translateX(-110%); + } + 100% { + transform: translateX(110%); + } +} + +.skeleton { + height: 20px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + position: relative; + overflow: hidden; +} + +.skeleton::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.28), transparent); + animation: shimmer 1.4s infinite; +} + +.svg-prism { + width: 90px; + height: 90px; + filter: drop-shadow(0 15px 40px rgba(234, 179, 8, 0.35)); + animation: rotatePrism 14s linear infinite; +} + +@keyframes rotatePrism { + from { + transform: rotateY(0deg) rotateZ(0deg); + } + to { + transform: rotateY(360deg) rotateZ(360deg); + } +} + +@keyframes orbitRing { + 0% { + transform: translateY(0) scale(1); + opacity: 0.35; + } + 50% { + transform: translateY(-20px) scale(1.05); + opacity: 0.6; + } + 100% { + transform: translateY(0) scale(1); + opacity: 0.35; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..3fe98d1 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,81 @@ +import type { Metadata } from 'next' +import Script from 'next/script' +import { Dela_Gothic_One, JetBrains_Mono, Space_Grotesk } from 'next/font/google' +import './globals.css' + +const spaceGrotesk = Space_Grotesk({ subsets: ['latin'], variable: '--font-body' }) +const delaGothic = Dela_Gothic_One({ subsets: ['latin'], variable: '--font-heading' }) +const jetbrains = JetBrains_Mono({ subsets: ['latin'], variable: '--font-data' }) + +const plausibleDomain = process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN || '1000planets.cloud' +const gaMeasurementId = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID + +const structuredData = { + '@context': 'https://schema.org', + '@type': 'Organization', + name: '1000 Planets', + url: 'https://1000planets.cloud', + logo: 'https://1000planets.cloud/logo.png', + sameAs: [ + 'https://www.linkedin.com/company/1000planets', + 'https://www.twitter.com/1000planets', + ], + description: + '1KP delivers AI-assisted marketing tools for small and micro businesses, keeping you in control of every customer touchpoint.', +} + +export const metadata: Metadata = { + title: '1000 Planets · AI tools for small & micro businesses', + description: + 'Post-marketing tools from 1KP that help small and micro teams capture leads, publish human-reviewed content, and launch five-page microsites fast.', + keywords: [ + 'small business marketing', + 'lead capture', + 'microsites', + 'AI content tools', + 'human-reviewed automation', + 'small business growth', + ], +} + +export const viewport = { width: 'device-width', initialScale: 1 } + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + + )} + + {children} + + + ) +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..11b1d21 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,281 @@ +'use client' + +import { motion } from 'framer-motion' +import { useMemo, useState } from 'react' + +const heroStats = [ + { label: 'Launch time', value: 'days', detail: 'From idea to a five-page microsite with forms and follow-up copy.' }, + { label: 'Mindshare', value: 'Small & micro businesses only', detail: 'Served with tools built for their pace, not enterprise bureaucracy.' }, + { label: 'Control', value: 'In your hands', detail: 'We never take over—just give you better drafts, faster loops, and steady oversight.' }, +] + +const moduleHighlights = [ + { + title: 'Lead journey blueprint', + copy: 'We map every touch—click, form, DM—so your follow-ups feel personal and focused.', + }, + { + title: 'Human-reviewed content', + copy: 'Blogs, emails, socials drafted by AI and approved by a human so the voice always sounds like you.', + }, + { + title: 'Microsite sprint', + copy: 'A five-page experience with lightweight storytelling, responsive forms, and quick updates.', + }, +] + +const addonModules = [ + { name: 'Social pulse', note: 'Pre-written slices you edit and send', stat: '3 weekly social loops' }, + { name: 'Conversation lab', note: 'Smart replies you customize', stat: '1 workflow per channel' }, + { name: 'Content studio', note: 'Blog drafts with on-brand tone', stat: '2 long-form pieces' }, +] + +export default function Home() { + const [formData, setFormData] = useState({ + name: '', + email: '', + company: '', + message: '', + }) + const [submitted, setSubmitted] = useState(false) + const [error, setError] = useState('') + + const isFormValid = useMemo( + () => formData.name.trim() && formData.email.includes('@') && formData.message.trim(), + [formData] + ) + + const handleChange = (field: keyof typeof formData) => (event: React.ChangeEvent) => { + setFormData((prev) => ({ ...prev, [field]: event.target.value })) + } + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault() + if (!isFormValid) { + setError('Please share a name, valid email, and your main objective.') + return + } + setError('') + setSubmitted(true) + } + + return ( +
+
+