onSelect?.(style)}
+ >
+ {/* Header */}
+
+
+
+ {style.styleCategory}
+
+
+
+ {style.type}
+
+ {style.eraOrigin}
+
+
+
+
+ {/* Preview */}
+
+
+
+
+ {/* Color Palette */}
+
+
+
+
+ {/* Metadata Badges */}
+
+
+
+
+ {/* Expandable Sections */}
+
+
+
+ {style.designSystemVariables && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ {style.bestFor && (
+
+
+ Best for:{" "}
+
+
+ {style.bestFor}
+
+
+ )}
+ {style.doNotUseFor && (
+
+
+ Avoid for:{" "}
+
+
+ {style.doNotUseFor}
+
+
+ )}
+ {style.frameworkCompatibility && (
+
+
+ Frameworks:{" "}
+
+
+ {style.frameworkCompatibility}
+
+
+ )}
+ {style.keywords && (
+
+ {style.keywords
+ .split(",")
+ .slice(0, 6)
+ .map((kw, i) => (
+
+ {kw.trim()}
+
+ ))}
+
+ )}
+
+
+
+
+ );
+}
diff --git a/gallery/components/StyleDetailModal.tsx b/gallery/components/StyleDetailModal.tsx
new file mode 100644
index 0000000..7f7124e
--- /dev/null
+++ b/gallery/components/StyleDetailModal.tsx
@@ -0,0 +1,220 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { useTheme } from "next-themes";
+import { StyleData } from "@/lib/types";
+import {
+ generatePreviewStyle,
+ generateBackgroundGradient,
+} from "@/lib/cssGenerator";
+import { PhoneMockup } from "./PhoneMockup";
+import { UIControlsShowcase } from "./UIControlsShowcase";
+import { ColorPalette } from "./ColorPalette";
+import { MetadataBadges } from "./MetadataBadges";
+import { ExpandableSection } from "./ExpandableSection";
+import { CodeSnippet } from "./CodeSnippet";
+import { InteractiveChecklist } from "./InteractiveChecklist";
+
+interface StyleDetailModalProps {
+ style: StyleData;
+ onClose: () => void;
+}
+
+export function StyleDetailModal({ style, onClose }: StyleDetailModalProps) {
+ const { theme } = useTheme();
+ const [mounted, setMounted] = useState(false);
+ useEffect(() => setMounted(true), []);
+
+ // ESC to close
+ useEffect(() => {
+ function handleKeyDown(e: KeyboardEvent) {
+ if (e.key === "Escape") onClose();
+ }
+ document.addEventListener("keydown", handleKeyDown);
+ return () => document.removeEventListener("keydown", handleKeyDown);
+ }, [onClose]);
+
+ // Lock body scroll
+ useEffect(() => {
+ document.body.style.overflow = "hidden";
+ return () => {
+ document.body.style.overflow = "";
+ };
+ }, []);
+
+ const isDark = mounted ? theme === "dark" : false;
+ const cardStyle = generatePreviewStyle(style, isDark);
+ const bgGradient = generateBackgroundGradient(style.extractedColors, isDark);
+
+ const accentColor =
+ style.extractedColors[0] || (isDark ? "#6366f1" : "#3b82f6");
+ const secondaryColor =
+ style.extractedColors[1] || style.extractedColors[0] || (isDark ? "#8b5cf6" : "#6366f1");
+
+ return (
+
+ {/* Backdrop */}
+
+
+ {/* Modal container */}
+
e.stopPropagation()}
+ >
+ {/* Header */}
+
+
+
+ {style.styleCategory}
+
+
{/* Spacer for centering */}
+
+
+ {/* Body — two-column on desktop, single-column on mobile */}
+
+
+ {/* Left: Phone mockup */}
+
+
+ {/* Right: Details */}
+
+ {/* Color Palette */}
+
+
+ Color Palette
+
+
+
+
+ {/* Metadata */}
+
+
+
+
+ {/* Expandable sections */}
+
+
+
+ {style.designSystemVariables && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ {style.bestFor && (
+
+
+ Best for:{" "}
+
+
+ {style.bestFor}
+
+
+ )}
+ {style.doNotUseFor && (
+
+
+ Avoid for:{" "}
+
+
+ {style.doNotUseFor}
+
+
+ )}
+ {style.frameworkCompatibility && (
+
+
+ Frameworks:{" "}
+
+
+ {style.frameworkCompatibility}
+
+
+ )}
+ {style.keywords && (
+
+ {style.keywords
+ .split(",")
+ .slice(0, 6)
+ .map((kw, i) => (
+
+ {kw.trim()}
+
+ ))}
+
+ )}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/gallery/components/StylePreview.tsx b/gallery/components/StylePreview.tsx
new file mode 100644
index 0000000..32f88db
--- /dev/null
+++ b/gallery/components/StylePreview.tsx
@@ -0,0 +1,80 @@
+"use client";
+
+import { StyleData } from "@/lib/types";
+import {
+ generatePreviewStyle,
+ generateBackgroundGradient,
+} from "@/lib/cssGenerator";
+import { useTheme } from "next-themes";
+import { useEffect, useState } from "react";
+
+interface StylePreviewProps {
+ style: StyleData;
+}
+
+export function StylePreview({ style }: StylePreviewProps) {
+ const { theme } = useTheme();
+ const [mounted, setMounted] = useState(false);
+ useEffect(() => setMounted(true), []);
+
+ const isDark = mounted ? theme === "dark" : false;
+ const cardStyle = generatePreviewStyle(style, isDark);
+ const bgGradient = generateBackgroundGradient(style.extractedColors, isDark);
+ const accentColor = style.extractedColors[0] || (isDark ? "#6366f1" : "#3b82f6");
+
+ return (
+
+ {/* 1. Buttons */}
+
+
+
+
+
+
+
+
+ {/* 2. Text Input */}
+
+
+ {/* 3. Toggle Switch */}
+
+
+ setToggle1(!toggle1)}
+ accentColor={accentColor}
+ borderRadius={borderRadius}
+ />
+ setToggle2(!toggle2)}
+ accentColor={accentColor}
+ borderRadius={borderRadius}
+ />
+
+
+
+ {/* 4. Checkbox */}
+
+
+ setCheck1(!check1)}
+ label="Checked option"
+ accentColor={accentColor}
+ halfRadius={halfRadius}
+ textPrimary={textPrimary}
+ baseFont={baseFont}
+ />
+ setCheck2(!check2)}
+ label="Unchecked option"
+ accentColor={accentColor}
+ halfRadius={halfRadius}
+ textPrimary={textPrimary}
+ baseFont={baseFont}
+ />
+
+
+
+ {/* 5. Radio Button */}
+
+
+ setRadio("a")}
+ label="Option A"
+ accentColor={accentColor}
+ textPrimary={textPrimary}
+ baseFont={baseFont}
+ />
+ setRadio("b")}
+ label="Option B"
+ accentColor={accentColor}
+ textPrimary={textPrimary}
+ baseFont={baseFont}
+ />
+
+
+
+ {/* 6. Card */}
+
+
+
+
+
+ Card Title
+
+
+ A short description of this card with some preview text.
+
+
+
+
+
+ {/* 7. Badge / Tag */}
+
+
+ {["Design", "UI/UX", "Style", "Modern"].map((tag) => (
+
+ {tag}
+
+ ))}
+
+
+
+ {/* 8. Slider */}
+
+
+
+ 0
+
+ {slider}
+
+ 100
+
+
setSlider(Number(e.target.value))}
+ style={{
+ width: "100%",
+ accentColor,
+ cursor: "pointer",
+ }}
+ />
+
+
+
+ {/* 9. Navigation Bar */}
+
+
+ );
+}
+
+/* --- Sub-components --- */
+
+function Section({
+ label,
+ textSecondary,
+ children,
+}: {
+ label: string;
+ textSecondary: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
+
+function RadioItem({
+ selected,
+ onSelect,
+ label,
+ accentColor,
+ textPrimary,
+ baseFont,
+}: {
+ selected: boolean;
+ onSelect: () => void;
+ label: string;
+ accentColor: string;
+ textPrimary: string;
+ baseFont: React.CSSProperties;
+}) {
+ return (
+