A web app to help you design things, local, offline, on device. In your browser.
TypeScript
53.1%
JSON
45.6%
CSS
0.9%
Markdown
0.3%
JavaScript
0.1%
'use client';
import React, { useState } from 'react';
import { Sparkles, Layers, Sun, Droplets } from 'lucide-react';
import { useEditorStore } from '@/lib/store/editor-store';
import {
GLASS_PRESETS,
MESH_GRADIENT_PRESETS,
defaultLiquidGlassConfig,
type GlassPreset,
type MeshGradientPreset,
type LiquidGlassConfig,
} from '@/lib/effects/liquid-glass';
import type { EffectLayerData, GlassEffectConfig } from '@/types';
const effectCategories = [
{ id: 'glass', label: 'Glass', icon: Layers },
{ id: 'backgrounds', label: 'Backgrounds', icon: Droplets },
{ id: 'spotlight', label: 'Spotlight', icon: Sun },
] as const;
export function EffectsPanel() {
const [activeCategory, setActiveCategory] = useState<(typeof effectCategories)[number]['id']>(
'glass'
);
const [glassConfig, setGlassConfig] = useState<LiquidGlassConfig>(defaultLiquidGlassConfig);
const { addLayer, canvas, setBackgroundColor } = useEditorStore();
const handleAddGlassEffect = (preset?: GlassPreset) => {
const config = preset ? GLASS_PRESETS[preset] : glassConfig;
const effectConfig: GlassEffectConfig = {
blur: config.blur,
opacity: config.opacity,
saturation: config.saturation,
borderRadius: config.borderRadius,
borderColor: config.borderColor,
borderWidth: config.borderWidth,
};
const data: EffectLayerData = {
type: 'effect',
effectType: 'glass',
config: effectConfig,
};
const size = Math.min(canvas.width, canvas.height) * 0.4;
addLayer({
type: 'effect',
name: preset ? `Glass (${preset})` : 'Glass Panel',
visible: true,
locked: false,
opacity: 1,
position: {
x: (canvas.width - size) / 2,
y: (canvas.height - size) / 2,
},
size: { width: size, height: size * 0.6 },
rotation: 0,
data,
});
};
const handleApplyMeshGradient = (preset: MeshGradientPreset) => {
const meshConfig = MESH_GRADIENT_PRESETS[preset];
// Apply as canvas background using a gradient approximation
setBackgroundColor({
type: 'radial',
stops: meshConfig.colors.map((color, i) => ({
offset: i / (meshConfig.colors.length - 1),
color,
})),
});
};
const handleAddSpotlight = () => {
const data: EffectLayerData = {
type: 'effect',
effectType: 'blur',
config: {
blur: 20,
shape: 'circle',
},
};
const size = Math.min(canvas.width, canvas.height) * 0.5;
addLayer({
type: 'effect',
name: 'Spotlight',
visible: true,
locked: false,
opacity: 0.8,
position: {
x: (canvas.width - size) / 2,
y: (canvas.height - size) / 2,
},
size: { width: size, height: size },
rotation: 0,
data,
});
};
return (
<div className="p-4">
<h2 className="text-lg font-semibold text-white mb-4">Effects</h2>
{/* Category tabs */}
<div className="flex gap-1 mb-6 overflow-x-auto pb-1">
{effectCategories.map((cat) => {
const Icon = cat.icon;
return (
<button
key={cat.id}
onClick={() => setActiveCategory(cat.id)}
className={`
flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm whitespace-nowrap
transition-colors
${
activeCategory === cat.id
? 'bg-indigo-500/20 text-indigo-400'
: 'text-neutral-400 hover:text-white hover:bg-neutral-800'
}
`}
>
<Icon className="w-4 h-4" />
{cat.label}
</button>
);
})}
</div>
{activeCategory === 'glass' && (
<div className="space-y-6">
{/* Glass presets */}
<div>
<label className="text-sm text-neutral-400 block mb-3">Liquid Glass Presets</label>
<div className="grid grid-cols-2 gap-3">
{(Object.keys(GLASS_PRESETS) as GlassPreset[]).map((preset) => (
<button
key={preset}
onClick={() => handleAddGlassEffect(preset)}
className="
p-4 rounded-xl
bg-gradient-to-br from-white/10 to-white/5
backdrop-blur-sm
border border-white/10 hover:border-indigo-500/50
transition-all group
"
>
{/* Glass preview */}
<div
className="h-12 rounded-lg mb-2"
style={{
background: `rgba(255, 255, 255, ${GLASS_PRESETS[preset].opacity})`,
backdropFilter: `blur(${GLASS_PRESETS[preset].blur / 2}px)`,
border: `1px solid rgba(255, 255, 255, 0.1)`,
}}
/>
<div className="text-xs text-neutral-300 capitalize group-hover:text-indigo-400 transition-colors">
{preset}
</div>
</button>
))}
</div>
</div>
{/* Custom glass controls */}
<div>
<label className="text-sm text-neutral-400 block mb-3">Customize</label>
<div className="space-y-3">
<div>
<div className="flex justify-between text-xs text-neutral-500 mb-1">
<span>Blur</span>
<span>{glassConfig.blur}px</span>
</div>
<input
type="range"
min={0}
max={50}
value={glassConfig.blur}
onChange={(e) =>
setGlassConfig({ ...glassConfig, blur: Number(e.target.value) })
}
className="w-full"
/>
</div>
<div>
<div className="flex justify-between text-xs text-neutral-500 mb-1">
<span>Opacity</span>
<span>{Math.round(glassConfig.opacity * 100)}%</span>
</div>
<input
type="range"
min={0}
max={100}
value={glassConfig.opacity * 100}
onChange={(e) =>
setGlassConfig({ ...glassConfig, opacity: Number(e.target.value) / 100 })
}
className="w-full"
/>
</div>
<div>
<div className="flex justify-between text-xs text-neutral-500 mb-1">
<span>Saturation</span>
<span>{Math.round(glassConfig.saturation * 100)}%</span>
</div>
<input
type="range"
min={100}
max={200}
value={glassConfig.saturation * 100}
onChange={(e) =>
setGlassConfig({ ...glassConfig, saturation: Number(e.target.value) / 100 })
}
className="w-full"
/>
</div>
<div>
<div className="flex justify-between text-xs text-neutral-500 mb-1">
<span>Border Radius</span>
<span>{glassConfig.borderRadius}px</span>
</div>
<input
type="range"
min={0}
max={50}
value={glassConfig.borderRadius}
onChange={(e) =>
setGlassConfig({ ...glassConfig, borderRadius: Number(e.target.value) })
}
className="w-full"
/>
</div>
<button
onClick={() => handleAddGlassEffect()}
className="
w-full py-2 rounded-lg
bg-indigo-500 hover:bg-indigo-600
text-white text-sm font-medium
transition-colors flex items-center justify-center gap-2
"
>
<Sparkles className="w-4 h-4" />
Add Glass Panel
</button>
</div>
</div>
</div>
)}
{activeCategory === 'backgrounds' && (
<div className="space-y-4">
<label className="text-sm text-neutral-400 block">Mesh Gradient Backgrounds</label>
<div className="grid grid-cols-2 gap-3">
{(Object.keys(MESH_GRADIENT_PRESETS) as MeshGradientPreset[]).map((preset) => {
const config = MESH_GRADIENT_PRESETS[preset];
const gradientCSS = `radial-gradient(at 20% 80%, ${config.colors[0]} 0px, transparent 50%),
radial-gradient(at 80% 20%, ${config.colors[1]} 0px, transparent 50%),
radial-gradient(at 60% 60%, ${config.colors[2]} 0px, transparent 50%),
${config.colors[3]}`;
return (
<button
key={preset}
onClick={() => handleApplyMeshGradient(preset)}
className="
aspect-video rounded-xl overflow-hidden
border border-neutral-700/50 hover:border-indigo-500/50
transition-all hover:scale-[1.02]
"
>
<div
className="w-full h-full"
style={{ background: gradientCSS }}
/>
</button>
);
})}
</div>
<p className="text-xs text-neutral-500">
Click a mesh gradient to apply it as your canvas background.
</p>
</div>
)}
{activeCategory === 'spotlight' && (
<div className="space-y-4">
<p className="text-sm text-neutral-400">
Add spotlight effects to highlight specific features in your screenshots.
</p>
<div className="grid grid-cols-2 gap-3">
<button
onClick={handleAddSpotlight}
className="
p-4 rounded-xl
bg-neutral-800/50 hover:bg-neutral-800
border border-neutral-700/50 hover:border-indigo-500/50
transition-all group
"
>
<div className="h-16 rounded-lg bg-gradient-to-br from-black/80 to-black/40 mb-2 flex items-center justify-center">
<div className="w-8 h-8 rounded-full bg-white/20 blur-sm" />
</div>
<div className="text-xs text-neutral-300 group-hover:text-indigo-400">
Circle Spotlight
</div>
</button>
<button
onClick={handleAddSpotlight}
className="
p-4 rounded-xl
bg-neutral-800/50 hover:bg-neutral-800
border border-neutral-700/50 hover:border-indigo-500/50
transition-all group
"
>
<div className="h-16 rounded-lg bg-gradient-to-br from-black/80 to-black/40 mb-2 flex items-center justify-center">
<div className="w-12 h-8 rounded bg-white/20 blur-sm" />
</div>
<div className="text-xs text-neutral-300 group-hover:text-indigo-400">
Rectangle Focus
</div>
</button>
</div>
<div className="p-3 rounded-lg bg-indigo-500/10 border border-indigo-500/20">
<p className="text-xs text-indigo-300">
Tip: Position the spotlight over a feature, then adjust opacity to create focus.
</p>
</div>
</div>
)}
</div>
);
}
About
A web app to help you design things, local, offline, on device. In your browser.
0 stars
0 forks