added a little css

This commit is contained in:
NicholaiVogel 2025-10-12 18:41:59 -06:00
parent d6ae81d11a
commit 06fe062114
13 changed files with 744 additions and 63 deletions

137
package-lock.json generated
View File

@ -10,7 +10,9 @@
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.2.2", "@hookform/resolvers": "^5.2.2",
"@opennextjs/cloudflare": "^1.10.1", "@opennextjs/cloudflare": "^1.10.1",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-label": "^2.1.7", "@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-progress": "^1.1.7",
@ -19,6 +21,7 @@
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"framer-motion": "^12.23.24",
"lucide-react": "^0.545.0", "lucide-react": "^0.545.0",
"next": "15.5.4", "next": "15.5.4",
"open-next": "^3.1.3", "open-next": "^3.1.3",
@ -6009,6 +6012,37 @@
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@radix-ui/react-accordion": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz",
"integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-collapsible": "1.1.12",
"@radix-ui/react-collection": "1.1.7",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-direction": "1.1.1",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-arrow": { "node_modules/@radix-ui/react-arrow": {
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
@ -6032,6 +6066,36 @@
} }
} }
}, },
"node_modules/@radix-ui/react-collapsible": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz",
"integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-controllable-state": "1.2.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection": { "node_modules/@radix-ui/react-collection": {
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
@ -6206,6 +6270,37 @@
} }
} }
}, },
"node_modules/@radix-ui/react-hover-card": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz",
"integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-dismissable-layer": "1.1.11",
"@radix-ui/react-popper": "1.2.8",
"@radix-ui/react-portal": "1.1.9",
"@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-id": { "node_modules/@radix-ui/react-id": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
@ -10507,6 +10602,33 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/framer-motion": {
"version": "12.23.24",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
"integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.23.23",
"motion-utils": "^12.23.6",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fresh": { "node_modules/fresh": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
@ -12605,6 +12727,21 @@
"obliterator": "^1.6.1" "obliterator": "^1.6.1"
} }
}, },
"node_modules/motion-dom": {
"version": "12.23.23",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.23.6"
}
},
"node_modules/motion-utils": {
"version": "12.23.6",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

View File

@ -13,7 +13,9 @@
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.2.2", "@hookform/resolvers": "^5.2.2",
"@opennextjs/cloudflare": "^1.10.1", "@opennextjs/cloudflare": "^1.10.1",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-label": "^2.1.7", "@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-progress": "^1.1.7",
@ -22,6 +24,7 @@
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"framer-motion": "^12.23.24",
"lucide-react": "^0.545.0", "lucide-react": "^0.545.0",
"next": "15.5.4", "next": "15.5.4",
"open-next": "^3.1.3", "open-next": "^3.1.3",

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 KiB

View File

@ -59,6 +59,12 @@
--font-sans: var(--font-geist-sans); --font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono); --font-mono: var(--font-geist-mono);
--font-brand: var(--font-bebas); --font-brand: var(--font-bebas);
--font-orbitron: var(--font-orbitron);
--font-inter: var(--font-inter);
--font-jetbrains-mono: var(--font-jetbrains-mono);
--font-space-mono: var(--font-space-mono);
--font-rajdhani: var(--font-rajdhani);
--font-exo-2: var(--font-exo-2);
--color-background: hsl(var(--background)); --color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground)); --color-foreground: hsl(var(--foreground));
--color-border: hsl(var(--border)); --color-border: hsl(var(--border));
@ -67,7 +73,7 @@
body { body {
background: hsl(var(--background)); background: hsl(var(--background));
color: hsl(var(--foreground)); color: hsl(var(--foreground));
font-family: var(--font-sans), Arial, Helvetica, sans-serif; font-family: var(--font-jetbrains-mono), Arial, Helvetica, sans-serif;
} }
.font-brand { .font-brand {
@ -76,3 +82,87 @@ body {
letter-spacing: 0.02em; letter-spacing: 0.02em;
text-transform: uppercase; text-transform: uppercase;
} }
.font-orbitron {
font-family: var(--font-orbitron), var(--font-sans), Arial, Helvetica, sans-serif;
font-weight: 700;
letter-spacing: 0.05em;
}
.font-inter {
font-family: var(--font-inter), Arial, Helvetica, sans-serif;
}
.font-terminal {
font-family: var(--font-jetbrains-mono), monospace;
font-weight: 700;
letter-spacing: 0.02em;
text-transform: uppercase;
}
.font-space-mono {
font-family: var(--font-space-mono), monospace;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.font-rajdhani {
font-family: var(--font-rajdhani), sans-serif;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.font-exo-2 {
font-family: var(--font-exo-2), sans-serif;
font-weight: 900;
letter-spacing: 0.05em;
text-transform: uppercase;
}
/* Dot effect for black text - simplified approach */
.text-dots {
position: relative;
color: #000000;
background-image:
radial-gradient(circle, #000000 1px, transparent 1px);
background-size: 3px 3px;
background-position: 0 0;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Custom dotted border with solid corner dots */
.dotted-border-corners {
position: relative;
}
.dotted-border-corners::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px dotted white;
pointer-events: none;
}
.dotted-border-corners::after {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background:
/* Corner dots */
radial-gradient(circle, white 1px, transparent 1px) 0 0 / 4px 4px,
radial-gradient(circle, white 1px, transparent 1px) 100% 0 / 4px 4px,
radial-gradient(circle, white 1px, transparent 1px) 0 100% / 4px 4px,
radial-gradient(circle, white 1px, transparent 1px) 100% 100% / 4px 4px;
background-repeat: no-repeat;
pointer-events: none;
}

View File

@ -1,5 +1,5 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Geist, Geist_Mono, Bebas_Neue } from "next/font/google"; import { Geist, Geist_Mono, Bebas_Neue, Orbitron, Inter, JetBrains_Mono, Space_Mono, Rajdhani, Exo_2 } from "next/font/google";
import "./globals.css"; import "./globals.css";
import { Navigation } from "@/components/Navigation"; import { Navigation } from "@/components/Navigation";
import { Footer } from "@/components/Footer"; import { Footer } from "@/components/Footer";
@ -26,6 +26,51 @@ const bebasNeue = Bebas_Neue({
preload: true, preload: true,
}); });
const orbitron = Orbitron({
variable: "--font-orbitron",
subsets: ["latin"],
display: "swap",
preload: true,
});
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
display: "swap",
preload: true,
});
const jetbrainsMono = JetBrains_Mono({
variable: "--font-jetbrains-mono",
subsets: ["latin"],
display: "swap",
preload: true,
});
const spaceMono = Space_Mono({
variable: "--font-space-mono",
subsets: ["latin"],
weight: ["400", "700"],
display: "swap",
preload: true,
});
const rajdhani = Rajdhani({
variable: "--font-rajdhani",
subsets: ["latin"],
weight: ["300", "400", "500", "600", "700"],
display: "swap",
preload: true,
});
const exo2 = Exo_2({
variable: "--font-exo-2",
subsets: ["latin"],
weight: ["400", "700", "800", "900"],
display: "swap",
preload: true,
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Biohazard VFX - Visual Effects Studio", title: "Biohazard VFX - Visual Effects Studio",
description: "Creating stunning visual effects for commercials, music videos, and digital media. Expert VFX, motion graphics, compositing, and 3D animation services.", description: "Creating stunning visual effects for commercials, music videos, and digital media. Expert VFX, motion graphics, compositing, and 3D animation services.",
@ -79,7 +124,7 @@ export default function RootLayout({
/> />
</head> </head>
<body <body
className={`${geistSans.variable} ${geistMono.variable} ${bebasNeue.variable} antialiased bg-black text-white`} className={`${geistSans.variable} ${geistMono.variable} ${bebasNeue.variable} ${orbitron.variable} ${inter.variable} ${jetbrainsMono.variable} ${spaceMono.variable} ${rajdhani.variable} ${exo2.variable} antialiased bg-black text-white`}
> >
<main className="min-h-screen"> <main className="min-h-screen">
{children} {children}

View File

@ -0,0 +1,120 @@
"use client";
import { useEffect, useState, useRef } from "react";
interface CursorDotBackgroundProps {
dotSize?: number;
dotSpacing?: number;
fadeDistance?: number;
opacity?: number;
className?: string;
}
export function CursorDotBackground({
dotSize = 1,
dotSpacing = 20,
fadeDistance = 100,
opacity = 0.3,
className = "",
}: CursorDotBackgroundProps) {
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
const [isHovering, setIsHovering] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (containerRef.current) {
const rect = containerRef.current.getBoundingClientRect();
setMousePosition({
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
}
};
const handleMouseEnter = () => setIsHovering(true);
const handleMouseLeave = () => setIsHovering(false);
const container = containerRef.current;
if (container) {
container.addEventListener("mousemove", handleMouseMove);
container.addEventListener("mouseenter", handleMouseEnter);
container.addEventListener("mouseleave", handleMouseLeave);
}
return () => {
if (container) {
container.removeEventListener("mousemove", handleMouseMove);
container.removeEventListener("mouseenter", handleMouseEnter);
container.removeEventListener("mouseleave", handleMouseLeave);
}
};
}, []);
// Generate dots based on container size
const generateDots = () => {
if (!containerRef.current) return [];
const rect = containerRef.current.getBoundingClientRect();
const width = rect.width;
const height = rect.height;
const dots = [];
const cols = Math.ceil(width / dotSpacing);
const rows = Math.ceil(height / dotSpacing);
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const x = col * dotSpacing;
const y = row * dotSpacing;
// Calculate distance from mouse position
const distance = Math.sqrt(
Math.pow(x - mousePosition.x, 2) + Math.pow(y - mousePosition.y, 2)
);
// Calculate opacity based on distance and hover state
let dotOpacity = 0;
if (isHovering && distance <= fadeDistance) {
dotOpacity = opacity * (1 - distance / fadeDistance);
}
if (dotOpacity > 0) {
dots.push({
x,
y,
opacity: dotOpacity,
});
}
}
}
return dots;
};
return (
<div
ref={containerRef}
className={`absolute inset-0 pointer-events-none ${className}`}
style={{
backgroundImage: `radial-gradient(circle, rgba(255,255,255,0.1) 1px, transparent 1px)`,
backgroundSize: `${dotSpacing}px ${dotSpacing}px`,
}}
>
{generateDots().map((dot, index) => (
<div
key={index}
className="absolute rounded-full bg-white transition-opacity duration-150 ease-out"
style={{
left: dot.x,
top: dot.y,
width: dotSize,
height: dotSize,
opacity: dot.opacity,
transform: "translate(-50%, -50%)",
}}
/>
))}
</div>
);
}

View File

@ -0,0 +1,83 @@
"use client";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ChevronRight } from "lucide-react";
interface HorizontalAccordionProps {
trigger: string;
children: React.ReactNode;
className?: string;
}
export function HorizontalAccordion({
trigger,
children,
className = ""
}: HorizontalAccordionProps) {
const [isOpen, setIsOpen] = useState(false);
return (
<div className={`flex flex-col gap-4 ${className}`}>
{/* Trigger Button */}
<motion.button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-2 text-gray-300 hover:text-white transition-colors whitespace-nowrap text-lg font-medium"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
{trigger}
<motion.div
animate={{ rotate: isOpen ? 90 : 0 }}
transition={{ duration: 0.2 }}
>
<ChevronRight className="h-4 w-4" />
</motion.div>
</motion.button>
{/* Animated Content */}
<AnimatePresence mode="wait">
{isOpen && (
<motion.div
initial={{
height: 0,
opacity: 0,
y: -10
}}
animate={{
height: "auto",
opacity: 1,
y: 0
}}
exit={{
height: 0,
opacity: 0,
y: -10
}}
transition={{
duration: 0.4,
ease: [0.4, 0, 0.2, 1],
opacity: { duration: 0.3 }
}}
className="overflow-hidden w-full"
style={{ willChange: "height, opacity, transform" }}
>
<motion.div
initial={{ x: -20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -20, opacity: 0 }}
transition={{
duration: 0.3,
delay: 0.15,
ease: "easeOut"
}}
className="w-full max-w-2xl"
>
{children}
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}

View File

@ -1,67 +1,126 @@
"use client";
import { CursorDotBackground } from "./CursorDotBackground";
import { HorizontalAccordion } from "./HorizontalAccordion";
import { useEffect, useRef, useState } from "react";
export function TempPlaceholder() { export function TempPlaceholder() {
const titleRef = useRef<HTMLHeadingElement | null>(null);
const titleInnerRef = useRef<HTMLSpanElement | null>(null);
const bioTextRef = useRef<HTMLSpanElement | null>(null);
const [titleWidth, setTitleWidth] = useState<number | null>(null);
const [bioFontSizePx, setBioFontSizePx] = useState<number | null>(null);
const baseBioFontSizeRef = useRef<number | null>(null);
useEffect(() => {
const measure = () => {
const measuredTitleWidth = titleInnerRef.current?.offsetWidth ?? null;
setTitleWidth(measuredTitleWidth);
if (measuredTitleWidth && bioTextRef.current) {
const element = bioTextRef.current;
if (baseBioFontSizeRef.current === null) {
const initialFontSize = parseFloat(getComputedStyle(element).fontSize);
baseBioFontSizeRef.current = isNaN(initialFontSize) ? 16 : initialFontSize;
}
// Temporarily ensure we measure at base font size
const baseFontSize = baseBioFontSizeRef.current ?? 16;
const previousInlineFontSize = element.style.fontSize;
element.style.fontSize = `${baseFontSize}px`;
const bioNaturalWidth = element.offsetWidth;
// Restore previous inline style before we set state (will update after render)
element.style.fontSize = previousInlineFontSize;
if (bioNaturalWidth > 0) {
const scale = measuredTitleWidth / bioNaturalWidth;
setBioFontSizePx(baseFontSize * scale);
}
}
};
measure();
window.addEventListener("resize", measure);
return () => window.removeEventListener("resize", measure);
}, []);
return ( return (
<section className="py-16 bg-black text-white min-h-screen"> <section className="py-16 bg-black text-white min-h-screen">
<div className="container mx-auto px-4 max-w-3xl"> <div className="container mx-auto px-4 max-w-3xl">
<div className="p-8 relative">
<CursorDotBackground
dotSize={2}
dotSpacing={20}
fadeDistance={80}
opacity={0.5}
className="rounded"
/>
<div className="relative z-10">
<p className="text-sm text-gray-500 mb-6">10-12-2025</p> <p className="text-sm text-gray-500 mb-6">10-12-2025</p>
<h1 className="text-5xl font-bold mb-4 leading-tight"> <h1 ref={titleRef} className="text-5xl font-bold mb-4 leading-tight">
You've gotta be <em className="text-gray-400">fucking</em> me. <span ref={titleInnerRef} className="inline-block">
You've gotta be <em className="text-gray-400">fucking</em> me.
</span>
</h1> </h1>
<p className="text-lg mb-2 text-gray-300">This is the 20th fucking time this has happened.</p> <p className="text-lg mb-2 text-gray-300">This is the 20th fucking time this has happened.</p>
<p className="text-lg mb-12 text-gray-400"> <p className="text-lg mb-8 text-gray-400">
<em>Nicholai broke the website, again.</em> <em>Nicholai broke the website, again.</em>
</p> </p>
<hr className="border-gray-800 mb-4" /> <div className="mb-8">
<hr className="border-gray-800 mb-8" /> <HorizontalAccordion trigger="How did we get here?">
<div className="w-full">
<p className="mb-4 text-gray-400 text-sm">
<em>(TLDR: perfectionism is the mind killer)</em>
</p>
<ol className="list-decimal list-inside space-y-3 text-gray-300 leading-relaxed break-words">
<li>We needed a website (circa January 2023)</li>
<li>We tried to build one on squarespace (that shit sucks)</li>
<li>
Nicholai figured "I know some html and javascript, why not just{" "}
<em>make</em> one."
</li>
<li>
But of course, <strong>the html site sucked</strong> and was
difficult to host.
</li>
<li>
And naturally, the website for some reason <em>needed</em> to look
good.
</li>
<li>
So then began a longwinded journey of Nicholai learning <em>react</em>
</li>
<li>Nicholai should've stuck to python.</li>
</ol>
</div>
</HorizontalAccordion>
</div>
<p className="mb-2 text-lg"> <p className="mb-8 text-gray-400">Anyway, heres all you assholes need for right now:</p>
<strong>How did we get here?</strong> Lets break it down:
</p>
<p className="mb-6 text-gray-400">
<em>(TLDR: perfectionism is the mind killer)</em>
</p>
<ol className="list-decimal list-inside mb-8 space-y-3 text-gray-300 leading-relaxed"> <h1
<li>We needed a website (circa January 2023)</li> className="text-5xl font-black mb-6 font-exo-2 text-center mx-auto leading-none"
<li>We tried to build one on squarespace (that shit sucks)</li> style={{
<li> color: '#000000',
Nicholai figured "I know some html and javascript, why not just{" "} textShadow: '2px 2px 0px #ff4d00, 4px 4px 0px #ff4d00',
<em>make</em> one." width: titleWidth ? `${titleWidth}px` : undefined
</li> }}
<li> >
But of course, <strong>the html site sucked</strong> and was <span ref={bioTextRef} className="inline-block" style={{ fontSize: bioFontSizePx ? `${bioFontSizePx}px` : undefined }}>
difficult to host. BIOHAZARD
</li> </span>
<li> </h1>
And naturally, the website for some reason <em>needed</em> to look
good.
</li>
<li>
So then began a longwinded journey of Nicholai learning <em>react</em>
</li>
<li>Nicholai should've stuck to python.</li>
</ol>
<hr className="border-gray-800 mb-10" />
<p className="mb-10 text-gray-400">Anyway, heres all you assholes need for right now:</p>
<h1 className="text-6xl font-bold mb-6 tracking-wider">BIOHAZARD VFX</h1>
<p className="mb-8 text-lg text-gray-300"> <p className="mb-8 text-lg text-gray-300">
<strong>Who we are:</strong> artists and technical people, we're <strong>Who we are:</strong> artists and technical people, we're
better at VFX than we are at web design, I promise. better at VFX than we are at web design, I promise.
</p> </p>
<hr className="border-gray-800 mb-4" />
<hr className="border-gray-800 mb-8" />
<p className="mb-4 text-lg"> <p className="mb-4 text-lg">
<strong>Here's our reel:</strong>{" "} <strong>Here's our reel:</strong>{" "}
<a <a
href="https://f.io/Wgx3EAHu" href="https://f.io/Wgx3EAHu"
className="hover:underline" className="hover:underline transition-all duration-300 hover:brightness-110 hover:scale-105 inline-block"
style={{ color: '#ff6b35' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -79,15 +138,12 @@ export function TempPlaceholder() {
<strong>Some projects we've worked on:</strong> <strong>Some projects we've worked on:</strong>
</p> </p>
<hr className="border-gray-800 mb-4" />
<hr className="border-gray-800 mb-8" />
<ul className="list-disc list-inside mb-8 space-y-3 text-gray-300"> <ul className="list-disc list-inside mb-8 space-y-3 text-gray-300">
<li> <li>
<a <a
href="https://www.instagram.com/p/DMykfRxssMR/?utm_source=ig_web_copy_link&igsh=Nmg0YXFuOG9yNGE2" href="https://www.instagram.com/p/DMykfRxssMR/?utm_source=ig_web_copy_link&igsh=Nmg0YXFuOG9yNGE2"
className="hover:underline" className="hover:underline transition-all duration-200 hover:brightness-105 inline-block"
style={{ color: '#ff6b35' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -97,8 +153,8 @@ export function TempPlaceholder() {
<li> <li>
<a <a
href="https://www.youtube.com/watch?v=4QIZE708gJ4" href="https://www.youtube.com/watch?v=4QIZE708gJ4"
className="hover:underline" className="hover:underline transition-all duration-200 hover:brightness-105 inline-block"
style={{ color: '#ff6b35' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -108,8 +164,8 @@ export function TempPlaceholder() {
<li> <li>
<a <a
href="https://www.youtube.com/watch?v=z2tUpLHdd4M" href="https://www.youtube.com/watch?v=z2tUpLHdd4M"
className="hover:underline" className="hover:underline transition-all duration-200 hover:brightness-105 inline-block"
style={{ color: '#ff6b35' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -119,8 +175,8 @@ export function TempPlaceholder() {
<li> <li>
<a <a
href="https://www.youtube.com/watch?v=RCZ9wl1Up40" href="https://www.youtube.com/watch?v=RCZ9wl1Up40"
className="hover:underline" className="hover:underline transition-all duration-200 hover:brightness-105 inline-block"
style={{ color: '#ff6b35' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -130,8 +186,8 @@ export function TempPlaceholder() {
<li> <li>
<a <a
href="https://www.youtube.com/watch?v=yLxoVrFpLmQ" href="https://www.youtube.com/watch?v=yLxoVrFpLmQ"
className="hover:underline" className="hover:underline transition-all duration-200 hover:brightness-105 inline-block"
style={{ color: '#ff6b35' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -141,8 +197,8 @@ export function TempPlaceholder() {
<li> <li>
<a <a
href="https://www.youtube.com/watch?v=a2Zqdo9RbNs" href="https://www.youtube.com/watch?v=a2Zqdo9RbNs"
className="hover:underline" className="hover:underline transition-all duration-200 hover:brightness-105 inline-block"
style={{ color: '#ff6b35' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -156,8 +212,8 @@ export function TempPlaceholder() {
Here's our{" "} Here's our{" "}
<a <a
href="https://www.instagram.com/biohazardvfx/" href="https://www.instagram.com/biohazardvfx/"
className="hover:underline" className="hover:underline transition-all duration-200 hover:brightness-105 inline-block"
style={{ color: '#ff6b35' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -175,13 +231,15 @@ export function TempPlaceholder() {
<a <a
href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
className="hover:underline" className="hover:underline"
style={{ color: '#ff6b35' }} style={{ color: '#ff4d00' }}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
help@biohazardvfx.com help@biohazardvfx.com
</a> </a>
</p> </p>
</div>
</div>
</div> </div>
</section> </section>
); );

View File

@ -0,0 +1,57 @@
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

@ -0,0 +1,59 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription }

View File

@ -0,0 +1,29 @@
"use client"
import * as React from "react"
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
import { cn } from "@/lib/utils"
const HoverCard = HoverCardPrimitive.Root
const HoverCardTrigger = HoverCardPrimitive.Trigger
const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-hover-card-content-transform-origin]",
className
)}
{...props}
/>
))
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
export { HoverCard, HoverCardTrigger, HoverCardContent }