light
This commit is contained in:
7
package-lock.json
generated
7
package-lock.json
generated
@@ -48,6 +48,7 @@
|
||||
"lucide-react": "^0.462.0",
|
||||
"motion": "^12.29.2",
|
||||
"next-themes": "^0.3.0",
|
||||
"ogl": "^1.0.11",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -6590,6 +6591,12 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/ogl": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ogl/-/ogl-1.0.11.tgz",
|
||||
"integrity": "sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==",
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"lucide-react": "^0.462.0",
|
||||
"motion": "^12.29.2",
|
||||
"next-themes": "^0.3.0",
|
||||
"ogl": "^1.0.11",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
@@ -63,7 +63,7 @@ const Hero = () => {
|
||||
<Silk
|
||||
speed={3}
|
||||
scale={0.5}
|
||||
color="#373737"
|
||||
color="#6a6a6a"
|
||||
noiseIntensity={4
|
||||
}
|
||||
rotation={0}
|
||||
|
||||
16
src/components/LightRays.css
Normal file
16
src/components/LightRays.css
Normal file
@@ -0,0 +1,16 @@
|
||||
.light-rays-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.light-rays-fallback {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
486
src/components/LightRays.tsx
Normal file
486
src/components/LightRays.tsx
Normal file
@@ -0,0 +1,486 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
// @ts-expect-error ogl has no type definitions
|
||||
import { Renderer, Program, Triangle, Mesh } from "ogl";
|
||||
import "./LightRays.css";
|
||||
|
||||
const DEFAULT_COLOR = "#ffffff";
|
||||
|
||||
const hexToRgb = (hex: string): [number, number, number] => {
|
||||
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return m
|
||||
? [
|
||||
parseInt(m[1], 16) / 255,
|
||||
parseInt(m[2], 16) / 255,
|
||||
parseInt(m[3], 16) / 255,
|
||||
]
|
||||
: [1, 1, 1];
|
||||
};
|
||||
|
||||
type RaysOrigin =
|
||||
| "top-left"
|
||||
| "top-right"
|
||||
| "top-center"
|
||||
| "left"
|
||||
| "right"
|
||||
| "bottom-left"
|
||||
| "bottom-center"
|
||||
| "bottom-right";
|
||||
|
||||
const getAnchorAndDir = (
|
||||
origin: RaysOrigin,
|
||||
w: number,
|
||||
h: number
|
||||
): { anchor: [number, number]; dir: [number, number] } => {
|
||||
const outside = 0.2;
|
||||
switch (origin) {
|
||||
case "top-left":
|
||||
return { anchor: [0, -outside * h], dir: [0, 1] };
|
||||
case "top-right":
|
||||
return { anchor: [w, -outside * h], dir: [0, 1] };
|
||||
case "left":
|
||||
return { anchor: [-outside * w, 0.5 * h], dir: [1, 0] };
|
||||
case "right":
|
||||
return { anchor: [(1 + outside) * w, 0.5 * h], dir: [-1, 0] };
|
||||
case "bottom-left":
|
||||
return { anchor: [0, (1 + outside) * h], dir: [0, -1] };
|
||||
case "bottom-center":
|
||||
return { anchor: [0.5 * w, (1 + outside) * h], dir: [0, -1] };
|
||||
case "bottom-right":
|
||||
return { anchor: [w, (1 + outside) * h], dir: [0, -1] };
|
||||
default:
|
||||
return { anchor: [0.5 * w, -outside * h], dir: [0, 1] };
|
||||
}
|
||||
};
|
||||
|
||||
export interface LightRaysProps {
|
||||
raysOrigin?: RaysOrigin;
|
||||
raysColor?: string;
|
||||
raysSpeed?: number;
|
||||
lightSpread?: number;
|
||||
rayLength?: number;
|
||||
pulsating?: boolean;
|
||||
fadeDistance?: number;
|
||||
saturation?: number;
|
||||
followMouse?: boolean;
|
||||
mouseInfluence?: number;
|
||||
noiseAmount?: number;
|
||||
distortion?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function LightRays({
|
||||
raysOrigin = "top-center",
|
||||
raysColor = DEFAULT_COLOR,
|
||||
raysSpeed = 1,
|
||||
lightSpread = 1,
|
||||
rayLength = 2,
|
||||
pulsating = false,
|
||||
fadeDistance = 1.0,
|
||||
saturation = 1.0,
|
||||
followMouse = true,
|
||||
mouseInfluence = 0.1,
|
||||
noiseAmount = 0.0,
|
||||
distortion = 0.0,
|
||||
className = "",
|
||||
}: LightRaysProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const uniformsRef = useRef<Record<string, { value: unknown }> | null>(null);
|
||||
const rendererRef = useRef<InstanceType<typeof Renderer> | null>(null);
|
||||
const mouseRef = useRef({ x: 0.5, y: 0.5 });
|
||||
const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });
|
||||
const animationIdRef = useRef<number | null>(null);
|
||||
const meshRef = useRef<InstanceType<typeof Mesh> | null>(null);
|
||||
const cleanupFunctionRef = useRef<(() => void) | null>(null);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [useFallback, setUseFallback] = useState(false);
|
||||
const observerRef = useRef<IntersectionObserver | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
observerRef.current = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const entry = entries[0];
|
||||
setIsVisible(entry.isIntersecting);
|
||||
},
|
||||
{ threshold: 0.1 }
|
||||
);
|
||||
|
||||
observerRef.current.observe(containerRef.current);
|
||||
|
||||
return () => {
|
||||
if (observerRef.current) {
|
||||
observerRef.current.disconnect();
|
||||
observerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isVisible || !containerRef.current) return;
|
||||
|
||||
setUseFallback(false);
|
||||
if (cleanupFunctionRef.current) {
|
||||
cleanupFunctionRef.current();
|
||||
cleanupFunctionRef.current = null;
|
||||
}
|
||||
|
||||
const initializeWebGL = async () => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const isMobile =
|
||||
typeof window !== "undefined" &&
|
||||
(window.innerWidth <= 768 || "ontouchstart" in window);
|
||||
const dpr = isMobile ? 1 : Math.min(window.devicePixelRatio, 2);
|
||||
|
||||
try {
|
||||
const renderer = new Renderer({
|
||||
dpr,
|
||||
alpha: true,
|
||||
});
|
||||
rendererRef.current = renderer;
|
||||
|
||||
const gl = renderer.gl;
|
||||
gl.canvas.style.width = "100%";
|
||||
gl.canvas.style.height = "100%";
|
||||
|
||||
while (containerRef.current.firstChild) {
|
||||
containerRef.current.removeChild(containerRef.current.firstChild);
|
||||
}
|
||||
containerRef.current.appendChild(gl.canvas);
|
||||
|
||||
const vert = `
|
||||
attribute vec2 position;
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = position * 0.5 + 0.5;
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}`;
|
||||
|
||||
const frag = `precision mediump float;
|
||||
|
||||
uniform float iTime;
|
||||
uniform vec2 iResolution;
|
||||
|
||||
uniform vec2 rayPos;
|
||||
uniform vec2 rayDir;
|
||||
uniform vec3 raysColor;
|
||||
uniform float raysSpeed;
|
||||
uniform float lightSpread;
|
||||
uniform float rayLength;
|
||||
uniform float pulsating;
|
||||
uniform float fadeDistance;
|
||||
uniform float saturation;
|
||||
uniform vec2 mousePos;
|
||||
uniform float mouseInfluence;
|
||||
uniform float noiseAmount;
|
||||
uniform float distortion;
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
float noise(vec2 st) {
|
||||
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
|
||||
}
|
||||
|
||||
float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord,
|
||||
float seedA, float seedB, float speed) {
|
||||
vec2 sourceToCoord = coord - raySource;
|
||||
vec2 dirNorm = normalize(sourceToCoord);
|
||||
float cosAngle = dot(dirNorm, rayRefDirection);
|
||||
|
||||
float distortedAngle = cosAngle + distortion * sin(iTime * 2.0 + length(sourceToCoord) * 0.01) * 0.2;
|
||||
|
||||
float spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(lightSpread, 0.001));
|
||||
|
||||
float distance = length(sourceToCoord);
|
||||
float maxDistance = iResolution.x * rayLength;
|
||||
float lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0);
|
||||
|
||||
float fadeFalloff = clamp((iResolution.x * fadeDistance - distance) / (iResolution.x * fadeDistance), 0.5, 1.0);
|
||||
float pulse = pulsating > 0.5 ? (0.8 + 0.2 * sin(iTime * speed * 3.0)) : 1.0;
|
||||
|
||||
float baseStrength = clamp(
|
||||
(0.45 + 0.15 * sin(distortedAngle * seedA + iTime * speed)) +
|
||||
(0.3 + 0.2 * cos(-distortedAngle * seedB + iTime * speed)),
|
||||
0.0, 1.0
|
||||
);
|
||||
|
||||
return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse;
|
||||
}
|
||||
|
||||
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||
vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y);
|
||||
|
||||
vec2 finalRayDir = rayDir;
|
||||
if (mouseInfluence > 0.0) {
|
||||
vec2 mouseScreenPos = mousePos * iResolution.xy;
|
||||
vec2 mouseDirection = normalize(mouseScreenPos - rayPos);
|
||||
finalRayDir = normalize(mix(rayDir, mouseDirection, mouseInfluence));
|
||||
}
|
||||
|
||||
vec4 rays1 = vec4(1.0) *
|
||||
rayStrength(rayPos, finalRayDir, coord, 36.2214, 21.11349,
|
||||
1.5 * raysSpeed);
|
||||
vec4 rays2 = vec4(1.0) *
|
||||
rayStrength(rayPos, finalRayDir, coord, 22.3991, 18.0234,
|
||||
1.1 * raysSpeed);
|
||||
|
||||
fragColor = rays1 * 0.5 + rays2 * 0.4;
|
||||
|
||||
if (noiseAmount > 0.0) {
|
||||
float n = noise(coord * 0.01 + iTime * 0.1);
|
||||
fragColor.rgb *= (1.0 - noiseAmount + noiseAmount * n);
|
||||
}
|
||||
|
||||
float brightness = 1.0 - (coord.y / iResolution.y);
|
||||
fragColor.x *= 0.1 + brightness * 0.8;
|
||||
fragColor.y *= 0.3 + brightness * 0.6;
|
||||
fragColor.z *= 0.5 + brightness * 0.5;
|
||||
|
||||
if (saturation != 1.0) {
|
||||
float gray = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114));
|
||||
fragColor.rgb = mix(vec3(gray), fragColor.rgb, saturation);
|
||||
}
|
||||
|
||||
fragColor.rgb *= raysColor;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 color;
|
||||
mainImage(color, gl_FragCoord.xy);
|
||||
gl_FragColor = color;
|
||||
}`;
|
||||
|
||||
const uniforms = {
|
||||
iTime: { value: 0 },
|
||||
iResolution: { value: [1, 1] as [number, number] },
|
||||
|
||||
rayPos: { value: [0, 0] as [number, number] },
|
||||
rayDir: { value: [0, 1] as [number, number] },
|
||||
|
||||
raysColor: { value: hexToRgb(raysColor) },
|
||||
raysSpeed: { value: raysSpeed },
|
||||
lightSpread: { value: lightSpread },
|
||||
rayLength: { value: rayLength },
|
||||
pulsating: { value: pulsating ? 1.0 : 0.0 },
|
||||
fadeDistance: { value: fadeDistance },
|
||||
saturation: { value: saturation },
|
||||
mousePos: { value: [0.5, 0.5] as [number, number] },
|
||||
mouseInfluence: { value: mouseInfluence },
|
||||
noiseAmount: { value: noiseAmount },
|
||||
distortion: { value: distortion },
|
||||
};
|
||||
uniformsRef.current = uniforms as Record<string, { value: unknown }>;
|
||||
|
||||
const geometry = new Triangle(gl);
|
||||
const program = new Program(gl, {
|
||||
vertex: vert,
|
||||
fragment: frag,
|
||||
uniforms,
|
||||
});
|
||||
const mesh = new Mesh(gl, { geometry, program });
|
||||
meshRef.current = mesh;
|
||||
|
||||
const updatePlacement = () => {
|
||||
if (!containerRef.current || !renderer) return;
|
||||
|
||||
renderer.dpr = isMobile ? 1 : Math.min(window.devicePixelRatio, 2);
|
||||
|
||||
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current;
|
||||
renderer.setSize(wCSS, hCSS);
|
||||
|
||||
const dpr = renderer.dpr;
|
||||
const w = wCSS * dpr;
|
||||
const h = hCSS * dpr;
|
||||
|
||||
uniforms.iResolution.value = [w, h];
|
||||
|
||||
const { anchor, dir } = getAnchorAndDir(raysOrigin, w, h);
|
||||
uniforms.rayPos.value = anchor;
|
||||
uniforms.rayDir.value = dir;
|
||||
};
|
||||
|
||||
const loop = (t: number) => {
|
||||
if (!rendererRef.current || !uniformsRef.current || !meshRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uniforms = uniformsRef.current as typeof uniforms;
|
||||
uniforms.iTime.value = t * 0.001;
|
||||
|
||||
if (followMouse && mouseInfluence > 0.0) {
|
||||
const smoothing = 0.92;
|
||||
|
||||
smoothMouseRef.current.x =
|
||||
smoothMouseRef.current.x * smoothing +
|
||||
mouseRef.current.x * (1 - smoothing);
|
||||
smoothMouseRef.current.y =
|
||||
smoothMouseRef.current.y * smoothing +
|
||||
mouseRef.current.y * (1 - smoothing);
|
||||
|
||||
uniforms.mousePos.value = [
|
||||
smoothMouseRef.current.x,
|
||||
smoothMouseRef.current.y,
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
renderer.render({ scene: mesh });
|
||||
animationIdRef.current = requestAnimationFrame(loop);
|
||||
} catch (error) {
|
||||
console.warn("WebGL rendering error:", error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("resize", updatePlacement);
|
||||
const resizeObserver =
|
||||
typeof ResizeObserver !== "undefined" &&
|
||||
new ResizeObserver(() => updatePlacement());
|
||||
if (resizeObserver && containerRef.current) {
|
||||
resizeObserver.observe(containerRef.current);
|
||||
}
|
||||
updatePlacement();
|
||||
animationIdRef.current = requestAnimationFrame(loop);
|
||||
|
||||
cleanupFunctionRef.current = () => {
|
||||
if (resizeObserver && containerRef.current) {
|
||||
resizeObserver.unobserve(containerRef.current);
|
||||
}
|
||||
if (animationIdRef.current) {
|
||||
cancelAnimationFrame(animationIdRef.current);
|
||||
animationIdRef.current = null;
|
||||
}
|
||||
|
||||
window.removeEventListener("resize", updatePlacement);
|
||||
|
||||
if (renderer) {
|
||||
try {
|
||||
const canvas = renderer.gl.canvas;
|
||||
const loseContextExt =
|
||||
renderer.gl.getExtension("WEBGL_lose_context");
|
||||
if (loseContextExt) {
|
||||
loseContextExt.loseContext();
|
||||
}
|
||||
|
||||
if (canvas && canvas.parentNode) {
|
||||
canvas.parentNode.removeChild(canvas);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Error during WebGL cleanup:", error);
|
||||
}
|
||||
}
|
||||
|
||||
rendererRef.current = null;
|
||||
uniformsRef.current = null;
|
||||
meshRef.current = null;
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn("LightRays WebGL init failed (e.g. on mobile):", error);
|
||||
setUseFallback(true);
|
||||
}
|
||||
};
|
||||
|
||||
initializeWebGL();
|
||||
|
||||
return () => {
|
||||
if (cleanupFunctionRef.current) {
|
||||
cleanupFunctionRef.current();
|
||||
cleanupFunctionRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [
|
||||
isVisible,
|
||||
raysOrigin,
|
||||
raysColor,
|
||||
raysSpeed,
|
||||
lightSpread,
|
||||
rayLength,
|
||||
pulsating,
|
||||
fadeDistance,
|
||||
saturation,
|
||||
followMouse,
|
||||
mouseInfluence,
|
||||
noiseAmount,
|
||||
distortion,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!uniformsRef.current || !containerRef.current || !rendererRef.current)
|
||||
return;
|
||||
|
||||
const u = uniformsRef.current as Record<string, { value: unknown }>;
|
||||
const renderer = rendererRef.current;
|
||||
|
||||
u.raysColor.value = hexToRgb(raysColor);
|
||||
u.raysSpeed.value = raysSpeed;
|
||||
u.lightSpread.value = lightSpread;
|
||||
u.rayLength.value = rayLength;
|
||||
u.pulsating.value = pulsating ? 1.0 : 0.0;
|
||||
u.fadeDistance.value = fadeDistance;
|
||||
u.saturation.value = saturation;
|
||||
u.mouseInfluence.value = mouseInfluence;
|
||||
u.noiseAmount.value = noiseAmount;
|
||||
u.distortion.value = distortion;
|
||||
|
||||
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current;
|
||||
const dpr = renderer.dpr;
|
||||
const { anchor, dir } = getAnchorAndDir(
|
||||
raysOrigin,
|
||||
wCSS * dpr,
|
||||
hCSS * dpr
|
||||
);
|
||||
u.rayPos.value = anchor;
|
||||
u.rayDir.value = dir;
|
||||
}, [
|
||||
raysColor,
|
||||
raysSpeed,
|
||||
lightSpread,
|
||||
raysOrigin,
|
||||
rayLength,
|
||||
pulsating,
|
||||
fadeDistance,
|
||||
saturation,
|
||||
mouseInfluence,
|
||||
noiseAmount,
|
||||
distortion,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!containerRef.current || !rendererRef.current) return;
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
const x = (e.clientX - rect.left) / rect.width;
|
||||
const y = (e.clientY - rect.top) / rect.height;
|
||||
mouseRef.current = { x, y };
|
||||
};
|
||||
|
||||
if (followMouse) {
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
return () => window.removeEventListener("mousemove", handleMouseMove);
|
||||
}
|
||||
}, [followMouse]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`light-rays-container ${className}`.trim()}
|
||||
>
|
||||
{useFallback && (
|
||||
<div
|
||||
className="light-rays-fallback"
|
||||
style={{
|
||||
background: `linear-gradient(to bottom, ${raysColor}50 0%, ${raysColor}20 25%, ${raysColor}08 50%, transparent 85%)`,
|
||||
}}
|
||||
aria-hidden
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Calendar, MessageSquareOff, TrendingDown, Folders } from "lucide-react";
|
||||
import { LampTop } from "@/components/ui/lamp";
|
||||
import LightRays from "@/components/LightRays";
|
||||
|
||||
const ProblemSection = () => {
|
||||
const problems = [
|
||||
@@ -21,8 +23,25 @@ const ProblemSection = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-24 md:py-32 bg-background relative">
|
||||
<div className="container mx-auto px-6">
|
||||
<section className="section-problem-solution py-24 md:py-32 relative overflow-hidden">
|
||||
<div className="absolute inset-0 w-full overflow-hidden z-0">
|
||||
<LightRays
|
||||
raysOrigin="top-center"
|
||||
raysColor="#ef4444"
|
||||
raysSpeed={1}
|
||||
lightSpread={0.5}
|
||||
rayLength={3}
|
||||
followMouse={false}
|
||||
mouseInfluence={0}
|
||||
noiseAmount={0}
|
||||
distortion={0}
|
||||
pulsating
|
||||
fadeDistance={2}
|
||||
saturation={2}
|
||||
/>
|
||||
</div>
|
||||
<LampTop />
|
||||
<div className="container mx-auto px-6 relative z-10">
|
||||
{/* Section Header */}
|
||||
<div className="mb-16 md:mb-20 max-w-4xl">
|
||||
<div className="label-tag mb-4">Das Problem</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ArrowRight, CheckCircle2 } from "lucide-react";
|
||||
import { LampTop } from "@/components/ui/lamp";
|
||||
import LightRays from "@/components/LightRays";
|
||||
|
||||
const SolutionSection = () => {
|
||||
const benefits = [
|
||||
@@ -10,8 +12,25 @@ const SolutionSection = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="py-24 md:py-32 bg-background relative">
|
||||
<div className="container mx-auto px-6">
|
||||
<section className="section-problem-solution py-24 md:py-32 relative overflow-hidden">
|
||||
<div className="absolute inset-0 w-full overflow-hidden z-0">
|
||||
<LightRays
|
||||
raysOrigin="top-center"
|
||||
raysColor="#22d3ee"
|
||||
raysSpeed={1}
|
||||
lightSpread={0.5}
|
||||
rayLength={3}
|
||||
followMouse={false}
|
||||
mouseInfluence={0}
|
||||
noiseAmount={0}
|
||||
distortion={0}
|
||||
pulsating
|
||||
fadeDistance={2}
|
||||
saturation={2}
|
||||
/>
|
||||
</div>
|
||||
<LampTop lineClassName="bg-cyan-400" />
|
||||
<div className="container mx-auto px-6 relative z-10">
|
||||
<div className="grid lg:grid-cols-2 gap-16 items-center">
|
||||
{/* Left Content */}
|
||||
<div>
|
||||
|
||||
39
src/components/ui/lamp.tsx
Normal file
39
src/components/ui/lamp.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { motion } from "motion/react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const S = 2.5;
|
||||
|
||||
export const LampTop = ({
|
||||
className,
|
||||
lineClassName = "bg-red-500",
|
||||
children,
|
||||
}: {
|
||||
className?: string;
|
||||
lineClassName?: string;
|
||||
children?: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"absolute top-0 left-0 right-0 w-full min-h-0 pointer-events-none z-50 flex items-start justify-center",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<motion.div
|
||||
initial={{ width: `${15 * S}rem` }}
|
||||
whileInView={{ width: `${30 * S}rem` }}
|
||||
transition={{
|
||||
delay: 0.3,
|
||||
duration: 0.8,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
className={cn("absolute top-0 left-1/2 -translate-x-1/2 h-0.5", lineClassName)}
|
||||
style={{ width: `${30 * S}rem` }}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -154,6 +154,11 @@
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Gemeinsamer Hintergrund für Problem- und Lösungs-Sektion */
|
||||
.section-problem-solution {
|
||||
background-color: hsl(var(--background));
|
||||
}
|
||||
|
||||
/* Minimal glass nav */
|
||||
.glass-nav {
|
||||
@apply backdrop-blur-xl border-b;
|
||||
|
||||
Reference in New Issue
Block a user