diff --git a/package-lock.json b/package-lock.json
index 5a0f24a..c286669 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,9 @@
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@opennextjs/cloudflare": "^1.10.1",
+ "@radix-ui/react-accordion": "^1.2.12",
"@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-navigation-menu": "^1.2.14",
"@radix-ui/react-progress": "^1.1.7",
@@ -19,6 +21,7 @@
"@radix-ui/react-slot": "^1.2.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "framer-motion": "^12.23.24",
"lucide-react": "^0.545.0",
"next": "15.5.4",
"open-next": "^3.1.3",
@@ -6009,6 +6012,37 @@
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
"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": {
"version": "1.1.7",
"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": {
"version": "1.1.7",
"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": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
@@ -10507,6 +10602,33 @@
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
@@ -12605,6 +12727,21 @@
"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": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
diff --git a/package.json b/package.json
index 8c1c92e..e1305c2 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,9 @@
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@opennextjs/cloudflare": "^1.10.1",
+ "@radix-ui/react-accordion": "^1.2.12",
"@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-navigation-menu": "^1.2.14",
"@radix-ui/react-progress": "^1.1.7",
@@ -22,6 +24,7 @@
"@radix-ui/react-slot": "^1.2.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "framer-motion": "^12.23.24",
"lucide-react": "^0.545.0",
"next": "15.5.4",
"open-next": "^3.1.3",
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..9363a4f
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/favicon.png b/public/favicon.png
new file mode 100644
index 0000000..fba1fe7
Binary files /dev/null and b/public/favicon.png differ
diff --git a/public/logo apr transparent.png b/public/logo apr transparent.png
deleted file mode 100644
index 6da91a5..0000000
Binary files a/public/logo apr transparent.png and /dev/null differ
diff --git a/src/app/globals.css b/src/app/globals.css
index 46d9fd3..4919797 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -59,6 +59,12 @@
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--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-foreground: hsl(var(--foreground));
--color-border: hsl(var(--border));
@@ -67,7 +73,7 @@
body {
background: hsl(var(--background));
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 {
@@ -76,3 +82,87 @@ body {
letter-spacing: 0.02em;
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;
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 3f7c164..109a82e 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,5 +1,5 @@
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 { Navigation } from "@/components/Navigation";
import { Footer } from "@/components/Footer";
@@ -26,6 +26,51 @@ const bebasNeue = Bebas_Neue({
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 = {
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.",
@@ -79,7 +124,7 @@ export default function RootLayout({
/>
{children}
diff --git a/src/components/CursorDotBackground.tsx b/src/components/CursorDotBackground.tsx
new file mode 100644
index 0000000..56a995e
--- /dev/null
+++ b/src/components/CursorDotBackground.tsx
@@ -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(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 (
+
+ {generateDots().map((dot, index) => (
+
+ ))}
+
+ );
+}
diff --git a/src/components/HorizontalAccordion.tsx b/src/components/HorizontalAccordion.tsx
new file mode 100644
index 0000000..c64109a
--- /dev/null
+++ b/src/components/HorizontalAccordion.tsx
@@ -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 (
+
+ {/* Trigger Button */}
+
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}
+
+
+
+
+
+ {/* Animated Content */}
+
+ {isOpen && (
+
+
+ {children}
+
+
+ )}
+
+
+ );
+}
diff --git a/src/components/Temp-Placeholder.tsx b/src/components/Temp-Placeholder.tsx
index 0861f35..8e1f70f 100644
--- a/src/components/Temp-Placeholder.tsx
+++ b/src/components/Temp-Placeholder.tsx
@@ -1,67 +1,126 @@
+"use client";
+
+import { CursorDotBackground } from "./CursorDotBackground";
+import { HorizontalAccordion } from "./HorizontalAccordion";
+import { useEffect, useRef, useState } from "react";
+
export function TempPlaceholder() {
+ const titleRef = useRef(null);
+ const titleInnerRef = useRef(null);
+ const bioTextRef = useRef(null);
+ const [titleWidth, setTitleWidth] = useState(null);
+ const [bioFontSizePx, setBioFontSizePx] = useState(null);
+ const baseBioFontSizeRef = useRef(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 (
+
+
+
10-12-2025
-
- You've gotta be fucking me.
+
+
+ You've gotta be fucking me.
+
This is the 20th fucking time this has happened.
-
+
Nicholai broke the website, again.
-
-
+
+
+
+
+ (TLDR: perfectionism is the mind killer)
+
+
+ - We needed a website (circa January 2023)
+ - We tried to build one on squarespace (that shit sucks)
+ -
+ Nicholai figured "I know some html and javascript, why not just{" "}
+ make one."
+
+ -
+ But of course, the html site sucked and was
+ difficult to host.
+
+ -
+ And naturally, the website for some reason needed to look
+ good.
+
+ -
+ So then began a longwinded journey of Nicholai learning react
+
+ - Nicholai should've stuck to python.
+
+
+
+
-
- How did we get here? Lets break it down:
-
-
- (TLDR: perfectionism is the mind killer)
-
+
Anyway, heres all you assholes need for right now:
-
- - We needed a website (circa January 2023)
- - We tried to build one on squarespace (that shit sucks)
- -
- Nicholai figured "I know some html and javascript, why not just{" "}
- make one."
-
- -
- But of course, the html site sucked and was
- difficult to host.
-
- -
- And naturally, the website for some reason needed to look
- good.
-
- -
- So then began a longwinded journey of Nicholai learning react
-
- - Nicholai should've stuck to python.
-
-
-
-
-
Anyway, heres all you assholes need for right now:
-
-
BIOHAZARD VFX
+
+
+ BIOHAZARD
+
+
Who we are: artists and technical people, we're
better at VFX than we are at web design, I promise.
-
-
-
Here's our reel:{" "}
@@ -79,15 +138,12 @@ export function TempPlaceholder() {
Some projects we've worked on:
-
-
-
-
@@ -97,8 +153,8 @@ export function TempPlaceholder() {
-
@@ -108,8 +164,8 @@ export function TempPlaceholder() {
-
@@ -119,8 +175,8 @@ export function TempPlaceholder() {
-
@@ -130,8 +186,8 @@ export function TempPlaceholder() {
-
@@ -141,8 +197,8 @@ export function TempPlaceholder() {
-
@@ -156,8 +212,8 @@ export function TempPlaceholder() {
Here's our{" "}
@@ -175,13 +231,15 @@ export function TempPlaceholder() {
help@biohazardvfx.com
+
+
);
diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx
new file mode 100644
index 0000000..2f55a32
--- /dev/null
+++ b/src/components/ui/accordion.tsx
@@ -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,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+))
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
new file mode 100644
index 0000000..5afd41d
--- /dev/null
+++ b/src/components/ui/alert.tsx
@@ -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 & VariantProps
+>(({ className, variant, ...props }, ref) => (
+
+))
+Alert.displayName = "Alert"
+
+const AlertTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+AlertTitle.displayName = "AlertTitle"
+
+const AlertDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/src/components/ui/hover-card.tsx b/src/components/ui/hover-card.tsx
new file mode 100644
index 0000000..74efa6b
--- /dev/null
+++ b/src/components/ui/hover-card.tsx
@@ -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,
+ React.ComponentPropsWithoutRef
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+
+))
+HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
+
+export { HoverCard, HoverCardTrigger, HoverCardContent }