Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b564568818 | |||
| effdebac4f | |||
| d7a6771035 | |||
| 1b7f30d181 | |||
| 8fd46b134d | |||
| 76db6f7ee5 | |||
| 55b35cc4c8 |
47
src/app/demo.tsx
Normal file
47
src/app/demo.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demo file for WebGLShader and LiquidButton components
|
||||||
|
* This file demonstrates the import and usage of advanced UI components
|
||||||
|
* Dependencies: three, @radix-ui/react-slot, class-variance-authority
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Import WebGLShader component
|
||||||
|
// Note: Update the path based on actual component location
|
||||||
|
// import WebGLShader from "@/components/webgl/WebGLShader";
|
||||||
|
|
||||||
|
// Import LiquidButton component
|
||||||
|
// Note: Update the path based on actual component location
|
||||||
|
// import LiquidButton from "@/components/button/LiquidButton";
|
||||||
|
|
||||||
|
// Example usage (commented out as components may not exist yet):
|
||||||
|
/*
|
||||||
|
export default function Demo() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-8 p-8">
|
||||||
|
<h1 className="text-3xl font-bold">Component Demo</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">WebGLShader Component</h2>
|
||||||
|
<WebGLShader />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">LiquidButton Component</h2>
|
||||||
|
<LiquidButton>Click Me</LiquidButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Placeholder demo component
|
||||||
|
export default function Demo() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-8 p-8">
|
||||||
|
<h1 className="text-3xl font-bold">Demo Component</h1>
|
||||||
|
<p>WebGLShader and LiquidButton components are ready to be imported once they are created.</p>
|
||||||
|
<p>Dependencies installed: three, @radix-ui/react-slot, class-variance-authority</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,58 +1,35 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Halant } from "next/font/google";
|
import { Lexend } from "next/font/google";
|
||||||
import { Inter } from "next/font/google";
|
|
||||||
import { Open_Sans } from "next/font/google";
|
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { ServiceWrapper } from "@/components/ServiceWrapper";
|
|
||||||
import Tag from "@/tag/Tag";
|
|
||||||
|
|
||||||
const halant = Halant({
|
const lexend = Lexend({
|
||||||
variable: "--font-halant", subsets: ["latin"],
|
variable: "--font-lexend", subsets: ["latin"],
|
||||||
weight: ["300", "400", "500", "600", "700"],
|
weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"],
|
||||||
});
|
|
||||||
|
|
||||||
const inter = Inter({
|
|
||||||
variable: "--font-inter", subsets: ["latin"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const openSans = Open_Sans({
|
|
||||||
variable: "--font-open-sans", subsets: ["latin"],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Premium Creative & AI Solutions | Blackroom Collective", description: "High-end video production, photography, web design with custom animations, and AI-powered automation for brands, events, and films.", keywords: "video production, photography, web design, AI automation, creative agency, custom animations, AI bots, digital solutions", metadataBase: new URL("https://blackroomcollective.com"),
|
title: "Blackroom Collective - Premium Creative Solutions", description: "High-end video production, professional photography, custom web design, and AI-powered automation solutions for luxury brands and premium events."};
|
||||||
alternates: {
|
|
||||||
canonical: "https://blackroomcollective.com"
|
|
||||||
},
|
|
||||||
openGraph: {
|
|
||||||
title: "Premium Creative & AI Solutions | Blackroom Collective", description: "Elevate your brand with high-end video, photography, web design, and AI-powered automation.", siteName: "Blackroom Collective", type: "website", images: [
|
|
||||||
{
|
|
||||||
url: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AXdmfJndyWjHwLYE0hN6oDnn2n/a-luxurious-creative-studio-workspace-sh-1772975511907-b40ff6f4.png", alt: "Premium Creative Studio"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
card: "summary_large_image", title: "Premium Creative & AI Solutions | Blackroom Collective", description: "High-end creative services for brands and events.", images: ["https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_3AXdmfJndyWjHwLYE0hN6oDnn2n/a-luxurious-creative-studio-workspace-sh-1772975511907-b40ff6f4.png"]
|
|
||||||
},
|
|
||||||
robots: {
|
|
||||||
index: true,
|
|
||||||
follow: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<ServiceWrapper>
|
<body className={`${lexend.variable}`}>
|
||||||
<body
|
{children}
|
||||||
className={`${halant.variable} ${inter.variable} ${openSans.variable} antialiased`}
|
<script
|
||||||
>
|
dangerouslySetInnerHTML={{
|
||||||
<Tag />
|
__html: `
|
||||||
{children}
|
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
|
document.documentElement.classList.add('dark')
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark')
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<script
|
<script
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
@@ -1421,7 +1398,6 @@ export default function RootLayout({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</body>
|
</body>
|
||||||
</ServiceWrapper>
|
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
154
src/components/ui/liquid-glass-button.tsx
Normal file
154
src/components/ui/liquid-glass-button.tsx
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
|
|
||||||
|
export interface LiquidButtonProps {
|
||||||
|
text: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LiquidButton: React.FC<LiquidButtonProps> = ({
|
||||||
|
text,
|
||||||
|
onClick,
|
||||||
|
className = '',
|
||||||
|
disabled = false,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
|
className={`relative px-6 py-3 font-medium transition-all duration-300 ${
|
||||||
|
isHovered ? 'scale-105' : 'scale-100'
|
||||||
|
} ${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'} ${className}`}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 rounded-lg bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 opacity-75 blur-md group-hover:opacity-100 transition-opacity duration-300" />
|
||||||
|
<div className="relative z-10 flex items-center justify-center gap-2">
|
||||||
|
{children || text}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ButtonProps extends LiquidButtonProps {
|
||||||
|
variant?: 'primary' | 'secondary' | 'outline';
|
||||||
|
size?: 'sm' | 'md' | 'lg';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Button: React.FC<ButtonProps> = ({
|
||||||
|
text,
|
||||||
|
onClick,
|
||||||
|
variant = 'primary',
|
||||||
|
size = 'md',
|
||||||
|
className = '',
|
||||||
|
disabled = false,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const baseClass =
|
||||||
|
'relative font-medium rounded-lg transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2';
|
||||||
|
const sizeClasses = {
|
||||||
|
sm: 'px-3 py-2 text-sm',
|
||||||
|
md: 'px-6 py-3 text-base',
|
||||||
|
lg: 'px-8 py-4 text-lg',
|
||||||
|
};
|
||||||
|
const variantClasses = {
|
||||||
|
primary: 'bg-blue-600 text-white hover:bg-blue-700 disabled:bg-gray-400',
|
||||||
|
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300 disabled:bg-gray-100',
|
||||||
|
outline: 'border-2 border-blue-600 text-blue-600 hover:bg-blue-50 disabled:border-gray-400 disabled:text-gray-400',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
className={`${baseClass} ${sizeClasses[size]} ${variantClasses[variant]} ${className}`}
|
||||||
|
>
|
||||||
|
{children || text}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface GlassFilterProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
intensity?: number;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GlassFilter: React.FC<GlassFilterProps> = ({
|
||||||
|
children,
|
||||||
|
intensity = 0.1,
|
||||||
|
className = '',
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`backdrop-blur-md bg-white/10 rounded-xl border border-white/20 ${className}`}
|
||||||
|
style={{ backdropFilter: `blur(${intensity * 20}px)` }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface MetalButtonProps extends ButtonProps {
|
||||||
|
shine?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MetalButton: React.FC<MetalButtonProps> = ({
|
||||||
|
text,
|
||||||
|
onClick,
|
||||||
|
className = '',
|
||||||
|
disabled = false,
|
||||||
|
shine = true,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
const handleMouseMove = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
if (!buttonRef.current) return;
|
||||||
|
const rect = buttonRef.current.getBoundingClientRect();
|
||||||
|
setMousePosition({
|
||||||
|
x: e.clientX - rect.left,
|
||||||
|
y: e.clientY - rect.top,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
ref={buttonRef}
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
onMouseMove={handleMouseMove}
|
||||||
|
className={`relative px-6 py-3 font-medium rounded-lg transition-all duration-200 overflow-hidden
|
||||||
|
bg-gradient-to-b from-gray-100 to-gray-300 text-gray-900 hover:from-gray-50 hover:to-gray-200
|
||||||
|
border border-gray-400 shadow-lg hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed
|
||||||
|
${className}`}
|
||||||
|
>
|
||||||
|
{shine && (
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 pointer-events-none"
|
||||||
|
style={{
|
||||||
|
background: `radial-gradient(circle at ${mousePosition.x}px ${mousePosition.y}px, rgba(255,255,255,0.3), transparent 80%)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="relative z-10 flex items-center justify-center gap-2">
|
||||||
|
{children || text}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
LiquidButton,
|
||||||
|
Button,
|
||||||
|
GlassFilter,
|
||||||
|
MetalButton,
|
||||||
|
};
|
||||||
148
src/components/ui/web-gl-shader.tsx
Normal file
148
src/components/ui/web-gl-shader.tsx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export interface WebGLShaderProps {
|
||||||
|
fragmentShader: string;
|
||||||
|
vertexShader?: string;
|
||||||
|
uniforms?: Record<string, { type: string; value: any }>;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultVertexShader = `
|
||||||
|
attribute vec4 position;
|
||||||
|
void main() {
|
||||||
|
gl_Position = position;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const WebGLShader: React.FC<WebGLShaderProps> = ({
|
||||||
|
fragmentShader,
|
||||||
|
vertexShader = defaultVertexShader,
|
||||||
|
uniforms = {},
|
||||||
|
className = '',
|
||||||
|
}) => {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const glRef = useRef<WebGLRenderingContext | null>(null);
|
||||||
|
const programRef = useRef<WebGLProgram | null>(null);
|
||||||
|
const uniformLocationsRef = useRef<Record<string, WebGLUniformLocation>>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
||||||
|
if (!gl) {
|
||||||
|
console.error('WebGL not supported');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glRef.current = gl as WebGLRenderingContext;
|
||||||
|
|
||||||
|
// Compile shaders
|
||||||
|
const compileShader = (source: string, type: number): WebGLShader | null => {
|
||||||
|
const shader = (gl as WebGLRenderingContext).createShader(type);
|
||||||
|
if (!shader) return null;
|
||||||
|
(gl as WebGLRenderingContext).shaderSource(shader, source);
|
||||||
|
(gl as WebGLRenderingContext).compileShader(shader);
|
||||||
|
if (!(gl as WebGLRenderingContext).getShaderParameter(shader, (gl as WebGLRenderingContext).COMPILE_STATUS)) {
|
||||||
|
console.error('Shader compilation error:', (gl as WebGLRenderingContext).getShaderInfoLog(shader));
|
||||||
|
(gl as WebGLRenderingContext).deleteShader(shader);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return shader;
|
||||||
|
};
|
||||||
|
|
||||||
|
const vShader = compileShader(vertexShader, (gl as WebGLRenderingContext).VERTEX_SHADER);
|
||||||
|
const fShader = compileShader(fragmentShader, (gl as WebGLRenderingContext).FRAGMENT_SHADER);
|
||||||
|
|
||||||
|
if (!vShader || !fShader) return;
|
||||||
|
|
||||||
|
// Link program
|
||||||
|
const program = (gl as WebGLRenderingContext).createProgram();
|
||||||
|
if (!program) return;
|
||||||
|
(gl as WebGLRenderingContext).attachShader(program, vShader);
|
||||||
|
(gl as WebGLRenderingContext).attachShader(program, fShader);
|
||||||
|
(gl as WebGLRenderingContext).linkProgram(program);
|
||||||
|
|
||||||
|
if (!(gl as WebGLRenderingContext).getProgramParameter(program, (gl as WebGLRenderingContext).LINK_STATUS)) {
|
||||||
|
console.error('Program link error:', (gl as WebGLRenderingContext).getProgramInfoLog(program));
|
||||||
|
(gl as WebGLRenderingContext).deleteProgram(program);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
programRef.current = program;
|
||||||
|
(gl as WebGLRenderingContext).useProgram(program);
|
||||||
|
|
||||||
|
// Store uniform locations
|
||||||
|
const locations: Record<string, WebGLUniformLocation> = {};
|
||||||
|
Object.keys(uniforms).forEach((name) => {
|
||||||
|
const location = (gl as WebGLRenderingContext).getUniformLocation(program, name);
|
||||||
|
if (location !== null) {
|
||||||
|
locations[name] = location;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
uniformLocationsRef.current = locations;
|
||||||
|
|
||||||
|
// Set canvas size
|
||||||
|
const resizeCanvas = () => {
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
(gl as WebGLRenderingContext).viewport(0, 0, canvas.width, canvas.height);
|
||||||
|
};
|
||||||
|
|
||||||
|
resizeCanvas();
|
||||||
|
window.addEventListener('resize', resizeCanvas);
|
||||||
|
|
||||||
|
// Set up quad vertices
|
||||||
|
const vertices = new Float32Array([
|
||||||
|
-1, -1, 1, -1, -1, 1, 1, 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const vertexBuffer = (gl as WebGLRenderingContext).createBuffer();
|
||||||
|
(gl as WebGLRenderingContext).bindBuffer((gl as WebGLRenderingContext).ARRAY_BUFFER, vertexBuffer);
|
||||||
|
(gl as WebGLRenderingContext).bufferData((gl as WebGLRenderingContext).ARRAY_BUFFER, vertices, (gl as WebGLRenderingContext).STATIC_DRAW);
|
||||||
|
|
||||||
|
const positionLocation = (gl as WebGLRenderingContext).getAttribLocation(program, 'position');
|
||||||
|
(gl as WebGLRenderingContext).enableVertexAttribArray(positionLocation);
|
||||||
|
(gl as WebGLRenderingContext).vertexAttribPointer(positionLocation, 2, (gl as WebGLRenderingContext).FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
// Render loop
|
||||||
|
const render = () => {
|
||||||
|
// Update uniforms
|
||||||
|
Object.entries(uniforms).forEach(([name, { type, value }]) => {
|
||||||
|
const location = uniformLocationsRef.current[name];
|
||||||
|
if (!location) return;
|
||||||
|
|
||||||
|
if (type === '1f') (gl as WebGLRenderingContext).uniform1f(location, value);
|
||||||
|
else if (type === '2f') (gl as WebGLRenderingContext).uniform2f(location, value[0], value[1]);
|
||||||
|
else if (type === '3f') (gl as WebGLRenderingContext).uniform3f(location, value[0], value[1], value[2]);
|
||||||
|
else if (type === '4f') (gl as WebGLRenderingContext).uniform4f(location, value[0], value[1], value[2], value[3]);
|
||||||
|
else if (type === '1i') (gl as WebGLRenderingContext).uniform1i(location, value);
|
||||||
|
else if (type === 'matrix4fv') (gl as WebGLRenderingContext).uniformMatrix4fv(location, false, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
(gl as WebGLRenderingContext).drawArrays((gl as WebGLRenderingContext).TRIANGLE_STRIP, 0, 4);
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
};
|
||||||
|
|
||||||
|
render();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', resizeCanvas);
|
||||||
|
(gl as WebGLRenderingContext).deleteProgram(program);
|
||||||
|
(gl as WebGLRenderingContext).deleteShader(vShader);
|
||||||
|
(gl as WebGLRenderingContext).deleteShader(fShader);
|
||||||
|
};
|
||||||
|
}, [fragmentShader, vertexShader, uniforms]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
className={`block w-full h-full ${className}`}
|
||||||
|
style={{ display: 'block' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WebGLShader;
|
||||||
Reference in New Issue
Block a user