From 340247cd773a14e0b787d4805f78c9657cfc7fad Mon Sep 17 00:00:00 2001 From: Aidan Date: Thu, 27 Mar 2025 22:32:13 -0400 Subject: [PATCH] feat: homepage design fixes and tweaks (animation, style, theme), add todo --- README.md | 2 +- app/page.tsx | 16 +- components/pages/main/AnimatedIcon.tsx | 35 +++ components/pages/main/FeatureCard.tsx | 48 ++-- components/pages/main/Pricing.tsx | 350 +++++++++++++++---------- package.json | 2 +- 6 files changed, 286 insertions(+), 167 deletions(-) create mode 100644 components/pages/main/AnimatedIcon.tsx diff --git a/README.md b/README.md index 029bb6a..6772eaf 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,6 @@ npx prisma migrate deploy # Deploy ## To-Do -* [X] Add documentation on .env +* [ ] Add theme switcher to home page * [ ] Implement security scans * [ ] Rate-limiting on API diff --git a/app/page.tsx b/app/page.tsx index d4d6745..3783897 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,7 +1,5 @@ import Hero from "@/components/pages/main/Hero" import FeatureCard from "@/components/pages/main/FeatureCard" -import { Mail, Lock, Disc3, Headset } from "lucide-react" -import { SiGitea, SiAuthentik } from "react-icons/si"; import Navbar from "@/components/pages/main/Navbar" import Footer from "@/components/pages/main/Footer" import PoweredBySection from "@/components/pages/main/PoweredBySection" @@ -12,34 +10,34 @@ export default function Home() { { title: "Email", description: "Free email service with webmail and antispam, powered by a custom docker-mailserver setup.", - icon: Mail, + iconName: "Mail", }, { title: "Password Manager", description: "Securely store and manage your passwords across devices with Vaultwarden.", - icon: Lock, + iconName: "Lock", }, { title: "Git", description: "Host your repositories and run actions free of charge on our Gitea instance.", - icon: SiGitea, + iconName: "SiGitea", }, { title: "Authentik", description: "A secure single-sign-on service for easy login to your other services.", - icon: SiAuthentik, + iconName: "SiAuthentik", }, { title: "Music", description: "Coming soon. Host your music on our community server and stream it everywhere", - icon: Disc3, + iconName: "Disc3", }, { title: "Support", description: "Administrators are standing by most of the day via our various support channels.", - icon: Headset, + iconName: "Headset", }, - ] + ] as const return (
diff --git a/components/pages/main/AnimatedIcon.tsx b/components/pages/main/AnimatedIcon.tsx new file mode 100644 index 0000000..a564c2b --- /dev/null +++ b/components/pages/main/AnimatedIcon.tsx @@ -0,0 +1,35 @@ +"use client" + +import { motion } from "framer-motion" +import React from "react" +import { Mail, Lock, Disc3, Headset } from "lucide-react" +import { SiGitea, SiAuthentik } from "react-icons/si" + +const iconMap = { + Mail, + Lock, + Disc3, + Headset, + SiGitea, + SiAuthentik, +} as const + +interface AnimatedIconProps { + iconName: keyof typeof iconMap + className?: string +} + +const AnimatedIcon = ({ iconName, className }: AnimatedIconProps) => { + const Icon = iconMap[iconName] + + return ( + + + + ) +} + +export default AnimatedIcon \ No newline at end of file diff --git a/components/pages/main/FeatureCard.tsx b/components/pages/main/FeatureCard.tsx index acf4881..f107713 100644 --- a/components/pages/main/FeatureCard.tsx +++ b/components/pages/main/FeatureCard.tsx @@ -1,28 +1,44 @@ +"use client" + import React from "react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { motion } from "framer-motion" +import AnimatedIcon from "./AnimatedIcon" interface FeatureCardProps { title: string description: string - icon: React.ElementType + iconName: Parameters[0]["iconName"] } -/* TODO: I plan to add a better animation in the future, hover effects are not - good here, in my opinion. */ - -const FeatureCard = ({ title, description, icon: Icon }: FeatureCardProps) => { +const FeatureCard = ({ title, description, iconName }: FeatureCardProps) => { return ( - - - - - {title} - - - - {description} - - + + + + + + {title} + + + + {description} + + + ) } diff --git a/components/pages/main/Pricing.tsx b/components/pages/main/Pricing.tsx index 0d148ee..f41125a 100644 --- a/components/pages/main/Pricing.tsx +++ b/components/pages/main/Pricing.tsx @@ -1,55 +1,203 @@ "use client" import type React from "react" -import { useState } from "react" import { Check, ChevronRight, Clock } from "lucide-react" import { Separator } from "@/components/ui/separator" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" -import { motion } from "framer-motion" import Link from "next/link" +import { motion, HTMLMotionProps } from "framer-motion" -export default function Pricing() { - const [hoveredCard, setHoveredCard] = useState(null) +interface PricingFeatures { + everything: string[] + storage: string[] + ai: string[] +} - const features = { - everything: [ - "Use anything and everything on LibreCloud", - "Unlimited Password/Secret Storage with Vaultwarden", - "4GB of Email Storage", - "Unlimited Git Repositories with Gitea", - "Unlimited Fair-Use Actions runs with Gitea", - "Priority support via Email/Telegram", - ], - storage: [ - "ZERO FEES - You pay the price we pay", - "Through some providers, we offer 24/7 monitoring", - "Several data storage providers to choose from", - "Clone/erase your entire disk at any time (coming soon)", - "Do anything with your extra slice... we'll setup email, services, and more at no additional cost", - ], - ai: [ - "Flexible options such as OpenRouter and Cloud VPS", - "Pay per token or hour at no additional cost from LibreCloud", - "Use our open-source chat interface with a range of providers", - "Use the latest models such as Anthropic's Claude 3.7 Sonnet and OpenAI's o3-mini" - ], +interface FeatureItemProps { + children: React.ReactNode +} + +interface PricingCardProps { + title: string + price: string + period: string + description: string + features: string[] + badge?: string + buttonText?: string + buttonIcon?: React.ReactNode + isComingSoon?: boolean + className?: string +} + +const cardVariants = { + initial: { + transform: "scale(1)", + transition: { + type: "spring", + stiffness: 300, + damping: 20, + layout: false + } + }, + hover: { + transform: "scale(1.02)", + transition: { + type: "spring", + stiffness: 300, + damping: 20, + layout: false + } } +} as const - const FeatureItem = ({ children }: { children: React.ReactNode }) => ( -
- - {children} +const textVariants = { + initial: { + color: "inherit", + transition: { duration: 0.2 } + }, + hover: { + color: "inherit", + transition: { duration: 0.2 } + } +} as const + +const features: PricingFeatures = { + everything: [ + "Use anything and everything on LibreCloud", + "Unlimited Password/Secret Storage with Vaultwarden", + "4GB of Email Storage", + "Unlimited Git Repositories with Gitea", + "Unlimited Fair-Use Actions runs with Gitea", + "Priority support via Email/Telegram", + ], + storage: [ + "ZERO FEES - You pay the price we pay", + "Through some providers, we offer 24/7 monitoring", + "Several data storage providers to choose from", + "Clone/erase your entire disk at any time (coming soon)", + "Do anything with your extra slice... we'll setup email, services, and more at no additional cost", + ], + ai: [ + "Flexible options such as OpenRouter and Cloud VPS", + "Pay per token or hour at no additional cost from LibreCloud", + "Use our open-source chat interface with a range of providers", + "Use the latest models such as Anthropic's Claude 3.7 Sonnet and OpenAI's o3-mini", + ], +} + +const FeatureItem: React.FC = ({ children }) => ( +
+
+
- ) + + {children} + +
+) - const cardVariants = { - default: { scale: 1, y: 0 }, - hover: { scale: 1.03, y: -8 }, - } +const PricingCard: React.FC = ({ + title, + price, + period, + description, + features, + badge, + buttonText, + buttonIcon, + isComingSoon, + className, +}) => ( + +
+
+
+ + {title} + + {badge && ( +
+ + {badge} + +
+ )} +
+
+ + {price} + + + {period} + +
+ + {description} + + {isComingSoon ? ( + + ) : buttonText ? ( + + + + ) : null} +
+ +
+ + What's included: + +
+ {features.map((feature, index) => ( + {feature} + ))} +
+
+
+
+) +export default function Pricing(): React.ReactElement { return ( -
+

Pricing You'll Love

@@ -59,112 +207,34 @@ export default function Pricing() {
- setHoveredCard(0)} - onMouseLeave={() => setHoveredCard(null)} - > -
-
-

Everything

- - Most Popular - -
-
- $0.00 - /mo -
-

- All the services we offer, completely free. -

- - - -
- {/* TODO: this seperator be improved in the future, i can't find a good color for this */} - -
-

What's included:

-
- {features.everything.map((feature, index) => ( - {feature} - ))} -
-
-
+ } + /> - setHoveredCard(1)} - onMouseLeave={() => setHoveredCard(null)} - > -
-

Storage

-
Starting at
-
- $0.117 - /GB -
-

Flexible storage options with no markup.

- -
- -
-

What's included:

-
- {features.storage.map((feature, index) => ( - {feature} - ))} -
-
-

- Calculator price rounded to nearest dollar and based on ElcroDigital provider -

-
-
-
+ - setHoveredCard(2)} - onMouseLeave={() => setHoveredCard(null)} - > -
-

Generative AI

-
Starting at
-
- $0.00 - /M tokens -
-

Access powerful AI models at the best price.

- -
- -
-

What's included:

-
- {features.ai.map((feature, index) => ( - {feature} - ))} -
-
-
+
diff --git a/package.json b/package.json index 7e55e8c..9f08953 100644 --- a/package.json +++ b/package.json @@ -29,10 +29,10 @@ "clsx": "^2.1.1", "cmdk": "1.0.0", "cookies-next": "^5.1.0", - "framer-motion": "^12.6.2", "geist": "^1.3.1", "js-cookie": "^3.0.5", "lucide-react": "^0.474.0", + "motion": "^12.6.2", "next": "^15.2.4", "next-auth": "^5.0.0-beta.25", "next-themes": "^0.4.6",