/** Stil-UI (M4): Weg A Swipen · Weg B Preset per Bild-Klick · Smart Spider. * * Reine Achsen statt benannter Stile (ADR-0006); Bilder sind raumtyp-gebunden * und kommen aus data/images/ (SVG-Platzhalter, bis Bryan echte Fotos taggt). */ import { useEffect, useState } from "react"; import { bildUrl } from "./bildUrl"; import { karte as ciKarte, pill, THEME, titel } from "./theme"; export interface BildItem { id: string; bildRef: string; achsenTags: Record; palette: string[]; istPreset: boolean; } /** Bild-Kachel mit Fallback: fehlt/lädt das Foto nicht (echte PNGs kommen von * Bryan separat), zeigen wir die getaggte Farbpalette als Vorschau statt eines * kaputten Bild-Icons – der Swipe bleibt so schon vor den Fotos benutzbar. * * `maxHoehe` begrenzt die Bildhöhe (CSS-Wert, z. B. "min(46vh, 420px)"), damit * die Swipe-Karte ohne Scrollen komplett sichtbar bleibt (Bryan). Das Foto * füllt den Rahmen dann per `object-fit: cover`; ohne `maxHoehe` bleibt das * bisherige Verhalten (volle Breite, freie Höhe). */ export function BildKachel({ bild, maxHoehe }: { bild: BildItem; maxHoehe?: number | string }) { const [fehler, setFehler] = useState(false); if (fehler || !bild.bildRef) { return (
{(bild.palette.length ? bild.palette : [THEME.salbei]).map((c, i) => (
))}
Farbpalette · Foto folgt
); } if (maxHoehe !== undefined) { return (
Stil-Beispiel setFehler(true)} style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
); } return ( Stil-Beispiel setFehler(true)} style={{ width: "100%", borderRadius: 8 }} /> ); } export interface Achse { id: string; negativPol: string; positivPol: string; } export interface Stilprofil { id: string; styleVector: Record; derivedRequirements: string[]; palette: string[]; meta: { method: string; likes: number; dislikes: number; sampleSufficient: boolean }; } /** Radar über die Stilachsen: Wert −1…+1 → Radius 0…1 (0.5 = neutral). */ export function SmartSpider({ vektor, achsen, }: { vektor: Record; achsen: Achse[]; }) { const n = achsen.length; const cx = 110; const cy = 110; const r = 80; const punkt = (i: number, wert: number): string => { const winkel = (2 * Math.PI * i) / n - Math.PI / 2; const radius = r * (0.5 + wert / 2); return `${cx + radius * Math.cos(winkel)},${cy + radius * Math.sin(winkel)}`; }; return ( {[0.25, 0.5, 0.75, 1].map((f) => ( ))} punkt(i, vektor[a.id] ?? 0)).join(" ")} fill={`${THEME.gruen}55`} stroke={THEME.gruen} strokeWidth={2} /> {achsen.map((a, i) => { const winkel = (2 * Math.PI * i) / n - Math.PI / 2; const lx = cx + (r + 16) * Math.cos(winkel); const ly = cy + (r + 16) * Math.sin(winkel); return ( {(vektor[a.id] ?? 0) >= 0 ? a.positivPol : a.negativPol} ); })} ); } const stil = { overlay: { position: "fixed" as const, inset: 0, background: "#000000aa", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 10, }, // CI-Kartenstil (innerer Schein + harter Schlagschatten) für das Overlay. karte: { ...ciKarte, padding: 20, width: 460, maxWidth: "92vw", textAlign: "center" as const, }, // Grosse Swipe-Buttons als Pills; die Hintergrundfarbe setzt jeder Button selbst. knopf: { ...pill, padding: "10px 22px", fontSize: 18 }, }; /** Swipe-Dialog: Bild für Bild liken/ablehnen; Presets als Abkürzung. */ export function StilSwipe({ bilder, onFertig, onAbbruch, }: { bilder: BildItem[]; onFertig: (likes: string[], dislikes: string[], presetId: string | null) => void; onAbbruch: () => void; }) { const [index, setIndex] = useState(0); const [likes, setLikes] = useState([]); const [dislikes, setDislikes] = useState([]); const presets = bilder.filter((b) => b.istPreset); const bild = bilder[index]; const bewerte = (gefaellt: boolean) => { if (!bild) return; if (gefaellt) setLikes([...likes, bild.id]); else setDislikes([...dislikes, bild.id]); if (index + 1 >= bilder.length) { onFertig( gefaellt ? [...likes, bild.id] : likes, gefaellt ? dislikes : [...dislikes, bild.id], null, ); } else { setIndex(index + 1); } }; useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.key === "ArrowRight") bewerte(true); if (e.key === "ArrowLeft") bewerte(false); }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }); if (!bild) return null; return (
e.stopPropagation()}>

Was gefällt dir? ({index + 1}/{bilder.length})

{presets.length > 0 && index === 0 && (

Abkürzung – Preset wählen:{" "} {presets.map((p) => ( ))}

)}
); }