mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-14 06:39:08 +02:00
Add confetti on valid key and make thank you less intrusive
This commit is contained in:
parent
b2faeb3c17
commit
23a68fbc10
5 changed files with 162 additions and 104 deletions
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue