mirror of
https://github.com/Such-Software/such.software.git
synced 2026-10-17 05:32:05 +02:00
feat: Initial Monorepo Setup for Such Software
- **Architecture**: Initialized Turborepo monorepo with pnpm workspace. - **Frontend**: Scaffolding for Next.js 14 app (apps/web) with Tailwind CSS v4 configuration. - **UI System**: Created shared UI package (@repo/ui) with Shadcn/UI primitives (Card, Button). - **Procedural Design**: - Implemented 'NebulaField' organic background animation for Home. - Implemented 'DataGrid' isometric background for Services. - Added 'DynamicBackground' for theme-aware transitions. - **Features**: - Responsive 'ThemeToggle' with View Transitions API support. - 'PortfolioGrid' component showcasing key projects (r/CryptoCurrency, etc.). - 'FounderSignature' component detailing accessibility focus. - 'ContactForm' with Zod validation + turnstile integration stub. - **Pages**: - Landing page with parallax/procedural visuals. - Services page highlighting Medusa v2 development capabilities. - **Clean Up**: Removed initial Medusa backend scaffold to focus on frontend delivery first.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@repo/ui",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"exports": {
|
||||
"./*": "./src/*.tsx"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/config": "workspace:*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"typescript": "^5.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.0",
|
||||
"framer-motion": "^11.0.0",
|
||||
"lucide-react": "^0.350.0",
|
||||
"tailwind-merge": "^2.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { cn } from "../lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "../lib/utils"
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
export { Card }
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
export const DataGrid = () => {
|
||||
const gridLines = Array.from({ length: 10 }).map((_, i) => i * 10);
|
||||
|
||||
return (
|
||||
<svg className="absolute inset-0 w-full h-full [mask-image:linear-gradient(to_bottom,transparent,black)] pointer-events-none z-0">
|
||||
{gridLines.map((pos, i) => (
|
||||
<motion.line
|
||||
key={`h-${i}`}
|
||||
x1="0" y1={`${pos}%`} x2="100%" y2={`${pos}%`}
|
||||
stroke="currentColor"
|
||||
strokeWidth="1"
|
||||
className="text-gray-200 dark:text-slate-800"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: 1 }}
|
||||
transition={{ duration: 2, delay: i * 0.1 }}
|
||||
/>
|
||||
))}
|
||||
{/* Animated "Data Packets" travelling lines */}
|
||||
<motion.rect
|
||||
width="2" height="2" fill="currentColor"
|
||||
className="text-blue-500 dark:text-blue-400"
|
||||
>
|
||||
<motion.animateMotion
|
||||
path="M 0 50 H 100"
|
||||
dur="5s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</motion.rect>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useEffect, useState } from "react";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
interface NebulaProps {
|
||||
className?: string;
|
||||
density?: number;
|
||||
themeMode?: 'light' | 'dark'; // customized prop
|
||||
}
|
||||
|
||||
export const NebulaField = ({ className, density = 15, themeMode = 'dark' }: NebulaProps) => {
|
||||
const [paths, setPaths] = useState<string[]>([]);
|
||||
|
||||
// Procedural generation: Create organic bezier curves
|
||||
useEffect(() => {
|
||||
const newPaths = Array.from({ length: density }).map(() => {
|
||||
// Random control points
|
||||
const startX = Math.random() * 100;
|
||||
const startY = Math.random() * 100;
|
||||
const cp1X = startX + (Math.random() - 0.5) * 50;
|
||||
const cp1Y = startY + (Math.random() - 0.5) * 50;
|
||||
const endX = Math.random() * 100;
|
||||
const endY = Math.random() * 100;
|
||||
|
||||
return `M ${startX} ${startY} Q ${cp1X} ${cp1Y} ${endX} ${endY}`;
|
||||
});
|
||||
setPaths(newPaths);
|
||||
}, [density]);
|
||||
|
||||
return (
|
||||
<div className={cn("absolute inset-0 overflow-hidden pointer-events-none z-0", className)}>
|
||||
<svg
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="none"
|
||||
className="w-full h-full opacity-40 dark:opacity-60"
|
||||
>
|
||||
{paths.map((d, i) => (
|
||||
<motion.path
|
||||
key={i}
|
||||
d={d}
|
||||
stroke="currentColor"
|
||||
strokeWidth={themeMode === 'light' ? 0.1 : 0.3}
|
||||
fill="none"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{
|
||||
pathLength: [0, 1, 0],
|
||||
opacity: [0, 0.8, 0]
|
||||
}}
|
||||
transition={{
|
||||
duration: 20 + Math.random() * 10, // Async timing
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
delay: Math.random() * 5
|
||||
}}
|
||||
className="text-slate-400 dark:text-emerald-400"
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user