diff --git a/bun.lock b/bun.lock index 5ff8a259..36762674 100644 --- a/bun.lock +++ b/bun.lock @@ -24,7 +24,7 @@ "gray-matter": "^4.0.3", "lucide-react": "^0.559.0", "mathjs": "^14.6.0", - "motion": "^12.25.0", + "motion": "^12.29.2", "next": "16.1.3", "next-mdx-remote": "^5.0.0", "next-themes": "^0.4.6", @@ -831,7 +831,7 @@ "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], - "framer-motion": ["framer-motion@12.29.0", "", { "dependencies": { "motion-dom": "^12.29.0", "motion-utils": "^12.27.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-1gEFGXHYV2BD42ZPTFmSU9buehppU+bCuOnHU0AD18DKh9j4DuTx47MvqY5ax+NNWRtK32qIcJf1UxKo1WwjWg=="], + "framer-motion": ["framer-motion@12.29.2", "", { "dependencies": { "motion-dom": "^12.29.2", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-lSNRzBJk4wuIy0emYQ/nfZ7eWhqud2umPKw2QAQki6uKhZPKm2hRQHeQoHTG9MIvfobb+A/LbEWPJU794ZUKrg=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -1181,11 +1181,11 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - "motion": ["motion@12.29.0", "", { "dependencies": { "framer-motion": "^12.29.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rjB5CP2N9S2ESAyEFnAFMgTec6X8yvfxLNcz8n12gPq3M48R7ZbBeVYkDOTj8SPMwfvGIFI801SiPSr1+HCr9g=="], + "motion": ["motion@12.29.2", "", { "dependencies": { "framer-motion": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-jMpHdAzEDF1QQ055cB+1lOBLdJ6ialVWl6QQzpJI2OvmHequ7zFVHM2mx0HNAy+Tu4omUlApfC+4vnkX0geEOg=="], - "motion-dom": ["motion-dom@12.29.0", "", { "dependencies": { "motion-utils": "^12.27.2" } }, "sha512-3eiz9bb32yvY8Q6XNM4AwkSOBPgU//EIKTZwsSWgA9uzbPBhZJeScCVcBuwwYVqhfamewpv7ZNmVKTGp5qnzkA=="], + "motion-dom": ["motion-dom@12.29.2", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-/k+NuycVV8pykxyiTCoFzIVLA95Nb1BFIVvfSu9L50/6K6qNeAYtkxXILy/LRutt7AzaYDc2myj0wkCVVYAPPA=="], - "motion-utils": ["motion-utils@12.27.2", "", {}, "sha512-B55gcoL85Mcdt2IEStY5EEAsrMSVE2sI14xQ/uAdPL+mfQxhKKFaEag9JmfxedJOR4vZpBGoPeC/Gm13I/4g5Q=="], + "motion-utils": ["motion-utils@12.29.2", "", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], diff --git a/package.json b/package.json index 87839ac6..ab4ef4ef 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "gray-matter": "^4.0.3", "lucide-react": "^0.559.0", "mathjs": "^14.6.0", - "motion": "^12.25.0", + "motion": "^12.29.2", "next": "16.1.3", "next-mdx-remote": "^5.0.0", "next-themes": "^0.4.6", diff --git a/src/app/[lang]/(home)/page.tsx b/src/app/[lang]/(home)/page.tsx index 0b851b2a..2afebe23 100644 --- a/src/app/[lang]/(home)/page.tsx +++ b/src/app/[lang]/(home)/page.tsx @@ -4,6 +4,7 @@ import { BookIcon, MessageSquareIcon, ArrowUpRightIcon, + ArrowRightIcon, } from "lucide-react"; import { FlipWords } from "@/components/ui/flip-words"; import { Spotlight } from "@/components/ui/spotlight-new"; @@ -36,6 +37,7 @@ import { DiscordButton } from "./discord-button"; import { SponsorButton } from "./support-button"; import { GitInfoButton } from "@/components/git-info-button"; import { useState, ViewTransition, useMemo } from "react"; +import { GlowEffect } from "@/components/ui/glow-effect"; type ProjectType = "art" | "website" | "server" | "mod"; @@ -306,6 +308,20 @@ export default function HomePage() {
+
+ + +

{messages.home.title.split("{flipwords}")[0]}
diff --git a/src/components/ui/glow-effect.tsx b/src/components/ui/glow-effect.tsx new file mode 100644 index 00000000..815e4dd9 --- /dev/null +++ b/src/components/ui/glow-effect.tsx @@ -0,0 +1,151 @@ +'use client'; +import { cn } from '@/lib/utils'; +import { motion, Transition } from 'motion/react'; + +export type GlowEffectProps = { + className?: string; + style?: React.CSSProperties; + colors?: string[]; + mode?: + | 'rotate' + | 'pulse' + | 'breathe' + | 'colorShift' + | 'flowHorizontal' + | 'static'; + blur?: + | number + | 'softest' + | 'soft' + | 'medium' + | 'strong' + | 'stronger' + | 'strongest' + | 'none'; + transition?: Transition; + scale?: number; + duration?: number; +}; + +export function GlowEffect({ + className, + style, + colors = ['#FF5733', '#33FF57', '#3357FF', '#F1C40F'], + mode = 'rotate', + blur = 'medium', + transition, + scale = 1, + duration = 5, +}: GlowEffectProps) { + const BASE_TRANSITION = { + repeat: Infinity, + duration: duration, + ease: 'linear', + }; + + const animations = { + rotate: { + background: [ + `conic-gradient(from 0deg at 50% 50%, ${colors.join(', ')})`, + `conic-gradient(from 360deg at 50% 50%, ${colors.join(', ')})`, + ], + transition: { + ...(transition ?? BASE_TRANSITION), + }, + }, + pulse: { + background: colors.map( + (color) => + `radial-gradient(circle at 50% 50%, ${color} 0%, transparent 100%)` + ), + scale: [1 * scale, 1.1 * scale, 1 * scale], + opacity: [0.5, 0.8, 0.5], + transition: { + ...(transition ?? { + ...BASE_TRANSITION, + repeatType: 'mirror', + }), + }, + }, + breathe: { + background: [ + ...colors.map( + (color) => + `radial-gradient(circle at 50% 50%, ${color} 0%, transparent 100%)` + ), + ], + scale: [1 * scale, 1.05 * scale, 1 * scale], + transition: { + ...(transition ?? { + ...BASE_TRANSITION, + repeatType: 'mirror', + }), + }, + }, + colorShift: { + background: colors.map((color, index) => { + const nextColor = colors[(index + 1) % colors.length]; + return `conic-gradient(from 0deg at 50% 50%, ${color} 0%, ${nextColor} 50%, ${color} 100%)`; + }), + transition: { + ...(transition ?? { + ...BASE_TRANSITION, + repeatType: 'mirror', + }), + }, + }, + flowHorizontal: { + background: colors.map((color) => { + const nextColor = colors[(colors.indexOf(color) + 1) % colors.length]; + return `linear-gradient(to right, ${color}, ${nextColor})`; + }), + transition: { + ...(transition ?? { + ...BASE_TRANSITION, + repeatType: 'mirror', + }), + }, + }, + static: { + background: `linear-gradient(to right, ${colors.join(', ')})`, + }, + }; + + const getBlurClass = (blur: GlowEffectProps['blur']) => { + if (typeof blur === 'number') { + return `blur-[${blur}px]`; + } + + const presets = { + softest: 'blur-xs', + soft: 'blur-sm', + medium: 'blur-md', + strong: 'blur-lg', + stronger: 'blur-xl', + strongest: 'blur-xl', + none: 'blur-none', + }; + + return presets[blur as keyof typeof presets]; + }; + + return ( + + ); +}