Add confetti on valid key and make thank you less intrusive

This commit is contained in:
grokdesigns 2025-04-07 21:15:24 -07:00
parent b2faeb3c17
commit 23a68fbc10
No known key found for this signature in database
GPG key ID: 1084CD111FEE75DD
5 changed files with 162 additions and 104 deletions

View file

@ -5,29 +5,41 @@ import confetti from "canvas-confetti";
export default function SupporterMessage({ tier }: { tier: string }) { export default function SupporterMessage({ tier }: { tier: string }) {
return ( return (
<div className="mt-4 text-center"> <div className="relative flex items-center space-x-2 whitespace-nowrap group">
<div <span
className="relative inline-block px-2 py-1 bg-white/10 backdrop-blur-md border border-white/20 rounded-md shadow-sm max-w-screen-sm mx-auto cursor-pointer" className="cursor-pointer"
onClick={(e) => { onClick={(e) => {
// Get the bounding box of the element // Get the bounding box of the element
const rect = ( const rect = (
e.target as HTMLElement e.target as HTMLElement
).getBoundingClientRect(); ).getBoundingClientRect();
// Calculate the origin based on the top center of the box // Trigger confetti centered on the word "Pangolin"
confetti({ confetti({
particleCount: 100, particleCount: 100,
spread: 70, spread: 70,
origin: { origin: {
x: (rect.left + rect.width / 2) / window.innerWidth, // Horizontal center of the box x: (rect.left + rect.width / 2) / window.innerWidth,
y: rect.top / window.innerHeight // Top of the box y: rect.top / window.innerHeight
} },
colors: ["#FFA500", "#FF4500", "#FFD700"]
}); });
}} }}
> >
<h2 className="text-sm font-semibold text-transparent bg-clip-text bg-gradient-to-r from-orange-400 via-orange-500 to-orange-600 hover:from-orange-600 hover:via-orange-500 hover:to-orange-400"> Pangolin
</span>
{/* SVG Star */}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
className="w-4 h-4 text-primary"
>
<path d="M12 .587l3.668 7.431 8.2 1.192-5.934 5.782 1.4 8.168L12 18.896l-7.334 3.864 1.4-8.168L.132 9.21l8.2-1.192z" />
</svg>
{/* Popover */}
<div className="absolute left-1/2 transform -translate-x-1/2 -top-10 hidden group-hover:block bg-white/10 backdrop-blur-md text-primary text-sm rounded-md shadow-lg px-4 py-2">
Thank you for supporting Pangolin as a {tier}! Thank you for supporting Pangolin as a {tier}!
</h2>
</div> </div>
</div> </div>
); );

View file

@ -2,20 +2,19 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 20 0.0% 10.0%; --foreground: 20 0% 10%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 20 0.0% 10.0%; --card-foreground: 20 0% 10%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 20 0.0% 10.0%; --popover-foreground: 20 0% 10%;
--primary: 24.6 95% 53.1%; --primary: 24.6 95% 53.1%;
--primary-foreground: 60 9.1% 97.8%; --primary-foreground: 60 9.1% 97.8%;
--secondary: 60 4.8% 95.9%; --secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%; --secondary-foreground: 24 9.8% 10%;
--muted: 60 4.8% 85.0%; --muted: 60 4.8% 85%;
--muted-foreground: 25 5.3% 44.7%; --muted-foreground: 25 5.3% 44.7%;
--accent: 60 4.8% 90%; --accent: 60 4.8% 90%;
--accent-foreground: 24 9.8% 10%; --accent-foreground: 24 9.8% 10%;
@ -33,24 +32,24 @@
} }
.dark { .dark {
--background: 20 0.0% 10.0%; --background: 20 0% 10%;
--foreground: 60 9.1% 97.8%; --foreground: 60 9.1% 97.8%;
--card: 20 0.0% 10.0%; --card: 20 0% 10%;
--card-foreground: 60 9.1% 97.8%; --card-foreground: 60 9.1% 97.8%;
--popover: 20 0.0% 10.0%; --popover: 20 0% 10%;
--popover-foreground: 60 9.1% 97.8%; --popover-foreground: 60 9.1% 97.8%;
--primary: 20.5 90.2% 48.2%; --primary: 20.5 90.2% 48.2%;
--primary-foreground: 60 9.1% 97.8%; --primary-foreground: 60 9.1% 97.8%;
--secondary: 12 6.5% 15.0%; --secondary: 12 6.5% 15%;
--secondary-foreground: 60 9.1% 97.8%; --secondary-foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 25.0%; --muted: 12 6.5% 25%;
--muted-foreground: 24 5.4% 63.9%; --muted-foreground: 24 5.4% 63.9%;
--accent: 12 2.5% 15.0%; --accent: 12 2.5% 15%;
--accent-foreground: 60 9.1% 97.8%; --accent-foreground: 60 9.1% 97.8%;
--destructive: 0 72.2% 50.6%; --destructive: 0 72.2% 50.6%;
--destructive-foreground: 60 9.1% 97.8%; --destructive-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 30.0%; --border: 12 6.5% 30%;
--input: 12 6.5% 35.0%; --input: 12 6.5% 35%;
--ring: 20.5 90.2% 48.2%; --ring: 20.5 90.2% 48.2%;
--chart-1: 220 70% 50%; --chart-1: 220 70% 50%;
--chart-2: 160 60% 45%; --chart-2: 160 60% 45%;
@ -60,7 +59,6 @@
} }
} }
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;
@ -70,4 +68,3 @@
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
} }

View file

@ -62,9 +62,15 @@ export default async function RootLayout({
{/* Footer */} {/* Footer */}
<footer className="hidden md:block w-full mt-12 py-3 mb-6 px-4"> <footer className="hidden md:block w-full mt-12 py-3 mb-6 px-4">
<div className="container mx-auto flex flex-wrap justify-center items-center h-3 space-x-4 text-sm text-neutral-400 dark:text-neutral-600"> <div className="container mx-auto flex flex-wrap justify-center items-center h-3 space-x-4 text-sm text-neutral-400 dark:text-neutral-600">
{supporterData?.tier ? (
<SupporterMessage
tier={supporterData.tier}
/>
) : (
<div className="flex items-center space-x-2 whitespace-nowrap"> <div className="flex items-center space-x-2 whitespace-nowrap">
<span>Pangolin</span> <span>Pangolin</span>
</div> </div>
)}
<Separator orientation="vertical" /> <Separator orientation="vertical" />
<a <a
href="https://fossorial.io/" href="https://fossorial.io/"
@ -114,11 +120,6 @@ export default async function RootLayout({
</> </>
)} )}
</div> </div>
{supporterData?.tier && (
<SupporterMessage
tier={supporterData.tier}
/>
)}
</footer> </footer>
</SupportStatusProvider> </SupportStatusProvider>
</EnvProvider> </EnvProvider>

View file

@ -47,6 +47,7 @@ import {
CardTitle CardTitle
} from "./ui/card"; } from "./ui/card";
import { Check, ExternalLink } from "lucide-react"; import { Check, ExternalLink } from "lucide-react";
import confetti from "canvas-confetti";
const formSchema = z.object({ const formSchema = z.object({
githubUsername: z githubUsername: z
@ -100,6 +101,7 @@ export default function SupporterStatus() {
return; return;
} }
// Trigger the toast
toast({ toast({
variant: "default", variant: "default",
title: "Valid Key", title: "Valid Key",
@ -107,6 +109,50 @@ export default function SupporterStatus() {
"Your supporter key has been validated. Thank you for your support!" "Your supporter key has been validated. Thank you for your support!"
}); });
// Fireworks-style confetti
const duration = 5 * 1000; // 5 seconds
const animationEnd = Date.now() + duration;
const defaults = {
startVelocity: 30,
spread: 360,
ticks: 60,
zIndex: 0,
colors: ["#FFA500", "#FF4500", "#FFD700"] // Orange hues
};
function randomInRange(min: number, max: number) {
return Math.random() * (max - min) + min;
}
const interval = setInterval(() => {
const timeLeft = animationEnd - Date.now();
if (timeLeft <= 0) {
clearInterval(interval);
return;
}
const particleCount = 50 * (timeLeft / duration);
// Launch confetti from two random horizontal positions
confetti({
...defaults,
particleCount,
origin: {
x: randomInRange(0.1, 0.3),
y: Math.random() - 0.2
}
});
confetti({
...defaults,
particleCount,
origin: {
x: randomInRange(0.7, 0.9),
y: Math.random() - 0.2
}
});
}, 250);
setPurchaseOptionsOpen(false); setPurchaseOptionsOpen(false);
setKeyOpen(false); setKeyOpen(false);
@ -177,7 +223,9 @@ export default function SupporterStatus() {
</p> </p>
<div className="py-6"> <div className="py-6">
<p className="mb-3 text-center">Please select the option that best suits you.</p> <p className="mb-3 text-center">
Please select the option that best suits you.
</p>
<div className="grid md:grid-cols-2 grid-cols-1 gap-8"> <div className="grid md:grid-cols-2 grid-cols-1 gap-8">
<Card> <Card>
<CardHeader> <CardHeader>

View file

@ -5,59 +5,59 @@ const config: Config = {
content: [ content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}", "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}", "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}", "./src/app/**/*.{js,ts,jsx,tsx,mdx}"
], ],
theme: { theme: {
extend: { extend: {
colors: { colors: {
background: 'hsl(var(--background))', background: "hsl(var(--background))",
foreground: 'hsl(var(--foreground))', foreground: "hsl(var(--foreground))",
card: { card: {
DEFAULT: 'hsl(var(--card))', DEFAULT: "hsl(var(--card))",
foreground: 'hsl(var(--card-foreground))' foreground: "hsl(var(--card-foreground))"
}, },
popover: { popover: {
DEFAULT: 'hsl(var(--popover))', DEFAULT: "hsl(var(--popover))",
foreground: 'hsl(var(--popover-foreground))' foreground: "hsl(var(--popover-foreground))"
}, },
primary: { primary: {
DEFAULT: 'hsl(var(--primary))', DEFAULT: "hsl(var(--primary))",
foreground: 'hsl(var(--primary-foreground))' foreground: "hsl(var(--primary-foreground))"
}, },
secondary: { secondary: {
DEFAULT: 'hsl(var(--secondary))', DEFAULT: "hsl(var(--secondary))",
foreground: 'hsl(var(--secondary-foreground))' foreground: "hsl(var(--secondary-foreground))"
}, },
muted: { muted: {
DEFAULT: 'hsl(var(--muted))', DEFAULT: "hsl(var(--muted))",
foreground: 'hsl(var(--muted-foreground))' foreground: "hsl(var(--muted-foreground))"
}, },
accent: { accent: {
DEFAULT: 'hsl(var(--accent))', DEFAULT: "hsl(var(--accent))",
foreground: 'hsl(var(--accent-foreground))' foreground: "hsl(var(--accent-foreground))"
}, },
destructive: { destructive: {
DEFAULT: 'hsl(var(--destructive))', DEFAULT: "hsl(var(--destructive))",
foreground: 'hsl(var(--destructive-foreground))' foreground: "hsl(var(--destructive-foreground))"
}, },
border: 'hsl(var(--border))', border: "hsl(var(--border))",
input: 'hsl(var(--input))', input: "hsl(var(--input))",
ring: 'hsl(var(--ring))', ring: "hsl(var(--ring))",
chart: { chart: {
'1': 'hsl(var(--chart-1))', "1": "hsl(var(--chart-1))",
'2': 'hsl(var(--chart-2))', "2": "hsl(var(--chart-2))",
'3': 'hsl(var(--chart-3))', "3": "hsl(var(--chart-3))",
'4': 'hsl(var(--chart-4))', "4": "hsl(var(--chart-4))",
'5': 'hsl(var(--chart-5))' "5": "hsl(var(--chart-5))"
} }
}, },
borderRadius: { borderRadius: {
lg: 'var(--radius)', lg: "var(--radius)",
md: 'calc(var(--radius) - 2px)', md: "calc(var(--radius) - 2px)",
sm: 'calc(var(--radius) - 4px)' sm: "calc(var(--radius) - 4px)"
} }
} }
}, },
plugins: [require("tailwindcss-animate")], plugins: [require("tailwindcss-animate")]
}; };
export default config; export default config;