mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-07 20:35:06 -05:00
feat: finish home page
This commit is contained in:
parent
697a5f2ed7
commit
be55ec13db
@ -57,6 +57,7 @@
|
|||||||
"contentlayer": "^0.3.4",
|
"contentlayer": "^0.3.4",
|
||||||
"cron": "^3.1.7",
|
"cron": "^3.1.7",
|
||||||
"discord.js": "^14.15.3",
|
"discord.js": "^14.15.3",
|
||||||
|
"framer-motion": "^12.7.4",
|
||||||
"github-slugger": "^2.0.0",
|
"github-slugger": "^2.0.0",
|
||||||
"inngest": "^3.21.2",
|
"inngest": "^3.21.2",
|
||||||
"input-otp": "^1.2.4",
|
"input-otp": "^1.2.4",
|
||||||
|
|||||||
@ -9354,6 +9354,15 @@ body {
|
|||||||
transform: rotate(-5deg) scale(0.9);
|
transform: rotate(-5deg) scale(0.9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes ripple {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(-50%, -50%) scale(0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
---break---
|
---break---
|
||||||
*/
|
*/
|
||||||
@ -9368,9 +9377,15 @@ body {
|
|||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
--animate-aurora: aurora 8s ease-in-out infinite alternate;
|
--animate-aurora: aurora 8s ease-in-out infinite alternate;
|
||||||
--animate-ripple: ripple var(--duration,2s) ease calc(var(--i, 0)*.2s) infinite;
|
--animate-ripple: ripple var(--duration,2s) ease calc(var(--i, 0)*.2s) infinite;
|
||||||
@keyframes ripple {
|
|
||||||
0%, 100% {
|
--animate-line-shadow: line-shadow 15s linear infinite;
|
||||||
transform: translate(-50%, -50%) scale(1);}
|
|
||||||
50% {
|
@keyframes line-shadow {
|
||||||
transform: translate(-50%, -50%) scale(0.9);}}
|
0% {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 100% -100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -5,100 +5,107 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import Github from "@/components/ui/github";
|
import Github from "@/components/ui/github";
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
|
const hideFooterPages = ["/home"];
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
return (
|
const pathname = usePathname();
|
||||||
<footer className="w-full border-t mt-15">
|
if (!hideFooterPages.includes(pathname ?? ""))
|
||||||
<div className="flex justify-between items-start p-[20px]">
|
return (
|
||||||
<span className="flex items-center gap-4 text-muted-foreground">
|
<footer className="w-full mt-15 border-t border-neutral-500/20 bg-neutral-100 dark:border-neutral-700/50 dark:bg-neutral-900 text-muted-foreground">
|
||||||
<Link href="Special:Root">
|
<div className="flex justify-between items-start p-[20px]">
|
||||||
<BrandingGenericIcon className="max-w-[32px] max-h-[32px]" />
|
<span className="flex items-center gap-4">
|
||||||
</Link>
|
<Link href="Special:Root">
|
||||||
<ul className="grid grid-cols-2 md:flex gap-4 p-0 m-0 w-full md:items-center items-start list-none">
|
<BrandingGenericIcon className="max-w-[32px] max-h-[32px]" />
|
||||||
<li className="text-sm">
|
|
||||||
<Link
|
|
||||||
href="/home"
|
|
||||||
className="text-muted-foreground hover:text-shadcn-primary transition-colors"
|
|
||||||
>
|
|
||||||
Home
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li className="text-sm">
|
|
||||||
<Link
|
|
||||||
href="/servers"
|
|
||||||
className="text-muted-foreground hover:text-shadcn-primary transition-colors"
|
|
||||||
>
|
|
||||||
Servers
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li className="text-sm">
|
|
||||||
<Link
|
|
||||||
href="/support"
|
|
||||||
className="text-muted-foreground hover:text-shadcn-primary transition-colors"
|
|
||||||
>
|
|
||||||
Contact
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</span>
|
|
||||||
<div className="block">
|
|
||||||
<div className="flex items-center mb-2 justify-end gap-2">
|
|
||||||
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger>
|
|
||||||
<Button variant="tertiary" size="square-md" className="flex items-center">
|
|
||||||
<Discord className="w-[1.25em] h-[1.25em]" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent>
|
|
||||||
<Link href="https://t.mhsf.app/d/m" noExtraIcons>
|
|
||||||
<DropdownMenuItem className="py-2 flex items-center gap-2">
|
|
||||||
<Image className="max-w-[30px] max-h-[30px] rounded border border-muted-foreground" src="https://avatars.githubusercontent.com/u/16529253?s=200&v=4" alt="Minehut" width={30} height={30} />
|
|
||||||
<span className="block">
|
|
||||||
Minehut Discord
|
|
||||||
<small className="flex">Not officially owned by MHSF, however conversations about MHSF and related take place there.</small>
|
|
||||||
</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
<Link href="https://t.mhsf.app/d/u" noExtraIcons>
|
|
||||||
<DropdownMenuItem className="py-2 flex items-center gap-2">
|
|
||||||
<BrandingGenericIcon className="max-w-[30px] max-h-[30px] rounded border border-muted-foreground" width={30} height={30} />
|
|
||||||
<span className="block">
|
|
||||||
MHSF Discord
|
|
||||||
<small className="flex">A read-only server for updates related to MHSF.</small>
|
|
||||||
</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<Link href="https://github.com/DeveloLongScript/MHSF" noExtraIcons>
|
|
||||||
<Button variant="tertiary" size="square-md" className="flex items-center">
|
|
||||||
<Github className="w-[1.25em] h-[1.25em]" />
|
|
||||||
</Button>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
<ul className="grid grid-cols-2 md:flex gap-4 p-0 m-0 w-full md:items-center items-start list-none">
|
||||||
|
<li className="text-sm">
|
||||||
|
<Link
|
||||||
|
href="/home"
|
||||||
|
className="text-muted-foreground hover:text-shadcn-primary transition-colors"
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li className="text-sm">
|
||||||
|
<Link
|
||||||
|
href="/servers"
|
||||||
|
className="text-muted-foreground hover:text-shadcn-primary transition-colors"
|
||||||
|
>
|
||||||
|
Servers
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li className="text-sm">
|
||||||
|
<Link
|
||||||
|
href="/support"
|
||||||
|
className="text-muted-foreground hover:text-shadcn-primary transition-colors"
|
||||||
|
>
|
||||||
|
Contact
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
<div className="block">
|
||||||
|
<div className="flex items-center mb-2 justify-end gap-2">
|
||||||
|
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<Button variant="tertiary" size="square-md" className="flex items-center">
|
||||||
|
<Discord className="w-[1.25em] h-[1.25em]" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<Link href="https://t.mhsf.app/d/m" noExtraIcons>
|
||||||
|
<DropdownMenuItem className="py-2 flex items-center gap-2">
|
||||||
|
<Image className="max-w-[30px] max-h-[30px] rounded border border-muted-foreground" src="https://avatars.githubusercontent.com/u/16529253?s=200&v=4" alt="Minehut" width={30} height={30} />
|
||||||
|
<span className="block">
|
||||||
|
Minehut Discord
|
||||||
|
<small className="flex">Not officially owned by MHSF, however conversations about MHSF and related take place there.</small>
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<Link href="https://t.mhsf.app/d/u" noExtraIcons>
|
||||||
|
<DropdownMenuItem className="py-2 flex items-center gap-2">
|
||||||
|
<BrandingGenericIcon className="max-w-[30px] max-h-[30px] rounded border border-muted-foreground" width={30} height={30} />
|
||||||
|
<span className="block">
|
||||||
|
MHSF Discord
|
||||||
|
<small className="flex">A read-only server for updates related to MHSF.</small>
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<Link href="https://github.com/DeveloLongScript/MHSF" noExtraIcons>
|
||||||
|
<Button variant="tertiary" size="square-md" className="flex items-center">
|
||||||
|
<Github className="w-[1.25em] h-[1.25em]" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<FooterStatus />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<FooterStatus />
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<span className="block px-4 -translate-y-12">
|
||||||
<span className="block border-t border-neutral-500/20 bg-neutral-100 px-7 py-10 dark:border-neutral-700/50 dark:bg-neutral-900">
|
<small className="text-[0.75rem]">
|
||||||
<small className="text-[0.75rem]">
|
MHSF is an open-source project licensed under the MIT license. MHSF is
|
||||||
MHSF is an open-source project licensed under the MIT license. MHSF is
|
not officially affiliated with with Minehut, Super League Enterprise,
|
||||||
not officially affiliated with with Minehut, Super League Enterprise,
|
or GamerSafer in any way. <br className="spacing-3" />
|
||||||
or GamerSafer in any way. <br className="spacing-3" />
|
Spamming, abusing or misusing the Minehut API and/or MHSF will get
|
||||||
Spamming, abusing or misusing the Minehut API and/or MHSF will get
|
your IP blocked, we are not responsible for IP blocks.{" "}
|
||||||
your IP blocked, we are not responsible for IP blocks.{" "}
|
<strong>You have been warned.</strong>
|
||||||
<strong>You have been warned.</strong>
|
<br className="spacing-3" />
|
||||||
<br className="spacing-3" />
|
If you're worried, please review the{" "}
|
||||||
If you're worried, please review the{" "}
|
<Link href="https://support.minehut.com/hc/en-us/articles/27075816947731-Minehut-Rules">
|
||||||
<Link href="https://support.minehut.com/hc/en-us/articles/27075816947731-Minehut-Rules">
|
Rules
|
||||||
Rules
|
</Link>
|
||||||
</Link>
|
, <Link href="https://minehut.com/terms-of-service">ToS</Link> &{" "}
|
||||||
, <Link href="https://minehut.com/terms-of-service">ToS</Link> &{" "}
|
<Link href="https://t.mhsf.app/pr">Platform Rules</Link>.
|
||||||
<Link href="https://t.mhsf.app/pr">Platform Rules</Link>.
|
</small>
|
||||||
</small>
|
</span>
|
||||||
</span>
|
</footer>
|
||||||
</footer>
|
);
|
||||||
);
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { AnimatePresence, motion } from "motion/react";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import React, {
|
import React, {
|
||||||
type ComponentPropsWithoutRef,
|
type ComponentPropsWithoutRef,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
|||||||
199
apps/www/src/components/feat/home-page/flickering-grid.tsx
Normal file
199
apps/www/src/components/feat/home-page/flickering-grid.tsx
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
|
interface FlickeringGridProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
squareSize?: number;
|
||||||
|
gridGap?: number;
|
||||||
|
flickerChance?: number;
|
||||||
|
color?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
className?: string;
|
||||||
|
maxOpacity?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FlickeringGrid: React.FC<FlickeringGridProps> = ({
|
||||||
|
squareSize = 4,
|
||||||
|
gridGap = 6,
|
||||||
|
flickerChance = 0.3,
|
||||||
|
color = "rgb(0, 0, 0)",
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
className,
|
||||||
|
maxOpacity = 0.3,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [isInView, setIsInView] = useState(false);
|
||||||
|
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
|
const memoizedColor = useMemo(() => {
|
||||||
|
const toRGBA = (color: string) => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return `rgba(0, 0, 0,`;
|
||||||
|
}
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = canvas.height = 1;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
if (!ctx) return "rgba(255, 0, 0,";
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.fillRect(0, 0, 1, 1);
|
||||||
|
const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data);
|
||||||
|
return `rgba(${r}, ${g}, ${b},`;
|
||||||
|
};
|
||||||
|
return toRGBA(color);
|
||||||
|
}, [color]);
|
||||||
|
|
||||||
|
const setupCanvas = useCallback(
|
||||||
|
(canvas: HTMLCanvasElement, width: number, height: number) => {
|
||||||
|
const dpr = window.devicePixelRatio || 1;
|
||||||
|
canvas.width = width * dpr;
|
||||||
|
canvas.height = height * dpr;
|
||||||
|
canvas.style.width = `${width}px`;
|
||||||
|
canvas.style.height = `${height}px`;
|
||||||
|
const cols = Math.floor(width / (squareSize + gridGap));
|
||||||
|
const rows = Math.floor(height / (squareSize + gridGap));
|
||||||
|
|
||||||
|
const squares = new Float32Array(cols * rows);
|
||||||
|
for (let i = 0; i < squares.length; i++) {
|
||||||
|
squares[i] = Math.random() * maxOpacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { cols, rows, squares, dpr };
|
||||||
|
},
|
||||||
|
[squareSize, gridGap, maxOpacity],
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateSquares = useCallback(
|
||||||
|
(squares: Float32Array, deltaTime: number) => {
|
||||||
|
for (let i = 0; i < squares.length; i++) {
|
||||||
|
if (Math.random() < flickerChance * deltaTime) {
|
||||||
|
squares[i] = Math.random() * maxOpacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[flickerChance, maxOpacity],
|
||||||
|
);
|
||||||
|
|
||||||
|
const drawGrid = useCallback(
|
||||||
|
(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
cols: number,
|
||||||
|
rows: number,
|
||||||
|
squares: Float32Array,
|
||||||
|
dpr: number,
|
||||||
|
) => {
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
ctx.fillStyle = "transparent";
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
for (let i = 0; i < cols; i++) {
|
||||||
|
for (let j = 0; j < rows; j++) {
|
||||||
|
const opacity = squares[i * rows + j];
|
||||||
|
ctx.fillStyle = `${memoizedColor}${opacity})`;
|
||||||
|
ctx.fillRect(
|
||||||
|
i * (squareSize + gridGap) * dpr,
|
||||||
|
j * (squareSize + gridGap) * dpr,
|
||||||
|
squareSize * dpr,
|
||||||
|
squareSize * dpr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[memoizedColor, squareSize, gridGap],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
const container = containerRef.current;
|
||||||
|
if (!canvas || !container) return;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
let animationFrameId: number;
|
||||||
|
let gridParams: ReturnType<typeof setupCanvas>;
|
||||||
|
|
||||||
|
const updateCanvasSize = () => {
|
||||||
|
const newWidth = width || container.clientWidth;
|
||||||
|
const newHeight = height || container.clientHeight;
|
||||||
|
setCanvasSize({ width: newWidth, height: newHeight });
|
||||||
|
gridParams = setupCanvas(canvas, newWidth, newHeight);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCanvasSize();
|
||||||
|
|
||||||
|
let lastTime = 0;
|
||||||
|
const animate = (time: number) => {
|
||||||
|
if (!isInView) return;
|
||||||
|
|
||||||
|
const deltaTime = (time - lastTime) / 1000;
|
||||||
|
lastTime = time;
|
||||||
|
|
||||||
|
updateSquares(gridParams.squares, deltaTime);
|
||||||
|
drawGrid(
|
||||||
|
ctx,
|
||||||
|
canvas.width,
|
||||||
|
canvas.height,
|
||||||
|
gridParams.cols,
|
||||||
|
gridParams.rows,
|
||||||
|
gridParams.squares,
|
||||||
|
gridParams.dpr,
|
||||||
|
);
|
||||||
|
animationFrameId = requestAnimationFrame(animate);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
updateCanvasSize();
|
||||||
|
});
|
||||||
|
|
||||||
|
resizeObserver.observe(container);
|
||||||
|
|
||||||
|
const intersectionObserver = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
setIsInView(entry.isIntersecting);
|
||||||
|
},
|
||||||
|
{ threshold: 0 },
|
||||||
|
);
|
||||||
|
|
||||||
|
intersectionObserver.observe(canvas);
|
||||||
|
|
||||||
|
if (isInView) {
|
||||||
|
animationFrameId = requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelAnimationFrame(animationFrameId);
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
intersectionObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, [setupCanvas, updateSquares, drawGrid, width, height, isInView]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className={cn(`h-full w-full ${className}`)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
className="pointer-events-none"
|
||||||
|
style={{
|
||||||
|
width: canvasSize.width,
|
||||||
|
height: canvasSize.height,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
64
apps/www/src/components/feat/home-page/flip-text.tsx
Normal file
64
apps/www/src/components/feat/home-page/flip-text.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { AnimatePresence, motion, Variants, MotionProps } from "framer-motion";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { ElementType } from "react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface FlipTextProps extends MotionProps {
|
||||||
|
/** The duration of the animation */
|
||||||
|
duration?: number;
|
||||||
|
/** The delay between each character */
|
||||||
|
delayMultiple?: number;
|
||||||
|
/** The variants of the animation */
|
||||||
|
framerProps?: Variants;
|
||||||
|
/** The class name of the component */
|
||||||
|
className?: string;
|
||||||
|
/** The element type of the component */
|
||||||
|
as?: ElementType;
|
||||||
|
/** The children of the component */
|
||||||
|
children: React.ReactNode;
|
||||||
|
/** The variants of the animation */
|
||||||
|
variants?: Variants;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultVariants: Variants = {
|
||||||
|
hidden: { rotateX: -90, opacity: 0 },
|
||||||
|
visible: { rotateX: 0, opacity: 1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FlipText({
|
||||||
|
children,
|
||||||
|
duration = 0.5,
|
||||||
|
delayMultiple = 0.08,
|
||||||
|
|
||||||
|
className,
|
||||||
|
as: Component = "span",
|
||||||
|
variants,
|
||||||
|
...props
|
||||||
|
}: FlipTextProps) {
|
||||||
|
const MotionComponent = motion.create(Component);
|
||||||
|
const characters = React.Children.toArray(children).join("").split("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{characters.map((char, i) => (
|
||||||
|
<MotionComponent
|
||||||
|
key={i}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
exit="hidden"
|
||||||
|
variants={variants || defaultVariants}
|
||||||
|
transition={{ duration, delay: i * delayMultiple }}
|
||||||
|
className={cn("origin-center drop-shadow-sm", className, char === " " ? "px-1" : "")}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{char}
|
||||||
|
</MotionComponent>
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
105
apps/www/src/components/feat/home-page/font-changer.tsx
Normal file
105
apps/www/src/components/feat/home-page/font-changer.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* MHSF, Minehut Server List
|
||||||
|
* All external content is rather licensed under the ECA Agreement
|
||||||
|
* located here: https://mhsf.app/docs/legal/external-content-agreement
|
||||||
|
*
|
||||||
|
* All code under MHSF is licensed under the MIT License
|
||||||
|
* by open source contributors
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 dvelo
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffectOnce } from "@/lib/useEffectOnce";
|
||||||
|
import {
|
||||||
|
Inter,
|
||||||
|
Roboto,
|
||||||
|
Montserrat,
|
||||||
|
Ubuntu_Condensed,
|
||||||
|
Ubuntu,
|
||||||
|
Poppins,
|
||||||
|
} from "next/font/google";
|
||||||
|
import { GeistSans } from "geist/font/sans";
|
||||||
|
import { useEffect, useMemo, useState, type JSX } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
const roboto = Roboto({
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: ["100", "300", "400", "500", "700", "900"],
|
||||||
|
});
|
||||||
|
const montserrat = Montserrat({
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: ["100", "300", "400", "500", "700", "900"],
|
||||||
|
});
|
||||||
|
const ubuntuCondensed = Ubuntu_Condensed({
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: ["400"],
|
||||||
|
});
|
||||||
|
const ubuntu = Ubuntu({
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: ["400"],
|
||||||
|
});
|
||||||
|
const poppins = Poppins({
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: ["400"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const fonts = [
|
||||||
|
inter.style,
|
||||||
|
roboto.style,
|
||||||
|
montserrat.style,
|
||||||
|
GeistSans.style,
|
||||||
|
ubuntuCondensed.style,
|
||||||
|
ubuntu.style,
|
||||||
|
poppins.style,
|
||||||
|
];
|
||||||
|
|
||||||
|
export function FontChanger({
|
||||||
|
children,
|
||||||
|
nounderline
|
||||||
|
}: { children: JSX.Element | JSX.Element[] | string, nounderline?: boolean }) {
|
||||||
|
const [position, setPosition] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setPosition((position) => {
|
||||||
|
if (position === fonts.length - 1) return 0;
|
||||||
|
return position + 1;
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
console.log(position);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-[1.2em] overflow-hidden flex items-center justify-center">
|
||||||
|
<span
|
||||||
|
className={cn("leading-[1.2] inline-block text-black dark:text-white font-normal h-[1.2em]", !nounderline ? "underline decoration-black dark:decoration-white" : "")}
|
||||||
|
style={fonts[position]}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -47,6 +47,13 @@ import { ExampleChart } from "./example-chart";
|
|||||||
import { Link } from "@/components/util/link";
|
import { Link } from "@/components/util/link";
|
||||||
import { type Avatar, AvatarCircles } from "./avatar-circles";
|
import { type Avatar, AvatarCircles } from "./avatar-circles";
|
||||||
import { Ripple } from "./ripple";
|
import { Ripple } from "./ripple";
|
||||||
|
import { FontChanger } from "./font-changer";
|
||||||
|
import { FlickeringGrid } from "./flickering-grid";
|
||||||
|
import { DiscordWordmark } from "@/components/ui/discord";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { LineShadowText } from "@/components/feat/home-page/line-shadow-text";
|
||||||
|
import { FlipText } from "./flip-text";
|
||||||
|
import { InteractiveHoverButton } from "./interactive-hover-button";
|
||||||
|
|
||||||
const getGitHubDetails = async () => {
|
const getGitHubDetails = async () => {
|
||||||
const githubRepo = await (
|
const githubRepo = await (
|
||||||
@ -71,6 +78,7 @@ export default function HomePageComponent() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { isSignedIn } = useUser();
|
const { isSignedIn } = useUser();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const shadowColor = theme.resolvedTheme === "dark" ? "white" : "black";
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
const [stars, setStars] = useState(0);
|
const [stars, setStars] = useState(0);
|
||||||
const [stargazers, setStargazers] = useState<Avatar[]>([]);
|
const [stargazers, setStargazers] = useState<Avatar[]>([]);
|
||||||
@ -117,7 +125,10 @@ export default function HomePageComponent() {
|
|||||||
<div className="absolute pointer-events-none inset-0 flex items-center justify-center dark:bg-[rgb(10,10,10)] bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)] " />
|
<div className="absolute pointer-events-none inset-0 flex items-center justify-center dark:bg-[rgb(10,10,10)] bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)] " />
|
||||||
|
|
||||||
<h1 className="bg-clip-text animate-fade-in -translate-y-4 bg-gradient-to-br from-black from-30% to-black/40 pb-6 text-5xl font-semibold tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] sm:text-5xl md:text-6xl lg:text-7xl dark:from-white dark:to-white/40">
|
<h1 className="bg-clip-text animate-fade-in -translate-y-4 bg-gradient-to-br from-black from-30% to-black/40 pb-6 text-5xl font-semibold tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] sm:text-5xl md:text-6xl lg:text-7xl dark:from-white dark:to-white/40">
|
||||||
The missing half of Minehut
|
The missing half of{" "}
|
||||||
|
<LineShadowText shadowColor="#407ce5" className="text-[#488aff]">
|
||||||
|
Minehut
|
||||||
|
</LineShadowText>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="animate-fade-in mb-12 -translate-y-4 text-balance text-lg tracking-tight text-neutral-400 opacity-0 [--animation-delay:400ms] md:text-xl ">
|
<p className="animate-fade-in mb-12 -translate-y-4 text-balance text-lg tracking-tight text-neutral-400 opacity-0 [--animation-delay:400ms] md:text-xl ">
|
||||||
MHSF is the next generation Minehut server list wrapper, with <br />
|
MHSF is the next generation Minehut server list wrapper, with <br />
|
||||||
@ -150,8 +161,18 @@ export default function HomePageComponent() {
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<p className="text-center w-full font-bold text-sm">
|
<p className="text-center w-full font-bold justify-center text-sm flex items-center gap-2">
|
||||||
An open-source unofficial project brought to you by dvelo
|
An open-source unofficial project brought to you by{" "}
|
||||||
|
<Link
|
||||||
|
href="https://giftedly.dev"
|
||||||
|
className="flex gap-1 items-center"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="https://avatars.githubusercontent.com/u/52332868?v=4"
|
||||||
|
className="w-[24px] h-[24px] rounded-full border"
|
||||||
|
/>
|
||||||
|
dvelo
|
||||||
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<div className="flex items-center justify-center border-b text-shadcn-primary/5 min-h-[50px] z-0">
|
<div className="flex items-center justify-center border-b text-shadcn-primary/5 min-h-[50px] z-0">
|
||||||
@ -228,10 +249,7 @@ export default function HomePageComponent() {
|
|||||||
.reverse()
|
.reverse()
|
||||||
.flat()
|
.flat()
|
||||||
.map((c) => (
|
.map((c) => (
|
||||||
<TypeScriptError
|
<TypeScriptError name={c.name} code={c.code} />
|
||||||
name={c.name}
|
|
||||||
code={c.code}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</AnimatedList>
|
</AnimatedList>
|
||||||
|
|
||||||
@ -306,10 +324,10 @@ export default function HomePageComponent() {
|
|||||||
entries.
|
entries.
|
||||||
</p>
|
</p>
|
||||||
</span>
|
</span>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<ExampleChart />
|
<ExampleChart />
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
</section>
|
</section>
|
||||||
<section className="md:flex relative overflow-hidden h-[500px] md:justify-center md:items-center px-8 w-full text-center border-b">
|
<section className="md:flex relative overflow-hidden h-[500px] md:justify-center md:items-center px-8 w-full text-center border-b">
|
||||||
<span>
|
<span>
|
||||||
@ -329,17 +347,145 @@ export default function HomePageComponent() {
|
|||||||
<AvatarCircles numPeople={stars} avatarUrls={stargazers} />
|
<AvatarCircles numPeople={stars} avatarUrls={stargazers} />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
</span>
|
</span>
|
||||||
<Ripple mainCircleSize={700}/>
|
<Ripple mainCircleSize={700} />
|
||||||
</section>
|
</section>
|
||||||
<div className="flex items-center justify-center border-b text-shadcn-primary/5 min-h-[50px] z-0">
|
<div className="flex items-center justify-center border-b text-shadcn-primary/5 min-h-[50px] z-0">
|
||||||
<Badge className="animate-fade-in my-2 rounded-xl px-4 py-2 relative z-1 text-shadcn-primary">
|
<Badge className="animate-fade-in my-2 rounded-xl px-4 py-2 relative z-1 text-shadcn-primary">
|
||||||
For server <AuroraText>owners</AuroraText>
|
For server <AuroraText>owners</AuroraText>
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
<section className="md:flex mt-15 md:justify-center md:items-center px-8 w-full text-center border-b">
|
||||||
|
<span>
|
||||||
|
<h1 className="animate-fade-in text-balance bg-gradient-to-br from-black from-30% to-black/40 bg-clip-text pb-6 text-2xl font-semibold leading-none tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] sm:text-2xl md:text-3xl lg:text-4xl dark:from-white dark:to-white/40">
|
||||||
|
Showcase <FontChanger>your server</FontChanger>
|
||||||
|
</h1>
|
||||||
|
<p className="animate-fade-in mb-6 mt-6 -translate-y-4 text-balance text-md tracking-tight text-gray-400 opacity-0 [--animation-delay:400ms] md:text-xl">
|
||||||
|
Make your server stand out from the crowd with options to <br />
|
||||||
|
display what your server is.
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
<section className="flex w-full border-b">
|
||||||
|
<div className="lg:flex hidden border-r w-[50px] h-[350px] text-shadcn-primary/5 bg-[size:10px_10px] [background-image:repeating-linear-gradient(315deg,currentColor_0_1px,#0000_0_50%)] " />
|
||||||
|
<div className="lg:grid grid-cols-3 max-h-[350px] max-lg:h-full w-full">
|
||||||
|
<Material className="border-0 relative p-0! max-h-[350px] overflow-hidden rounded-none bg-transparent! hover:bg-white! hover:dark:bg-zinc-900! max-lg:h-[200px] transition-all">
|
||||||
|
<img
|
||||||
|
src="https://i.imgur.com/5dUxh0c.png"
|
||||||
|
style={{
|
||||||
|
maskImage: "linear-gradient(to top, transparent, black)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="mt-auto absolute bottom-0 backdrop-blur-lg px-4 pt-2">
|
||||||
|
<strong className="animate-fade-in text-balance bg-gradient-to-br from-black from-30% to-black/40 bg-clip-text pb-6 font-semibold leading-none tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] dark:from-white dark:to-white/40">
|
||||||
|
Show. Not tell.
|
||||||
|
</strong>
|
||||||
|
<p className="animate-fade-in mb-6 text-balance tracking-tight text-gray-400 opacity-0 [--animation-delay:400ms]">
|
||||||
|
Use a static banner that can show what your server is about.
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
</Material>
|
||||||
|
<Material className="border-0 p-4 relative rounded-none border-r border-l bg-transparent! hover:bg-white! hover:dark:bg-zinc-900! max-lg:h-[200px] transition-all">
|
||||||
|
<div className="h-[calc(100%-100px)] w-[calc(100%-32px)] mx-4 my-2 rounded-lg bg-[#5865F2] max-2xl:hidden">
|
||||||
|
<span className="flex items-center justify-between text-white p-4">
|
||||||
|
<DiscordWordmark fill="white" className="h-[16px]" />
|
||||||
|
<Separator orientation="vertical" />
|
||||||
|
<span>
|
||||||
|
<strong>300k+</strong> online currently
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<div className="bg-[#454FBF] h-[calc(100%-50px)] w-full p-4 rounded-lg grid">
|
||||||
|
<Skeleton className="w-full h-[20px]" />
|
||||||
|
<Skeleton className="w-full h-[20px]" />
|
||||||
|
<Skeleton className="w-full h-[20px]" />
|
||||||
|
<Skeleton className="w-full h-[20px]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="mt-auto absolute bottom-0 backdrop-blur-lg">
|
||||||
|
<strong className="animate-fade-in text-balance bg-gradient-to-br from-black from-30% to-black/40 bg-clip-text pb-6 font-semibold leading-none tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] dark:from-white dark:to-white/40">
|
||||||
|
Link your community.
|
||||||
|
</strong>
|
||||||
|
<p className="animate-fade-in mb-6 text-balance tracking-tight text-gray-400 opacity-0 [--animation-delay:400ms]">
|
||||||
|
Quickly enable a embed of your Discord server to show
|
||||||
|
directly on your server page.
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
</Material>
|
||||||
|
<Material className="!p-0 z-0 relative border-0 rounded-none bg-transparent! hover:bg-white! hover:dark:bg-zinc-900! max-lg:h-[200px] transition-all">
|
||||||
|
<span className="h-[calc(100%-100px)] flex justify-center items-center my-auto">
|
||||||
|
<FlickeringGrid
|
||||||
|
className="absolute inset-0 z-0 w-full h-full"
|
||||||
|
squareSize={2}
|
||||||
|
gridGap={4}
|
||||||
|
color="#6B7280"
|
||||||
|
maxOpacity={0.5}
|
||||||
|
flickerChance={0.1}
|
||||||
|
/>
|
||||||
|
<Material className="relative z-0 w-[300px]">
|
||||||
|
<span className="text-2xl">
|
||||||
|
<FontChanger nounderline>
|
||||||
|
<AuroraText>My Server</AuroraText>
|
||||||
|
</FontChanger>
|
||||||
|
</span>
|
||||||
|
Lorem ipsum dolor sit amet consectetur adipiscing elit.
|
||||||
|
Consectetur adipiscing elit quisque faucibus ex sapien
|
||||||
|
vitae.
|
||||||
|
</Material>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="mt-auto absolute bottom-0 backdrop-blur-lg pl-4 pt-2">
|
||||||
|
<strong className="animate-fade-in text-balance bg-gradient-to-br from-black from-30% to-black/40 bg-clip-text pb-6 font-semibold leading-none tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] dark:from-white dark:to-white/40">
|
||||||
|
Whats your server?
|
||||||
|
</strong>
|
||||||
|
<p className="animate-fade-in mb-6 text-balance tracking-tight text-gray-400 opacity-0 [--animation-delay:400ms]">
|
||||||
|
Describe your server using Markdown to include bold, italic
|
||||||
|
& images directly in the description of your server.
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
</Material>
|
||||||
|
</div>
|
||||||
|
<div className="border-l lg:flex hidden w-[50px] h-[350px] text-shadcn-primary/5 bg-[size:10px_10px] [background-image:repeating-linear-gradient(315deg,currentColor_0_1px,#0000_0_50%)] " />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="flex w-full items-center justify-center py-16 text-center">
|
||||||
|
<span>
|
||||||
|
<h1>
|
||||||
|
<FlipText className="text-balance bg-gradient-to-br pb-6 text-2xl font-semibold leading-none tracking-tighter sm:text-2xl md:text-3xl lg:text-4xl text-black dark:text-white">
|
||||||
|
The modern server list
|
||||||
|
</FlipText>
|
||||||
|
</h1>
|
||||||
|
<p className="mb-6 text-balance tracking-tight text-gray-400">
|
||||||
|
Whether you are a player, data hunter or server owner, MHSF{" "}
|
||||||
|
<br />
|
||||||
|
always has a solution to the community side of Minehut.
|
||||||
|
</p>
|
||||||
|
<Link href="/servers">
|
||||||
|
<InteractiveHoverButton>Find servers</InteractiveHoverButton>
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<div className="hidden md:block relative sm:max-w-[1158px] mx-auto w-full -z-10 h-[300px] overflow-clip mt-[100px]">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"hidden md:block absolute left-1/2 z-10 -translate-x-1/2 translate-y-[125px]",
|
||||||
|
"text-center text-[255px] font-bold leading-none before:bg-gradient-to-b before:from-gray-200 before:dark:from-gray-700",
|
||||||
|
"before:to-gray-100/30 before:dark:to-gray-500/30 before:to-80% before:bg-clip-text before:text-transparent before:content-['MHSF']",
|
||||||
|
"after:absolute after:inset-0 after:bg-gray-300/70 after:dark:bg-gray-700/70 after:bg-clip-text after:text-transparent after:mix-blend-darken",
|
||||||
|
"after:content-['MHSF'] after:[text-shadow:0_1px_0_white] after:dark:[text-shadow:0_1px_0_black] dark:text-gray-700",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FlickeringGrid
|
||||||
|
className="opacity-80 absolute z-5 translate-y-[8px]"
|
||||||
|
color="#6B7280"
|
||||||
|
style={{
|
||||||
|
maskImage: "linear-gradient(to bottom, transparent, black)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { ArrowRightIcon } from "@radix-ui/react-icons";
|
||||||
|
|
||||||
|
interface InteractiveHoverButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
|
||||||
|
|
||||||
|
export const InteractiveHoverButton = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
InteractiveHoverButtonProps
|
||||||
|
>(({ children, className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"group relative w-auto cursor-pointer overflow-hidden rounded-full border bg-background p-2 px-6 text-center font-semibold",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="h-2 w-2 rounded-full bg-shadcn-primary transition-all duration-300 group-hover:scale-[100.8]" />
|
||||||
|
<span className="inline-block transition-all duration-300 group-hover:translate-x-12 group-hover:opacity-0">
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-0 z-10 flex h-full w-full translate-x-12 items-center justify-center gap-2 text-shadcn-primary-foreground opacity-0 transition-all duration-300 group-hover:-translate-x-5 group-hover:opacity-100">
|
||||||
|
<span>{children}</span>
|
||||||
|
<ArrowRightIcon />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
InteractiveHoverButton.displayName = "InteractiveHoverButton";
|
||||||
42
apps/www/src/components/feat/home-page/line-shadow-text.tsx
Normal file
42
apps/www/src/components/feat/home-page/line-shadow-text.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { motion, type MotionProps } from "framer-motion";
|
||||||
|
|
||||||
|
interface LineShadowTextProps
|
||||||
|
extends Omit<React.HTMLAttributes<HTMLElement>, keyof MotionProps>,
|
||||||
|
MotionProps {
|
||||||
|
shadowColor?: string;
|
||||||
|
as?: React.ElementType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LineShadowText({
|
||||||
|
children,
|
||||||
|
shadowColor = "black",
|
||||||
|
className,
|
||||||
|
as: Component = "span",
|
||||||
|
...props
|
||||||
|
}: LineShadowTextProps) {
|
||||||
|
const MotionComponent = motion.create(Component);
|
||||||
|
const content = typeof children === "string" ? children : null;
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
throw new Error("LineShadowText only accepts string content");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MotionComponent
|
||||||
|
style={{ "--shadow-color": shadowColor } as React.CSSProperties}
|
||||||
|
className={cn(
|
||||||
|
"relative z-0 inline-flex",
|
||||||
|
"after:absolute after:left-[0.04em] after:top-[0.04em] after:content-[attr(data-text)]",
|
||||||
|
"after:bg-[linear-gradient(45deg,transparent_45%,var(--shadow-color)_45%,var(--shadow-color)_55%,transparent_0)]",
|
||||||
|
"after:-z-10 after:bg-[length:0.06em_0.06em] after:bg-clip-text after:text-transparent",
|
||||||
|
"after:animate-line-shadow",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
data-text={content}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</MotionComponent>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -73,7 +73,8 @@ export function NavBar() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"w-screen h-[3rem] grid-cols-3 fixed z-10 flex",
|
"w-screen h-[3rem] grid-cols-3 fixed z-10 flex",
|
||||||
"items-center justify-self-start me-auto pl-4 flex-1 transition-all justify-between",
|
"items-center justify-self-start me-auto pl-4 flex-1 transition-all justify-between",
|
||||||
showBorder ? "border-b backdrop-blur-xl" : "",
|
"lg:top-0 max-lg:bottom-0",
|
||||||
|
showBorder ? "border-b backdrop-blur-xl" : "max-lg:border-b max-lg:backdrop-blur-xl",
|
||||||
pathname !== null && animatedTopbarPages.includes(pathname)
|
pathname !== null && animatedTopbarPages.includes(pathname)
|
||||||
? "[--animation-delay:1000ms] opacity-0 animate-fade-in"
|
? "[--animation-delay:1000ms] opacity-0 animate-fade-in"
|
||||||
: ""
|
: ""
|
||||||
@ -187,7 +188,7 @@ export function NavBar() {
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<SignedIn>
|
<SignedIn>
|
||||||
<div
|
<div
|
||||||
className="absolute right-0 z-10 h-full
|
className="absolute right-0 -z-10 h-full
|
||||||
overflow-hidden w-full ml-auto"
|
overflow-hidden w-full ml-auto"
|
||||||
style={{ borderRadius: "inherit" }}
|
style={{ borderRadius: "inherit" }}
|
||||||
>
|
>
|
||||||
|
|||||||
47
apps/www/src/components/ui/discord.tsx
Normal file
47
apps/www/src/components/ui/discord.tsx
Normal file
File diff suppressed because one or more lines are too long
21
yarn.lock
21
yarn.lock
@ -6456,15 +6456,6 @@ forwarded@0.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||||
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
|
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
|
||||||
|
|
||||||
framer-motion@^11.3.8:
|
|
||||||
version "11.18.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-11.18.2.tgz#0c6bd05677f4cfd3b3bdead4eb5ecdd5ed245718"
|
|
||||||
integrity sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==
|
|
||||||
dependencies:
|
|
||||||
motion-dom "^11.18.1"
|
|
||||||
motion-utils "^11.18.1"
|
|
||||||
tslib "^2.4.0"
|
|
||||||
|
|
||||||
framer-motion@^12.7.4:
|
framer-motion@^12.7.4:
|
||||||
version "12.7.4"
|
version "12.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-12.7.4.tgz#50aeb8b5b5a672dea931bdb74956d7b526bf0b4b"
|
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-12.7.4.tgz#50aeb8b5b5a672dea931bdb74956d7b526bf0b4b"
|
||||||
@ -9495,13 +9486,6 @@ mongodb@^6.12.0, mongodb@^6.8.0:
|
|||||||
bson "^6.10.3"
|
bson "^6.10.3"
|
||||||
mongodb-connection-string-url "^3.0.0"
|
mongodb-connection-string-url "^3.0.0"
|
||||||
|
|
||||||
motion-dom@^11.18.1:
|
|
||||||
version "11.18.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-11.18.1.tgz#e7fed7b7dc6ae1223ef1cce29ee54bec826dc3f2"
|
|
||||||
integrity sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==
|
|
||||||
dependencies:
|
|
||||||
motion-utils "^11.18.1"
|
|
||||||
|
|
||||||
motion-dom@^12.7.4:
|
motion-dom@^12.7.4:
|
||||||
version "12.7.4"
|
version "12.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-12.7.4.tgz#80f5f8d706e94bc29f6f4f4afa300ff9e1f976a2"
|
resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-12.7.4.tgz#80f5f8d706e94bc29f6f4f4afa300ff9e1f976a2"
|
||||||
@ -9509,11 +9493,6 @@ motion-dom@^12.7.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
motion-utils "^12.7.2"
|
motion-utils "^12.7.2"
|
||||||
|
|
||||||
motion-utils@^11.18.1:
|
|
||||||
version "11.18.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-11.18.1.tgz#671227669833e991c55813cf337899f41327db5b"
|
|
||||||
integrity sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==
|
|
||||||
|
|
||||||
motion-utils@^12.7.2:
|
motion-utils@^12.7.2:
|
||||||
version "12.7.2"
|
version "12.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-12.7.2.tgz#99b673d8851583b325bd0c8b0f04c5bf42b9b818"
|
resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-12.7.2.tgz#99b673d8851583b325bd0c8b0f04c5bf42b9b818"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user