mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
added react flow to show the flow of better-t-stack, add customization option and more
This commit is contained in:
94
apps/web/components/BorderBeam.tsx
Normal file
94
apps/web/components/BorderBeam.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { type MotionStyle, type Transition, motion } from "motion/react";
|
||||
|
||||
interface BorderBeamProps {
|
||||
/**
|
||||
* The size of the border beam.
|
||||
*/
|
||||
size?: number;
|
||||
/**
|
||||
* The duration of the border beam.
|
||||
*/
|
||||
duration?: number;
|
||||
/**
|
||||
* The delay of the border beam.
|
||||
*/
|
||||
delay?: number;
|
||||
/**
|
||||
* The color of the border beam from.
|
||||
*/
|
||||
colorFrom?: string;
|
||||
/**
|
||||
* The color of the border beam to.
|
||||
*/
|
||||
colorTo?: string;
|
||||
/**
|
||||
* The motion transition of the border beam.
|
||||
*/
|
||||
transition?: Transition;
|
||||
/**
|
||||
* The class name of the border beam.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* The style of the border beam.
|
||||
*/
|
||||
style?: React.CSSProperties;
|
||||
/**
|
||||
* Whether to reverse the animation direction.
|
||||
*/
|
||||
reverse?: boolean;
|
||||
/**
|
||||
* The initial offset position (0-100).
|
||||
*/
|
||||
initialOffset?: number;
|
||||
}
|
||||
|
||||
export const BorderBeam = ({
|
||||
className,
|
||||
size = 50,
|
||||
delay = 0,
|
||||
duration = 6,
|
||||
colorFrom = "#ffaa40",
|
||||
colorTo = "#9c40ff",
|
||||
transition,
|
||||
style,
|
||||
reverse = false,
|
||||
initialOffset = 0,
|
||||
}: BorderBeamProps) => {
|
||||
return (
|
||||
<div className="pointer-events-none absolute inset-0 rounded-[inherit] border border-transparent [mask-clip:padding-box,border-box] [mask-composite:intersect] [mask-image:linear-gradient(transparent,transparent),linear-gradient(#000,#000)]">
|
||||
<motion.div
|
||||
className={cn(
|
||||
"absolute aspect-square",
|
||||
"bg-gradient-to-l from-[var(--color-from)] via-[var(--color-to)] to-transparent",
|
||||
className,
|
||||
)}
|
||||
style={
|
||||
{
|
||||
width: size,
|
||||
offsetPath: `rect(0 auto auto 0 round ${size}px)`,
|
||||
"--color-from": colorFrom,
|
||||
"--color-to": colorTo,
|
||||
...style,
|
||||
} as MotionStyle
|
||||
}
|
||||
initial={{ offsetDistance: `${initialOffset}%` }}
|
||||
animate={{
|
||||
offsetDistance: reverse
|
||||
? [`${100 - initialOffset}%`, `${-initialOffset}%`]
|
||||
: [`${initialOffset}%`, `${100 + initialOffset}%`],
|
||||
}}
|
||||
transition={{
|
||||
repeat: Number.POSITIVE_INFINITY,
|
||||
ease: "linear",
|
||||
duration,
|
||||
delay: -delay,
|
||||
...transition,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -10,6 +10,8 @@
|
||||
"postinstall": "fumadocs-mdx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xyflow/react": "^12.4.3",
|
||||
"framer-motion": "^12.4.3",
|
||||
"fumadocs-core": "15.0.6",
|
||||
"fumadocs-mdx": "11.5.3",
|
||||
"fumadocs-ui": "15.0.6",
|
||||
|
||||
60
apps/web/src/app/(home)/_components/CustomizableSection.tsx
Normal file
60
apps/web/src/app/(home)/_components/CustomizableSection.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { motion } from "framer-motion";
|
||||
import CustomizableStack from "./CustomizableStack";
|
||||
|
||||
export default function CustomizableSection() {
|
||||
return (
|
||||
<section className="w-full max-w-6xl mx-auto space-y-12 mt-24 relative">
|
||||
<div className="text-center space-y-6 relative z-10">
|
||||
<motion.h2
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-4xl md:text-5xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 pb-2"
|
||||
>
|
||||
Your Stack, Your Choice
|
||||
</motion.h2>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
className="space-y-4 max-w-3xl mx-auto"
|
||||
>
|
||||
<p className="text-xl text-gray-300 leading-relaxed">
|
||||
Better-T Stack comes with carefully selected defaults, but we
|
||||
understand one size doesn't fit all.
|
||||
<span className="text-purple-400 font-semibold">
|
||||
{" "}
|
||||
Customize your stack{" "}
|
||||
</span>
|
||||
while maintaining full type safety and integration.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-4 text-sm text-gray-400">
|
||||
<span className="px-3 py-1 bg-gray-800/50 rounded-full hover:bg-gray-800 transition-colors">
|
||||
Multiple Database Options
|
||||
</span>
|
||||
<span className="px-3 py-1 bg-gray-800/50 rounded-full hover:bg-gray-800 transition-colors">
|
||||
Flexible Authentication
|
||||
</span>
|
||||
<span className="px-3 py-1 bg-gray-800/50 rounded-full hover:bg-gray-800 transition-colors">
|
||||
Alternative ORMs
|
||||
</span>
|
||||
<span className="px-3 py-1 bg-gray-800/50 rounded-full hover:bg-gray-800 transition-colors">
|
||||
Framework Choices
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5, delay: 0.4 }}
|
||||
className="relative"
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-500/10 via-purple-500/10 to-pink-500/10 blur-3xl" />
|
||||
<CustomizableStack />
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
177
apps/web/src/app/(home)/_components/CustomizableStack.tsx
Normal file
177
apps/web/src/app/(home)/_components/CustomizableStack.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Background,
|
||||
type Connection,
|
||||
type Edge,
|
||||
ReactFlow,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
} from "@xyflow/react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { TechSelector } from "./TechSelector";
|
||||
import "@xyflow/react/dist/style.css";
|
||||
import { initialNodes } from "@/lib/constant";
|
||||
import { TechNodeComponent } from "./TechNodeComponent";
|
||||
|
||||
const initialEdges = [
|
||||
{ id: "bun-hono", source: "bun", target: "hono", animated: true },
|
||||
{ id: "bun-tanstack", source: "bun", target: "tanstack", animated: true },
|
||||
{ id: "hono-libsql", source: "hono", target: "libsql", animated: true },
|
||||
{ id: "libsql-drizzle", source: "libsql", target: "drizzle", animated: true },
|
||||
{
|
||||
id: "hono-better-auth",
|
||||
source: "hono",
|
||||
target: "better-auth",
|
||||
animated: true,
|
||||
},
|
||||
{ id: "bun-tailwind", source: "bun", target: "tailwind", animated: true },
|
||||
{
|
||||
id: "tailwind-shadcn",
|
||||
source: "tailwind",
|
||||
target: "shadcn",
|
||||
animated: true,
|
||||
},
|
||||
];
|
||||
|
||||
const nodeTypes = {
|
||||
techNode: TechNodeComponent,
|
||||
};
|
||||
|
||||
const CustomizableStack = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const [activeNodes, setActiveNodes] = useState({
|
||||
backend: "hono",
|
||||
database: "libsql",
|
||||
orm: "drizzle",
|
||||
auth: "better-auth",
|
||||
});
|
||||
|
||||
const handleTechSelect = useCallback(
|
||||
(category: string, techId: string) => {
|
||||
setActiveNodes((prev) => ({ ...prev, [category]: techId }));
|
||||
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => ({
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
isActive: node.data.isStatic
|
||||
? true
|
||||
: node.data.category === category
|
||||
? node.id === techId
|
||||
: node.data.isActive,
|
||||
},
|
||||
})),
|
||||
);
|
||||
|
||||
const sourceNodes = nodes.filter(
|
||||
(n) => n.data.isActive && !n.data.isStatic,
|
||||
);
|
||||
const targetNode = nodes.find((n) => n.id === techId);
|
||||
|
||||
if (!targetNode) return;
|
||||
|
||||
setEdges((eds) =>
|
||||
eds.filter(
|
||||
(edge) =>
|
||||
!nodes.some(
|
||||
(n) =>
|
||||
n.data.category === category &&
|
||||
(edge.source === n.id || edge.target === n.id),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
setEdges((eds) => [
|
||||
...eds,
|
||||
...sourceNodes.map((source) => ({
|
||||
id: `${source.id}-${techId}`,
|
||||
source: source.id,
|
||||
target: techId,
|
||||
animated: true,
|
||||
})),
|
||||
]);
|
||||
},
|
||||
[nodes, setNodes, setEdges],
|
||||
);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) => {
|
||||
const sourceNode = nodes.find((n) => n.id === connection.source);
|
||||
const targetNode = nodes.find((n) => n.id === connection.target);
|
||||
|
||||
if (!sourceNode || !targetNode) return;
|
||||
|
||||
if (sourceNode.data.group === targetNode.data.group) {
|
||||
return;
|
||||
}
|
||||
|
||||
const edgesToRemove = edges.filter((edge) => {
|
||||
const edgeTarget = nodes.find((n) => n.id === edge.target);
|
||||
return edgeTarget?.data.group === targetNode.data.group;
|
||||
});
|
||||
|
||||
setEdges((eds) => {
|
||||
const newEdges = eds.filter((edge) => !edgesToRemove.includes(edge));
|
||||
return [...newEdges, { ...(connection as Edge), animated: true }];
|
||||
});
|
||||
|
||||
setNodes((nds) =>
|
||||
nds.map((node) => ({
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
isActive:
|
||||
node.id === connection.source || node.id === connection.target
|
||||
? true
|
||||
: node.data.group !== targetNode.data.group
|
||||
? node.data.isActive
|
||||
: false,
|
||||
},
|
||||
})),
|
||||
);
|
||||
},
|
||||
[nodes, edges, setEdges, setNodes],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative w-full max-w-5xl mx-auto z-50">
|
||||
<TechSelector onSelect={handleTechSelect} activeNodes={activeNodes} />
|
||||
<div className="absolute top-4 right-4 z-50 max-w-xs bg-gray-950/10 p-4 rounded-xl border border-gray-800 backdrop-blur-3xl">
|
||||
<div className="text-sm text-gray-300">
|
||||
Select technologies from the left panel to customize your stack. The
|
||||
graph will automatically update connections.
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute inset-0 backdrop-blur-3xl bg-gradient-to-r from-blue-500/10 via-purple-500/10 to-pink-500/10 rounded-xl" />
|
||||
<div className="h-[600px] relative backdrop-blur-sm bg-gray-950/50 rounded-xl overflow-hidden border border-gray-800">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
nodeTypes={nodeTypes}
|
||||
fitView
|
||||
minZoom={1}
|
||||
maxZoom={1}
|
||||
zoomOnScroll={false}
|
||||
zoomOnPinch={false}
|
||||
preventScrolling={false}
|
||||
nodesConnectable={true}
|
||||
>
|
||||
<Background
|
||||
className="bg-gray-950/5"
|
||||
color="#1e293b"
|
||||
gap={12}
|
||||
size={1}
|
||||
/>
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomizableStack;
|
||||
@@ -56,7 +56,7 @@ const Navbar = () => {
|
||||
|
||||
<div
|
||||
className={`flex items-center backdrop-blur-md bg-white/5 rounded-full border border-white/10 py-1 px-1.5 text-sm relative transition-all duration-500 ease-out ${
|
||||
scrolled ? "w-[475px]" : "w-[335px]"
|
||||
scrolled ? "w-[480px]" : "w-[335px]"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
@@ -126,7 +126,7 @@ const Navbar = () => {
|
||||
style={{
|
||||
transform: scrolled ? "translateY(0)" : "translateY(-8px)",
|
||||
}}
|
||||
className={`text-gray-300 hover:text-white transition-all duration-300 py-2 px-4 rounded-full ${
|
||||
className={`text-violet-700 hover:text-white transition-all duration-300 py-2 px-4 rounded-full ${
|
||||
scrolled
|
||||
? "opacity-100 translate-y-0"
|
||||
: "opacity-0 pointer-events-none"
|
||||
|
||||
@@ -2,14 +2,14 @@ const SideCircles = () => {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className="h-[40vh] w-[40vw] rounded-full bg-violet-950/10 backdrop-blur-xl z-50 fixed top-1/2 -translate-y-1/2 -left-[30%]" />
|
||||
<div className="h-[50vh] w-[40vw] rounded-full bg-violet-950/10 backdrop-blur-xl z-40 fixed top-1/2 -translate-y-1/2 -left-[25%]" />
|
||||
<div className="h-[60vh] w-[40vw] rounded-full bg-violet-950/10 backdrop-blur-xl z-30 fixed top-1/2 -translate-y-1/2 -left-[20%]" />
|
||||
<div className="h-[50vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl z-40 fixed top-1/2 -translate-y-1/2 -left-[30%]" />
|
||||
<div className="h-[40vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl z-50 fixed top-1/2 -translate-y-1/2 -left-[35%]" />
|
||||
<div className="h-[60vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl z-30 fixed top-1/2 -translate-y-1/2 -left-[25%]" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="h-[40vh] w-[40vw] rounded-full bg-violet-950/10 backdrop-blur-xl z-50 fixed top-1/2 -translate-y-1/2 -right-[30%]" />
|
||||
<div className="h-[50vh] w-[40vw] rounded-full bg-violet-950/10 backdrop-blur-xl z-40 fixed top-1/2 -translate-y-1/2 -right-[25%]" />
|
||||
<div className="h-[60vh] w-[40vw] rounded-full bg-violet-950/10 backdrop-blur-xl z-30 fixed top-1/2 -translate-y-1/2 -right-[20%]" />
|
||||
<div className="h-[40vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl z-50 fixed top-1/2 -translate-y-1/2 -right-[35%]" />
|
||||
<div className="h-[50vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl z-40 fixed top-1/2 -translate-y-1/2 -right-[30%]" />
|
||||
<div className="h-[60vh] w-[40vw] rounded-full bg-gradient-to-b from-transparent via-violet-950/20 to-transparent backdrop-blur-xl z-30 fixed top-1/2 -translate-y-1/2 -right-[25%]" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -153,7 +153,7 @@ const TechConstellation = () => {
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative w-full h-[90vh] bg-gradient-to-b from-transparent via-gray-950 to-transparent overflow-hidden flex items-center justify-center"
|
||||
className="relative w-full h-[90vh] bg-gradient-to-b from-transparent mt-8 via-gray-950 to-transparent overflow-hidden flex items-center justify-center"
|
||||
>
|
||||
<div
|
||||
ref={centerRef}
|
||||
|
||||
63
apps/web/src/app/(home)/_components/TechNodeComponent.tsx
Normal file
63
apps/web/src/app/(home)/_components/TechNodeComponent.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Handle, Position } from "@xyflow/react";
|
||||
|
||||
const categoryIndicators = {
|
||||
core: "before:bg-amber-500",
|
||||
frontend: "before:bg-blue-500",
|
||||
backend: "before:bg-emerald-500",
|
||||
database: "before:bg-purple-500",
|
||||
auth: "before:bg-red-500",
|
||||
orm: "before:bg-orange-500",
|
||||
};
|
||||
|
||||
interface TechNodeData {
|
||||
category: keyof typeof categoryIndicators;
|
||||
isActive: boolean;
|
||||
label: string;
|
||||
description: string;
|
||||
isDefault?: boolean;
|
||||
isStatic?: boolean;
|
||||
}
|
||||
|
||||
export function TechNodeComponent({ data }: { data: TechNodeData }) {
|
||||
const baseStyles = `
|
||||
relative px-5 py-4 rounded-lg
|
||||
transition-all duration-300
|
||||
border border-white/20
|
||||
before:content-[''] before:absolute before:left-0 before:top-0 before:w-1.5 before:h-full
|
||||
before:rounded-l-xl ${categoryIndicators[data.category]}
|
||||
`;
|
||||
|
||||
const activeStyles = data.isActive
|
||||
? "opacity-100 bg-gradient-to-br from-indigo-900/10 to-violet-900/10"
|
||||
: "opacity-80 hover:opacity-95 bg-slate-800";
|
||||
|
||||
return (
|
||||
<div className="relative group">
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
className="!w-1.5 !h-1.5 !bg-indigo-400/70"
|
||||
/>
|
||||
|
||||
<div className={`${baseStyles} ${activeStyles} backdrop-blur-3xl`}>
|
||||
<div className="text-white font-medium text-sm tracking-wide mb-1.5">
|
||||
{data.label}
|
||||
</div>
|
||||
<div className="text-[11px] leading-relaxed text-white/80">
|
||||
{data.description}
|
||||
</div>
|
||||
{!data.isDefault && !data.isStatic && (
|
||||
<div className="text-[10px] text-indigo-200/70 mt-2 italic">
|
||||
Alternative Option
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
className="!w-1.5 !h-1.5 !bg-indigo-400/70"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
81
apps/web/src/app/(home)/_components/TechSelector.tsx
Normal file
81
apps/web/src/app/(home)/_components/TechSelector.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
type TechOption = {
|
||||
id: string;
|
||||
label: string;
|
||||
category: string;
|
||||
};
|
||||
|
||||
const techOptions: Record<string, TechOption[]> = {
|
||||
backend: [
|
||||
{ id: "hono", label: "Hono", category: "backend" },
|
||||
{ id: "express", label: "Express", category: "backend" },
|
||||
],
|
||||
database: [
|
||||
{ id: "libsql", label: "libSQL", category: "database" },
|
||||
{ id: "postgres", label: "PostgreSQL", category: "database" },
|
||||
],
|
||||
orm: [
|
||||
{ id: "drizzle", label: "Drizzle", category: "orm" },
|
||||
{ id: "prisma", label: "Prisma", category: "orm" },
|
||||
],
|
||||
auth: [
|
||||
{ id: "better-auth", label: "Better-Auth", category: "auth" },
|
||||
{ id: "no-auth", label: "No Auth", category: "auth" },
|
||||
],
|
||||
};
|
||||
|
||||
interface TechSelectorProps {
|
||||
onSelect: (category: string, techId: string) => void;
|
||||
activeNodes: Record<string, string>;
|
||||
}
|
||||
|
||||
export function TechSelector({ onSelect, activeNodes }: TechSelectorProps) {
|
||||
return (
|
||||
<div className="absolute top-4 left-4 z-50 space-y-4 bg-gray-950/10 p-4 rounded-xl border border-gray-800 backdrop-blur-3xl">
|
||||
<div className="text-sm font-medium text-gray-200">Customize Stack</div>
|
||||
{Object.entries(techOptions).map(([category, options]) => (
|
||||
<div key={category} className="space-y-2">
|
||||
<div className="text-xs text-gray-400 capitalize">{category}</div>
|
||||
<div className="flex gap-2">
|
||||
{options.map((option) => (
|
||||
<Badge
|
||||
key={option.id}
|
||||
variant="secondary"
|
||||
className={`cursor-pointer hover:bg-gray-700 ${
|
||||
activeNodes[category] === option.id &&
|
||||
"bg-blue-600 text-white"
|
||||
}`}
|
||||
onClick={() => onSelect(category, option.id)}
|
||||
>
|
||||
{option.label}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Badge = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
variant: "primary" | "secondary";
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<span
|
||||
className={`
|
||||
px-2 rounded-full py-1 text-xs font-medium,
|
||||
${className}
|
||||
`}
|
||||
{...props}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,8 @@ import { Poppins } from "next/font/google";
|
||||
import React from "react";
|
||||
import BackgroundGradients from "./_components/BackgroundGradients";
|
||||
import CodeContainer from "./_components/CodeContainer";
|
||||
import Featured from "./_components/FeaturedSection";
|
||||
import CustomizableSection from "./_components/CustomizableSection";
|
||||
// import Featured from "./_components/FeaturedSection";
|
||||
import NpmPackage from "./_components/NpmPackage";
|
||||
import SideCircles from "./_components/SideCircles";
|
||||
import Spotlight from "./_components/Spotlight";
|
||||
@@ -62,6 +63,7 @@ export default function HomePage() {
|
||||
</div>
|
||||
</div>
|
||||
<TerminalDisplay />
|
||||
|
||||
<div className="w-full max-w-6xl mx-auto space-y-12 mt-12">
|
||||
<div className="text-center space-y-6 relative z-10">
|
||||
<h2 className="text-4xl md:text-5xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-slate-700 via-gray-100 to-stone-600 pb-2">
|
||||
@@ -90,9 +92,9 @@ export default function HomePage() {
|
||||
</div>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-gray-900 via-gray-900/50 to-transparent -z-10 blur-xl" />
|
||||
</div>
|
||||
<TechConstellation />
|
||||
</div>
|
||||
<Featured />
|
||||
<TechConstellation />
|
||||
<CustomizableSection />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,3 +52,16 @@
|
||||
opacity: 0;
|
||||
animation: fadeInUp 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
.border-beam {
|
||||
animation: border-beam 3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes border-beam {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ServerCog,
|
||||
Workflow,
|
||||
} from "lucide-react";
|
||||
import type { TechNode } from "./types";
|
||||
|
||||
export const technologies = [
|
||||
{
|
||||
@@ -103,3 +104,161 @@ export const technologies = [
|
||||
left: "left-[6rem]",
|
||||
},
|
||||
];
|
||||
|
||||
export const initialNodes: TechNode[] = [
|
||||
{
|
||||
id: "bun",
|
||||
type: "techNode",
|
||||
position: { x: 536, y: 204 },
|
||||
data: {
|
||||
label: "Bun",
|
||||
category: "core",
|
||||
description: "Fast all-in-one JavaScript runtime",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "tanstack",
|
||||
type: "techNode",
|
||||
position: { x: 362, y: 296 },
|
||||
data: {
|
||||
label: "TanStack Router",
|
||||
category: "frontend",
|
||||
description: "Type-safe routing",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
group: "router",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "tailwind",
|
||||
type: "techNode",
|
||||
position: { x: 494, y: 379 },
|
||||
data: {
|
||||
label: "Tailwind CSS",
|
||||
category: "frontend",
|
||||
description: "Utility-first CSS framework",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
isStatic: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "shadcn",
|
||||
type: "techNode",
|
||||
position: { x: 358, y: 486 },
|
||||
data: {
|
||||
label: "shadcn/ui",
|
||||
category: "frontend",
|
||||
description: "Re-usable components",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
isStatic: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "hono",
|
||||
type: "techNode",
|
||||
position: { x: 700, y: 325 },
|
||||
data: {
|
||||
label: "Hono",
|
||||
category: "backend",
|
||||
description: "Ultrafast web framework",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
group: "backend",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "express",
|
||||
type: "techNode",
|
||||
position: { x: 897, y: 389 },
|
||||
data: {
|
||||
label: "Express",
|
||||
category: "backend",
|
||||
description: "Fast, unopinionated web framework",
|
||||
isDefault: false,
|
||||
isActive: false,
|
||||
group: "backend",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "libsql",
|
||||
type: "techNode",
|
||||
position: { x: 544, y: 532 },
|
||||
data: {
|
||||
label: "libSQL",
|
||||
category: "database",
|
||||
description: "SQLite-compatible database",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
group: "database",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "postgres",
|
||||
type: "techNode",
|
||||
position: { x: 318, y: 579 },
|
||||
data: {
|
||||
label: "PostgreSQL",
|
||||
category: "database",
|
||||
description: "Advanced SQL database",
|
||||
isDefault: false,
|
||||
isActive: false,
|
||||
group: "database",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "drizzle",
|
||||
type: "techNode",
|
||||
position: { x: 559, y: 651 },
|
||||
data: {
|
||||
label: "Drizzle",
|
||||
category: "orm",
|
||||
description: "TypeScript ORM",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
group: "orm",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "prisma",
|
||||
type: "techNode",
|
||||
position: { x: 707, y: 675 },
|
||||
data: {
|
||||
label: "Prisma",
|
||||
category: "orm",
|
||||
description: "Next-generation ORM",
|
||||
isDefault: false,
|
||||
isActive: false,
|
||||
group: "orm",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "better-auth",
|
||||
type: "techNode",
|
||||
position: { x: 770, y: 502 },
|
||||
data: {
|
||||
label: "Better-Auth",
|
||||
category: "auth",
|
||||
description: "Modern authentication",
|
||||
isDefault: true,
|
||||
isActive: true,
|
||||
group: "auth",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "no-auth",
|
||||
type: "techNode",
|
||||
position: { x: 950, y: 621 },
|
||||
data: {
|
||||
label: "No Auth",
|
||||
category: "auth",
|
||||
description: "No authentication needed",
|
||||
isDefault: false,
|
||||
isActive: false,
|
||||
group: "auth",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
32
apps/web/src/lib/types.ts
Normal file
32
apps/web/src/lib/types.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export type TechCategory =
|
||||
| "core"
|
||||
| "frontend"
|
||||
| "backend"
|
||||
| "database"
|
||||
| "auth"
|
||||
| "orm"
|
||||
| "router";
|
||||
|
||||
export interface TechNode {
|
||||
id: string;
|
||||
type: string;
|
||||
position: { x: number; y: number };
|
||||
data: {
|
||||
label: string;
|
||||
category: TechCategory;
|
||||
description: string;
|
||||
isDefault: boolean;
|
||||
alternatives?: string[];
|
||||
isActive: boolean;
|
||||
group?: TechCategory;
|
||||
isStatic?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TechEdge {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
type?: string;
|
||||
animated?: boolean;
|
||||
}
|
||||
48
bun.lock
48
bun.lock
@@ -39,6 +39,8 @@
|
||||
"name": "web",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@xyflow/react": "^12.4.3",
|
||||
"framer-motion": "^12.4.3",
|
||||
"fumadocs-core": "15.0.6",
|
||||
"fumadocs-mdx": "11.5.3",
|
||||
"fumadocs-ui": "15.0.6",
|
||||
@@ -448,6 +450,18 @@
|
||||
|
||||
"@types/acorn": ["@types/acorn@4.0.6", "", { "dependencies": { "@types/estree": "*" } }, "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ=="],
|
||||
|
||||
"@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
|
||||
|
||||
"@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="],
|
||||
|
||||
"@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
|
||||
|
||||
"@types/d3-selection": ["@types/d3-selection@3.0.11", "", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="],
|
||||
|
||||
"@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="],
|
||||
|
||||
"@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="],
|
||||
|
||||
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
||||
|
||||
"@types/degit": ["@types/degit@2.8.6", "", {}, "sha512-y0M7sqzsnHB6cvAeTCBPrCQNQiZe8U4qdzf8uBVmOWYap5MMTN/gB2iEqrIqFiYcsyvP74GnGD5tgsHttielFw=="],
|
||||
@@ -498,6 +512,10 @@
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||
|
||||
"@xyflow/react": ["@xyflow/react@12.4.3", "", { "dependencies": { "@xyflow/system": "0.0.51", "classcat": "^5.0.3", "zustand": "^4.4.0" }, "peerDependencies": { "react": ">=17", "react-dom": ">=17" } }, "sha512-oO50TIY4rbgOURK5pmvL4LwLOQdh6YkvrvOBZPBedltJ1TINCRp0FiyYKfYhLnaDcW8/aayvGtFpUcSkPQxpGg=="],
|
||||
|
||||
"@xyflow/system": ["@xyflow/system@0.0.51", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-cYnuM3oWQQjx2Rdz0LdZCnbUaWZdZDiik20kPDYsa5SIlq++ZDIcKiDF6a93ncfMv9Ej5GWfDkouE7bObrdRqQ=="],
|
||||
|
||||
"acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
@@ -596,6 +614,8 @@
|
||||
|
||||
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
||||
|
||||
"classcat": ["classcat@5.0.5", "", {}, "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="],
|
||||
|
||||
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
|
||||
|
||||
"cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="],
|
||||
@@ -634,6 +654,24 @@
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
|
||||
|
||||
"d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="],
|
||||
|
||||
"d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="],
|
||||
|
||||
"d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
|
||||
|
||||
"d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
|
||||
|
||||
"d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="],
|
||||
|
||||
"d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
|
||||
|
||||
"d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="],
|
||||
|
||||
"d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="],
|
||||
|
||||
"damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="],
|
||||
|
||||
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
||||
@@ -794,6 +832,8 @@
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" } }, "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg=="],
|
||||
|
||||
"framer-motion": ["framer-motion@12.4.3", "", { "dependencies": { "motion-dom": "^12.0.0", "motion-utils": "^12.0.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-rsMeO7w3dKyNG09o3cGwSH49iHU+VgDmfSSfsX+wfkO3zDA6WWkh4sUsMXd155YROjZP+7FTIhDrBYfgZeHjKQ=="],
|
||||
|
||||
"fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
|
||||
|
||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||
@@ -1184,6 +1224,10 @@
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"motion-dom": ["motion-dom@12.0.0", "", { "dependencies": { "motion-utils": "^12.0.0" } }, "sha512-CvYd15OeIR6kHgMdonCc1ihsaUG4MYh/wrkz8gZ3hBX/uamyZCXN9S9qJoYF03GqfTt7thTV/dxnHYX4+55vDg=="],
|
||||
|
||||
"motion-utils": ["motion-utils@12.0.0", "", {}, "sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA=="],
|
||||
|
||||
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
@@ -1566,6 +1610,8 @@
|
||||
|
||||
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
|
||||
|
||||
"use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
|
||||
@@ -1602,6 +1648,8 @@
|
||||
|
||||
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
||||
|
||||
"zustand": ["zustand@4.5.6", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ=="],
|
||||
|
||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||
|
||||
"@changesets/apply-release-plan/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
|
||||
|
||||
Reference in New Issue
Block a user