feat: initial release of NASG

This commit is contained in:
UltraLionFr
2025-08-20 22:56:16 +02:00
parent 34af26c8ea
commit 586c1a9720
21 changed files with 540 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
"use client";
import { motion } from "framer-motion";
import { Check, Copy, RefreshCw } from "lucide-react";
type Props = {
secret: string;
copiedEnv: boolean;
onGenerate: () => void;
onCopyEnv: () => void;
};
export default function Actions({ secret, copiedEnv, onGenerate, onCopyEnv }: Props) {
return (
<motion.div
className="mt-8 flex flex-col items-stretch gap-3 sm:flex-row sm:items-center sm:justify-center"
initial={{ opacity: 0, scale: 0.97 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.3, duration: 0.5, ease: "easeOut" }}
>
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
onClick={onGenerate}
className="group inline-flex justify-center items-center gap-2 rounded-2xl border border-sky-500/30 bg-sky-500/10 px-5 py-2.5 text-sm font-semibold text-sky-300 ring-sky-500/40 transition hover:bg-sky-500/15 focus:outline-none focus:ring-4"
>
<RefreshCw className="h-4 w-4 transition group-hover:rotate-90" />
Generate
</motion.button>
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
onClick={onCopyEnv}
className="inline-flex justify-center items-center gap-2 rounded-2xl border border-white/10 bg-white/10 px-5 py-2.5 text-sm font-medium shadow-sm transition hover:bg-white/15"
aria-label="Copy .env line"
title={copiedEnv ? "Copied" : "Copy .env"}
>
<span className="font-semibold">.env</span>
{copiedEnv ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</motion.button>
</motion.div>
);
}
+12
View File
@@ -0,0 +1,12 @@
"use client";
export default function Background() {
return (
<div className="absolute inset-0 -z-10">
<div className="absolute inset-0 bg-gradient-to-br from-[#0f172a] via-[#1e293b] to-[#0f172a]" />
<div
className="absolute inset-0 bg-[url('data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2240%22 height=%2240%22 fill=%22none%22 stroke=%22%233459a1%22 stroke-width=%220.4%22><path d=%22M0 .5H40M.5 0V40%22/></svg>')] opacity-[0.05]"
/>
</div>
);
}
+21
View File
@@ -0,0 +1,21 @@
"use client";
import { FaHeart } from "react-icons/fa";
export default function Footer() {
return (
<footer className="fixed bottom-4 left-1/2 -translate-x-1/2 text-xs text-neutral-400 flex items-center gap-1">
<span>Made with</span>
<FaHeart className="text-red-500 animate-pulse" />
<span>by</span>
<a
href="https://ultralion.xyz"
target="_blank"
rel="noopener noreferrer"
className="font-medium text-white hover:text-sky-400 transition"
>
UltraLion
</a>
</footer>
);
}
+42
View File
@@ -0,0 +1,42 @@
"use client";
import { motion } from "framer-motion";
import Link from "next/link";
import { useState } from "react";
import { FaGithub } from "react-icons/fa";
export default function GithubLink() {
const [hover, setHover] = useState(false);
return (
<div className="fixed top-4 right-4 z-50">
<Link
href="https://github.com/UltraLionfr/nasg"
target="_blank"
rel="noopener noreferrer"
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
className="group relative flex items-center justify-center"
>
{/* Icône GitHub */}
<motion.div
whileHover={{ scale: 1.1, rotate: 5 }}
whileTap={{ scale: 0.95 }}
className="rounded-full border border-white/10 bg-black/60 p-2 backdrop-blur transition-colors group-hover:bg-black/80"
>
<FaGithub className="h-5 w-5 text-white" />
</motion.div>
{/* Tooltip au survol */}
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={hover ? { opacity: 1, y: 0 } : { opacity: 0, y: 10 }}
transition={{ duration: 0.2 }}
className="absolute right-10 top-1/2 -translate-y-1/2 whitespace-nowrap rounded-lg border border-white/10 bg-black/80 px-3 py-1.5 text-xs text-white shadow-lg backdrop-blur"
>
View on GitHub
</motion.div>
</Link>
</div>
);
}
+26
View File
@@ -0,0 +1,26 @@
"use client";
import { motion } from "framer-motion";
import { Shield } from "lucide-react";
export default function Header() {
return (
<motion.div
className="text-center"
initial={{ opacity: 0, y: -15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2, duration: 0.5, ease: "easeOut" }}
>
<div className="mx-auto mb-3 inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/10 px-3 py-1 text-xs font-medium text-neutral-200 backdrop-blur-sm">
<Shield className="h-3.5 w-3.5" />
secure 32 bytes Base64
</div>
<h1 className="text-3xl md:text-5xl font-extrabold tracking-tight text-white">
Auth secret generator
</h1>
<p className="mt-2 text-sm text-neutral-300">
Generate a cryptographically-secure secret for your Next.js apps.
</p>
</motion.div>
);
}
+43
View File
@@ -0,0 +1,43 @@
"use client";
import { AnimatePresence, motion } from "framer-motion";
import { Check, Copy } from "lucide-react";
type Props = {
secret: string;
copied: boolean;
onCopy: () => void;
};
export default function SecretCard({ secret, copied, onCopy }: Props) {
return (
<div className="mt-6 rounded-2xl border border-white/10 bg-black/40 text-white shadow-md">
<div className="flex items-center gap-3 p-4">
<AnimatePresence mode="wait">
<motion.div
key={secret}
className="flex-1 max-h-24 overflow-y-auto break-all font-mono text-sm opacity-90"
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 8 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
{secret}
</motion.div>
</AnimatePresence>
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
onClick={onCopy}
className="inline-flex items-center justify-center rounded-xl border border-white/10 bg-white/5 p-2 transition hover:bg-white/10"
aria-label="Copy secret"
title={copied ? "Copied" : "Copy"}
>
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</motion.button>
</div>
</div>
);
}
+21
View File
@@ -0,0 +1,21 @@
"use client";
import { motion } from "framer-motion";
import { Lock } from "lucide-react";
export default function SecurityNotice() {
return (
<motion.div
className="mt-4 flex items-start gap-2 rounded-xl border border-sky-500/30 bg-black/50 p-3 font-mono text-xs text-sky-300"
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4, duration: 0.5, ease: "easeOut" }}
>
<Lock className="h-4 w-4 mt-0.5 shrink-0 text-sky-400" />
<p>
No secrets generated are saved or transmitted.
Everything is generated <span className="text-white font-semibold">locally</span> in your browser.
</p>
</motion.div>
);
}