import { DEFAULT_ENTITIES, DEFAULT_THEMES, APP_VERSION, APP_CHANGE } from './js/constants/data.js';
import { drawEntityFace } from './js/utils/entityFaces.js';
import { formatTime } from './js/utils/time.js';
import { generateMaze, spawnEntities } from './js/engine/mapGen.js';
import { updateEntities, updateEntitiesTick, checkLineOfSight } from './js/engine/ai.js';
import { createPlayer, updatePlayer, updatePlayerSnap, getFloatPos, getFloatAngle } from './js/engine/player.js';
import { createKeyState, getIntent, getMapIntent } from './js/engine/input.js';
import { WebGLMazeRenderer } from './js/engine/renderers/webgl/index.js';
import { drawOverlay } from './js/engine/renderers/overlay.js';
import { DEATH_MESSAGES } from './js/constants/deathMessages.js';
import { MenuIcon, XIcon, DatabaseIcon, LogoIcon, BrushIcon, PlusIcon, CopyIcon, TrashIcon, ClockIcon, UserIcon, LayersIcon, RotateCcwIcon, SettingsIcon, InfoIcon, LockIcon, SaveIcon, PlayIcon, QuitIcon } from './js/components/icons.js';
import HUD from './js/components/HUD.js';
import EntityEditor from './js/components/EntityEditor.js';
import DataHUD from './js/components/DataHUD.js';

const { useState, useEffect, useRef, useCallback } = React;

const zoneMapColor = (zone) => {
    if (!zone) return '#334155';
    const m = zone.textureMode || 'solid';
    if (m === 'brick') return zone.brickColor || zone.color || '#334155';
    if (m === 'tiles' || m === 'hex') return zone.tileColor || zone.color || '#334155';
    if (m === 'wood') return zone.woodColor || zone.color || '#334155';
    if (m === 'concrete') return zone.panelColor || zone.color || '#334155';
    if (m === 'checker') return zone.colorA || zone.color || '#334155';
    if (m === 'metal') return zone.metalColor || zone.color || '#334155';
    if (m === 'stucco') return zone.stuccoColor || zone.color || '#334155';
    if (m === 'circuit') return zone.boardColor || zone.color || '#334155';
    return zone.color || '#334155';
};

const resolveMapWallColor = (theme) => {
    const zones = theme.wallZones || [];
    const mode = theme.mapWallMode || 'z0';
    if (mode === 'custom') return theme.mapWallColor || '#334155';
    if (mode === 'cap') return theme.capColor || zoneMapColor(zones[0]) || '#334155';
    if (mode === 'wallOutline') return theme.wallOutlineColor || zoneMapColor(zones[0]) || '#334155';
    const zIdx = mode.startsWith('z') ? (parseInt(mode.slice(1)) || 0) : 0;
    return zoneMapColor(zones[zIdx]) || '#334155';
};

const resolveMapFloorColor = (theme) => {
    const mode = theme.mapFloorMode || 'auto';
    if (mode === 'custom') return theme.mapFloorColor || '#f8fafc';
    if (mode === 'floorOutline') return theme.floorOutlineColor || theme.floor || '#f8fafc';
    return theme.floor || '#f8fafc';
};

const resolveMapExitColor = (theme) => {
    const mode = theme.mapExitMode || 'auto';
    if (mode === 'custom') return theme.mapExitColor || '#ef4444';
    if (mode === 'exitOutline') return theme.exitOutlineColor || theme.exitColor || '#ef4444';
    return theme.exitColor || '#ef4444';
};

function App() {
    const canvasRef = useRef(null);
    const minimapRef = useRef(null);

    const [menuOpen, setMenuOpen] = useState(false);
    const [activeModal, setActiveModal] = useState(null);
    const [gameState, setGameState] = useState('menu');

    const [gameData, setGameData] = useState(() => {
        const saved = localStorage.getItem('maze_data_v1');
        if (saved) return JSON.parse(saved);
        return { themes: DEFAULT_THEMES, entities: DEFAULT_ENTITIES };
    });
    const [savedData, setSavedData] = useState(() => {
        const saved = localStorage.getItem('maze_data_v1');
        if (saved) return JSON.parse(saved);
        return { themes: DEFAULT_THEMES, entities: DEFAULT_ENTITIES };
    });
    const [appSettings, setAppSettings] = useState(() => {
        const saved = localStorage.getItem('maze_settings_v1');
        return saved ? JSON.parse(saved) : { theme: 'system' };
    });
    const [settingsTab, setSettingsTab] = useState('appearance');
    const [startThemeId, setStartThemeId] = useState(gameData.themes[0].id);
    const startThemeIdRef = useRef(gameData.themes[0].id);
    const [lives, setLives] = useState(3);

    const [designerTab, setDesignerTab] = useState('themes');
    const [themeSubTab, setThemeSubTab] = useState('surfaces');
    const [surfacePill, setSurfacePill] = useState('wall');
    const [editingThemeId, setEditingThemeId] = useState(gameData.themes[0].id);
    const [editingEntityId, setEditingEntityId] = useState(gameData.entities[0]?.id);
    const [deleteConfirmItem, setDeleteConfirmItem] = useState(null); // { id, type: 'theme' | 'entity' }

    const savePreset = (preset, type) => {
        setSavedData(prev => {
            const next = { ...prev };
            if (type === 'theme') {
                const idx = next.themes.findIndex(t => t.id === preset.id);
                if (idx >= 0) next.themes = [...next.themes.slice(0, idx), JSON.parse(JSON.stringify(preset)), ...next.themes.slice(idx + 1)];
                else next.themes = [...next.themes, JSON.parse(JSON.stringify(preset))];
            } else {
                const idx = next.entities.findIndex(e => e.id === preset.id);
                if (idx >= 0) next.entities = [...next.entities.slice(0, idx), JSON.parse(JSON.stringify(preset)), ...next.entities.slice(idx + 1)];
                else next.entities = [...next.entities, JSON.parse(JSON.stringify(preset))];
            }
            localStorage.setItem('maze_data_v1', JSON.stringify(next));
            return next;
        });
    };

    const [clearConfirm, setClearConfirm] = useState(false);
    const clearConfirmRef = useRef(false);
    const [viewMode, setViewMode] = useState('3d');
    const [devMode, setDevMode] = useState(false);
    const [devInvis, setDevInvis] = useState(false);
    const [devRadar, setDevRadar] = useState(true);
    const [debugFlags, setDebugFlags] = useState({ map: true, lines: true, floor: true, entities: true, ai: true, exits: true, lights: true, radar: true, raycast: true, webgl: false });
    const [level, setLevel] = useState(1);
    const [currentGridSize, setCurrentGridSize] = useState(21);
    const [scores, setScores] = useState(() => JSON.parse(localStorage.getItem('maze_scores_v1') || '{}'));
    const [mapGenPreview, setMapGenPreview] = useState(null);
    const [mapGenPreviewSize, setMapGenPreviewSize] = useState(21);
    const [textureFiles, setTextureFiles] = useState([]);
    const [entityImageFiles, setEntityImageFiles] = useState([]);
    const [entityFolders, setEntityFolders] = useState([]);
    const [listSearch, setListSearch] = useState('');
    const [listSort, setListSort] = useState(null);       // null | 'az' | 'za'
    const [themeFilterCat, setThemeFilterCat] = useState(null);
    // Two separate questions, deliberately not conflated:
    //  - isMobile: "how much space is there?" — width-only, matches Tailwind's md
    //    breakpoint (768px) exactly, same signal the rest of the app's responsive CSS
    //    already uses. Drives LAYOUT SHAPE: compact single-card HUD vs the wide
    //    Info/Map sidebar, main stacking, canvas aspect ratio.
    //  - isTouchDevice: "can this device press a key?" — capability-based, via
    //    navigator.maxTouchPoints, a static hardware fact that doesn't need a live
    //    listener the way width does. Drives WHICH CONTROLS show: floating icon pill
    //    vs keyboard hint text. A pointer/hover media-query check was tried first but
    //    proved unreliable across DevTools device emulation; maxTouchPoints is the
    //    more dependable signal for real hardware.
    // Without this split, a wide touch device (iPad Pro, 1024px) would get the width-
    // based "desktop" treatment and be shown keyboard hints with no way to act on them.
    const MOBILE_QUERY = '(max-width: 767px)';
    const [isMobile, setIsMobile] = useState(() =>
        typeof window !== 'undefined' && window.matchMedia && window.matchMedia(MOBILE_QUERY).matches
    );
    useEffect(() => {
        if (typeof window === 'undefined' || !window.matchMedia) return;
        const mql = window.matchMedia(MOBILE_QUERY);
        const onChange = () => setIsMobile(mql.matches);
        mql.addEventListener('change', onChange);
        return () => mql.removeEventListener('change', onChange);
    }, []);
    const [isTouchDevice] = useState(() =>
        typeof navigator !== 'undefined' && navigator.maxTouchPoints > 0
    );
    const showTouchControls = isMobile || isTouchDevice;

    // Genuinely short viewports (landscape phones, ~550px height or less) — separate
    // from isMobile (width) on purpose. Caps the game box's height so it can't force
    // page scroll on these, WITHOUT touching typical laptop screens: a common laptop's
    // browser viewport (often ~650-750px tall) is well above this threshold, so the
    // box keeps its normal size there — this cap only exists for the extreme case.
    const SHORT_QUERY = '(max-height: 550px)';
    const [isShortViewport, setIsShortViewport] = useState(() =>
        typeof window !== 'undefined' && window.matchMedia && window.matchMedia(SHORT_QUERY).matches
    );
    useEffect(() => {
        if (typeof window === 'undefined' || !window.matchMedia) return;
        const mql = window.matchMedia(SHORT_QUERY);
        const onChange = () => setIsShortViewport(mql.matches);
        mql.addEventListener('change', onChange);
        return () => mql.removeEventListener('change', onChange);
    }, []);

    const gameStateRef = useRef('menu');
    const devModeRef = useRef(false);
    const devGridRef = useRef(true);
    const devInvisRef = useRef(false);
    const devRadarRef = useRef(true);
    const debugFlagsRef = useRef({ map: true, lines: true, floor: true, entities: true, ai: true, exits: true, lights: true, radar: true, raycast: true, webgl: false });
    const viewModeRef = useRef('3d');
    const webglCanvasRef = useRef(null);
    const gameAreaRef = useRef(null);
    const touchGestureRef = useRef({ active: false, startX: 0, startY: 0, key: null });
    const sidebarRef = useRef(null);
    const webglRendererRef = useRef(null);
    const pixelPreviewCanvasRef = useRef(null);
    const webglPreviewCanvasRef = useRef(null);
    const webglPreviewRendererRef = useRef(null);
    const mapGenPreviewCanvasRef = useRef(null);
    const gridSizeRef = useRef(21);
    const levelRef = useRef(1);
    const livesRef = useRef(3);
    const fpsRef = useRef(null);
    const previewClockRef = useRef(null);
    const mapGenPreviewThemeRef = useRef(null); // tracks which theme the current 2D preview was generated for
    const fpsCanvasRef = useRef(null);
    const entitiesRef = useRef(null);
    const entityCountRef = useRef(null);
    const proximityCanvasRef = useRef(null);
    const overlayCanvasRef = useRef(null);
    const overlayPreviewCanvasRef = useRef(null);
    const mappedPercentRef = useRef(null);
    const keyBufferRef = useRef('');
    const pendingIntentRef = useRef(null);

    const activeModalRef = useRef(activeModal);
    const designerSnapshotRef = useRef(null);
    const themeRef = useRef(gameData.themes[0].theme);
    const dataRef = useRef(gameData);
    const editingThemeIdRef = useRef(editingThemeId);

    // Drag State for Preview Panning
    const isDraggingRef = useRef(false);
    const lastMouseXRef = useRef(0);

    const handlePreviewMouseDown = useCallback((e) => {
        isDraggingRef.current = true; lastMouseXRef.current = e.clientX;
    }, []);

    const handlePreviewMouseMove = useCallback((e) => {
        if (!isDraggingRef.current) return;
        engine.current.player.renderAngle -= (e.clientX - lastMouseXRef.current) * 0.5;
        lastMouseXRef.current = e.clientX;
    }, []);

    const handlePreviewMouseUp = useCallback(() => { isDraggingRef.current = false; }, []);

    // Mobile touch-and-hold movement: swipe direction picks a virtual key (w/a/s/d),
    // held until release — feeds the same engine.current.keys state the keyboard
    // uses, so getIntent/getMapIntent need no changes for either view mode.
    const TOUCH_DEADZONE = 12;
    const TOUCH_SPRINT_THRESHOLD = 70; // drag past this (along the locked axis) engages sprint
    const handleGameTouchStart = useCallback((e) => {
        if (gameStateRef.current !== 'playing') return;
        const t = e.touches[0];
        touchGestureRef.current = { active: true, startX: t.clientX, startY: t.clientY, key: null };
    }, []);

    const handleGameTouchMove = useCallback((e) => {
        const g = touchGestureRef.current;
        if (!g.active) return;
        // Must call this on every touchmove, starting with the first — the browser
        // decides whether the gesture is a scroll on that first event. Calling it
        // only after the deadzone is crossed lets the browser commit to scrolling
        // first, after which every subsequent touchmove is cancelable:false and
        // preventDefault() throws an "ignored" intervention warning on each one.
        if (e.cancelable) e.preventDefault();
        const t = e.touches[0];
        const dx = t.clientX - g.startX, dy = t.clientY - g.startY;
        if (!g.key) {
            if (Math.hypot(dx, dy) < TOUCH_DEADZONE) return;
            const horizontal = Math.abs(dx) > Math.abs(dy);
            g.key = horizontal ? (dx < 0 ? 'a' : 'd') : (dy < 0 ? 'w' : 's');
            engine.current.keys[g.key] = true;
        }
        // Sprint tiers off the same continuous drag distance along the locked axis —
        // drag further to sprint, pull back toward the start point to drop to walk.
        const mag = (g.key === 'a' || g.key === 'd') ? Math.abs(dx) : Math.abs(dy);
        engine.current.keys.Shift = mag > TOUCH_SPRINT_THRESHOLD;
    }, []);

    const handleGameTouchEnd = useCallback(() => {
        const g = touchGestureRef.current;
        if (g.key) engine.current.keys[g.key] = false;
        engine.current.keys.Shift = false;
        touchGestureRef.current = { active: false, startX: 0, startY: 0, key: null };
    }, []);

    // React binds JSX touch handlers as passive, so e.preventDefault() inside
    // handleGameTouchMove throws ("preventDefault inside passive event listener").
    // Attach natively with { passive: false } instead. Always attached, independent
    // of isMobile (which only controls layout/width) — a real touch-capable device
    // (e.g. an iPad at 820px, above the mobile layout breakpoint) should still get
    // swipe controls; these listeners are simply inert on devices that never fire
    // touch events.
    useEffect(() => {
        const el = gameAreaRef.current;
        if (!el) return;
        el.addEventListener('touchstart', handleGameTouchStart, { passive: true });
        el.addEventListener('touchmove', handleGameTouchMove, { passive: false });
        el.addEventListener('touchend', handleGameTouchEnd, { passive: true });
        el.addEventListener('touchcancel', handleGameTouchEnd, { passive: true });
        return () => {
            el.removeEventListener('touchstart', handleGameTouchStart);
            el.removeEventListener('touchmove', handleGameTouchMove);
            el.removeEventListener('touchend', handleGameTouchEnd);
            el.removeEventListener('touchcancel', handleGameTouchEnd);
        };
    }, [handleGameTouchStart, handleGameTouchMove, handleGameTouchEnd]);

    useEffect(() => {
        dataRef.current = gameData;
        if (!gameData.themes.find(t => t.id === startThemeId)) setStartThemeId(gameData.themes[0].id);
    }, [gameData, startThemeId]);

    // MHCore Auto Backups
    useEffect(() => {
        if (typeof window.MHCore !== 'undefined' && window.MHCore.backups) {
            window.MHCore.backups.startAutoBackup('maze_data', { 
                getStateFn: () => localStorage.getItem('maze_data_v1') || '{}',
                intervalMinutes: 10, 
                maxRecent: 5, 
                maxHourly: 24, 
                maxDaily: 7 
            });
        }
        return () => {
            if (typeof window.MHCore !== 'undefined' && window.MHCore.backups) {
                window.MHCore.backups.stopAutoBackup('maze_data');
            }
        };
    }, []);

    useEffect(() => {
        localStorage.setItem('maze_settings_v1', JSON.stringify(appSettings));
        const isDark = appSettings.theme === 'dark' || (appSettings.theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
        if (isDark) {
            document.documentElement.classList.add('dark');
        } else {
            document.documentElement.classList.remove('dark');
        }
    }, [appSettings.theme]);

    useEffect(() => { clearConfirmRef.current = clearConfirm; }, [clearConfirm]);

    useEffect(() => {
        activeModalRef.current = activeModal;
        if (activeModal === 'level_designer') {
            designerSnapshotRef.current = JSON.parse(JSON.stringify(gameData));
        }
    }, [activeModal]); // eslint-disable-line react-hooks/exhaustive-deps
    useEffect(() => { editingThemeIdRef.current = editingThemeId; }, [editingThemeId]);
    useEffect(() => { startThemeIdRef.current = startThemeId; }, [startThemeId]);

    useEffect(() => {
        if (gameState !== 'playing') document.exitPointerLock?.();
    }, [gameState]);

    // Keep sidebar height locked to the game canvas height
    useEffect(() => {
        const area = gameAreaRef.current;
        const sidebar = sidebarRef.current;
        if (!area || !sidebar) return;
        const sync = () => { sidebar.style.height = area.offsetHeight + 'px'; };
        const ro = new ResizeObserver(sync);
        ro.observe(area);
        sync();
        return () => ro.disconnect();
    }, []);

    useEffect(() => {
        const wr = new WebGLMazeRenderer();
        wr.init(webglCanvasRef.current);
        webglRendererRef.current = wr;
        webglPreviewRendererRef.current = new WebGLMazeRenderer();
        return () => { wr.dispose(); webglPreviewRendererRef.current?.dispose(); };
    }, []);

    const loadTextureManifest = useCallback(async () => {
        try {
            const res = await fetch('assets/textures/manifest.json?t=' + Date.now());
            const list = await res.json();
            setTextureFiles(Array.isArray(list) ? list : []);
        } catch { setTextureFiles([]); }
    }, []);

    useEffect(() => { loadTextureManifest(); }, [loadTextureManifest]);

    const loadEntityManifest = useCallback(async () => {
        try {
            const res  = await fetch('assets/entities/manifest.json?t=' + Date.now());
            const data = await res.json();
            const files   = Array.isArray(data) ? data : (data.files ?? []);
            const folders = Array.isArray(data) ? [] : (data.folders ?? []);
            setEntityImageFiles(files);
            const folderMetas = await Promise.all(folders.map(async f => {
                try {
                    const m = await fetch(`assets/entities/${f}/manifest.json`).then(r => r.json());
                    return { folder: f, ...m };
                } catch { return { folder: f, name: f, sheet: 'sheet.png', cols: 4, rows: 1 }; }
            }));
            setEntityFolders(folderMetas);
        } catch { setEntityImageFiles([]); setEntityFolders([]); }
    }, []);

    useEffect(() => { loadEntityManifest(); }, [loadEntityManifest]);


    const engine = useRef({
        map: [], discovered: [], entities: [], rooms: [], isolatedClusters: [], clusterOf: [], clusterPerimeters: [], exit: { x: 0, y: 0 }, timeOfDay: 12, lastFrames: [],
        player: createPlayer(1, 1, 0, -1, 0),
        keys: createKeyState(),
        isPreviewMode: false,
        textureCache: {}
    });
    const staminaBarFillRef = useRef(null);

    const lastTimeRef = useRef(0);
    const requestRef = useRef(null);

    // Single function — generates one map and applies it to BOTH the 2D preview state
    // and the 3D engine. Always called with a fully-constructed lvl object so there is
    // no timing dependency on React state settling.
    const syncPreview = useCallback((lvl) => {
        const mg = lvl.theme.mapGen || {};
        const size = lvl.gridSize || 21;
        const { map, exitCandidates } = generateMaze(size, size, {
            loopFactor: mg.loopFactor ?? 0.08,
            mode: mg.mode ?? 'maze',
            roomDensity: mg.roomDensity ?? 0.15,
            roomMinSize: mg.roomMinSize ?? 4,
            roomMaxSize: mg.roomMaxSize ?? 8,
            columnDensity: mg.columnDensity ?? 0.15,
            columnMaxW: mg.columnMaxW ?? 3,
            columnMaxH: mg.columnMaxH ?? 2,
            seed: mg.seed ?? null,
        });
        map[1][1] = 0;
        const validExits = exitCandidates ?? (() => {
            const e = [];
            for (let y = 1; y < size - 1; y++)
                for (let x = 1; x < size - 1; x++)
                    if (map[y][x] === 0 && Math.hypot(x - 1, y - 1) > size / 2) e.push({ x, y });
            return e;
        })();
        let exit = validExits.length > 0 ? validExits[Math.floor(Math.random() * validExits.length)] : null;
        if (!exit || map[exit.y]?.[exit.x] !== 0) {
            // fallback: furthest reachable floor cell from spawn (handles single-room maps
            // too small to satisfy the hypot > size/2 threshold above)
            let bestDist = -1;
            for (let fy = 1; fy < size - 1; fy++)
                for (let fx = 1; fx < size - 1; fx++)
                    if (map[fy][fx] === 0) { const d = Math.hypot(fx - 1, fy - 1); if (d > bestDist) { bestDist = d; exit = { x: fx, y: fy }; } }
            exit = exit || { x: size - 2, y: size - 2 };
        }
        map[exit.y][exit.x] = 2;

        // 2D preview
        setMapGenPreview(map);

        // 3D engine
        themeRef.current = lvl.theme;
        let startDirX = 0, startDirY = -1, startAngle = 0;
        if (map[2]?.[1] === 0) { startDirX = 0; startDirY = 1; startAngle = 180; }
        else if (map[1]?.[2] === 0) { startDirX = 1; startDirY = 0; startAngle = 90; }
        engine.current.map = map;
        engine.current.exit = exit;
        engine.current.discovered = Array.from({ length: size }, () => Array(size).fill(true));
        let previewFloor = 0;
        for (let y = 1; y < size - 1; y++) for (let x = 1; x < size - 1; x++) if (map[y][x] !== 1) previewFloor++;
        const entCount = Math.max(1, Math.floor(previewFloor * ((lvl.theme.entityDensity ?? 1) + 4 * (lvl.theme.entityDensityScaling ?? 0.12)) / 100));
        engine.current.entities = spawnEntities(map, entCount, size, lvl.theme, 5, dataRef.current.entities);
        engine.current.timeOfDay = lvl.theme.timeOfDay ?? 12;
        engine.current.isPreviewMode = true;
        engine.current.player = createPlayer(1, 1, startDirX, startDirY, startAngle);
        engine.current.tickAccumulator = 0;
    }, []); // eslint-disable-line react-hooks/exhaustive-deps

    // Thin wrapper kept for the Map-tab Regenerate button (uses mapGenPreviewSize slider)
    const regenerateMapGenPreview = useCallback((lvl, sizeOverride) => {
        syncPreview(sizeOverride ? { ...lvl, gridSize: sizeOverride } : lvl);
    }, [syncPreview]);

    useEffect(() => {
        if (activeModal !== 'level_designer') return;
        const lvl = dataRef.current.themes.find(t => t.id === editingThemeId) || dataRef.current.themes[0];
        if (lvl) {
            mapGenPreviewThemeRef.current = editingThemeId;
            setMapGenPreviewSize(lvl.gridSize || 21);
            syncPreview(lvl);
        }
    }, [editingThemeId, activeModal, syncPreview]); // eslint-disable-line react-hooks/exhaustive-deps

    useEffect(() => {
        if (themeSubTab === 'map' && activeModal === 'level_designer') {
            const lvl = gameData.themes.find(t => t.id === editingThemeId);
            if (lvl && mapGenPreviewThemeRef.current !== editingThemeId) {
                mapGenPreviewThemeRef.current = editingThemeId;
                syncPreview(lvl);
            }
        }
    }, [themeSubTab, editingThemeId, activeModal]); // eslint-disable-line react-hooks/exhaustive-deps

    useEffect(() => {
        if (themeSubTab !== 'map' || !mapGenPreview || !mapGenPreviewCanvasRef.current) return;
        const lvl = gameData.themes.find(t => t.id === editingThemeId);
        if (!lvl) return;
        const canvas = mapGenPreviewCanvasRef.current;
        const ctx = canvas.getContext('2d');
        const theme = lvl.theme;
        const size = mapGenPreview.length;
        const cs = canvas.width / size;
        ctx.fillStyle = '#0f172a';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        const mapWallClr = resolveMapWallColor(theme);
        const mapFloorClr = resolveMapFloorColor(theme);
        for (let y = 0; y < size; y++) {
            for (let x = 0; x < size; x++) {
                ctx.fillStyle = mapGenPreview[y][x] === 1
                    ? mapWallClr
                    : mapGenPreview[y][x] === 2
                        ? resolveMapExitColor(theme)
                        : mapFloorClr;
                ctx.fillRect(Math.floor(x * cs), Math.floor(y * cs), Math.ceil(cs) + 1, Math.ceil(cs) + 1);
            }
        }
        ctx.fillStyle = theme.player || '#10b981';
        ctx.beginPath();
        ctx.arc(1.5 * cs, 1.5 * cs, cs * 0.4, 0, Math.PI * 2);
        ctx.fill();
    }, [mapGenPreview, themeSubTab, editingThemeId, gameData]);

    const recordScore = useCallback((themeId, completedLevel) => {
        setScores(prev => {
            const cur = prev[themeId]?.bestLevel || 0;
            if (completedLevel <= cur) return prev;
            const next = { ...prev, [themeId]: { bestLevel: completedLevel } };
            localStorage.setItem('maze_scores_v1', JSON.stringify(next));
            return next;
        });
    }, []);

    const initGame = useCallback((levelOverride = 1) => {
        const themeData = dataRef.current.themes.find(t => t.id === startThemeIdRef.current) || dataRef.current.themes[0];
        levelRef.current = levelOverride;
        setLevel(levelOverride);
        themeRef.current = themeData.theme;

        const baseSize = themeData.gridSize ?? 21;
        const sizeInc = themeData.gridSizeIncrement ?? 0;
        const size = baseSize + (levelOverride - 1) * sizeInc;
        gridSizeRef.current = size;
        setCurrentGridSize(size);

        const mg = themeData.theme.mapGen || {};
        const effectiveSeed = mg.seed != null ? mg.seed + levelOverride - 1 : null;
        const { map: newMap, exitCandidates, rooms } = generateMaze(size, size, {
            loopFactor: mg.loopFactor ?? 0.08,
            mode: mg.mode ?? 'maze',
            roomDensity: mg.roomDensity ?? 0.15,
            roomMinSize: mg.roomMinSize ?? 4,
            roomMaxSize: mg.roomMaxSize ?? 8,
            columnDensity: mg.columnDensity ?? 0.15,
            columnMaxW: mg.columnMaxW ?? 3,
            columnMaxH: mg.columnMaxH ?? 2,
            seed: effectiveSeed,
        });
        newMap[1][1] = 0;

        let startDirX = 0, startDirY = -1, startAngle = 0;
        if (newMap[2][1] === 0) { startDirX = 0; startDirY = 1; startAngle = 180; }
        else if (newMap[1][2] === 0) { startDirX = 1; startDirY = 0; startAngle = 90; }

        const newDiscovered = Array.from({ length: size }, () => Array(size).fill(false));

        for (let y = 0; y < size; y++) {
            for (let x = 0; x < size; x++) {
                if (x === 0 || x === size - 1 || y === 0 || y === size - 1) {
                    newDiscovered[y][x] = true;
                }
            }
        }

        const validExits = exitCandidates ?? (() => {
            const e = [];
            for (let y = 1; y < size - 1; y++)
                for (let x = 1; x < size - 1; x++)
                    if (newMap[y][x] === 0 && Math.hypot(x - 1, y - 1) > size / 2) e.push({x, y});
            return e;
        })();
        let chosen = validExits.length > 0 ? validExits[Math.floor(Math.random() * validExits.length)] : null;
        if (!chosen || newMap[chosen.y]?.[chosen.x] !== 0) {
            // fallback: furthest reachable floor cell from spawn
            let bestDist = -1;
            for (let fy = 1; fy < size - 1; fy++)
                for (let fx = 1; fx < size - 1; fx++)
                    if (newMap[fy][fx] === 0) { const d = Math.hypot(fx - 1, fy - 1); if (d > bestDist) { bestDist = d; chosen = { x: fx, y: fy }; } }
            chosen = chosen || { x: size - 2, y: size - 2 };
        }
        newMap[chosen.y][chosen.x] = 2;

        // Precompute isolated wall clusters (columns) for instant reveal on first ray hit
        const isolatedClusters = [];
        const clusterOf = Array.from({ length: size }, () => Array(size).fill(-1));
        const clusterVisited = Array.from({ length: size }, () => Array(size).fill(false));
        for (let sy = 0; sy < size; sy++) {
            for (let sx = 0; sx < size; sx++) {
                if (newMap[sy][sx] === 1 && !clusterVisited[sy][sx]) {
                    const cells = [];
                    let touchesBoundary = false;
                    const q = [{ x: sx, y: sy }];
                    clusterVisited[sy][sx] = true;
                    while (q.length > 0) {
                        const { x, y } = q.shift();
                        cells.push({ x, y });
                        if (x === 0 || x === size - 1 || y === 0 || y === size - 1) touchesBoundary = true;
                        for (const [dx, dy] of [[-1, 0], [1, 0], [0, -1], [0, 1]]) {
                            const nx = x + dx, ny = y + dy;
                            if (nx >= 0 && nx < size && ny >= 0 && ny < size && newMap[ny][nx] === 1 && !clusterVisited[ny][nx]) {
                                clusterVisited[ny][nx] = true;
                                q.push({ x: nx, y: ny });
                            }
                        }
                    }
                    if (!touchesBoundary) {
                        const idx = isolatedClusters.length;
                        isolatedClusters.push(cells);
                        cells.forEach(({ x, y }) => { clusterOf[y][x] = idx; });
                    }
                }
            }
        }

        // Precompute perimeter cells for each isolated cluster (cells adjacent to floor)
        const clusterPerimeters = isolatedClusters.map(cells =>
            cells.filter(({ x, y }) =>
                [[-1, 0], [1, 0], [0, -1], [0, 1]].some(([dx, dy]) => {
                    const nx = x + dx, ny = y + dy;
                    return nx >= 0 && nx < size && ny >= 0 && ny < size && newMap[ny][nx] !== 1;
                })
            )
        );

        engine.current.map = newMap;
        engine.current.discovered = newDiscovered;
        engine.current.rooms = rooms ?? [];
        engine.current.isolatedClusters = isolatedClusters;
        engine.current.clusterOf = clusterOf;
        engine.current.clusterPerimeters = clusterPerimeters;
        engine.current.prevRoomPos = null;
        engine.current.exit = chosen;

        let floorCells = 0;
        for (let y = 1; y < size - 1; y++) for (let x = 1; x < size - 1; x++) if (newMap[y][x] !== 1) floorCells++;
        const t = themeData.theme;
        const entCount = Math.max(0, Math.floor(
            floorCells * ((t.entityDensity ?? 0) + (levelOverride - 1) * (t.entityDensityScaling ?? 0)) / 100
        ));

        engine.current.entities = spawnEntities(newMap, entCount, size, t, levelOverride, dataRef.current.entities);
        if (levelOverride === 1) engine.current.timeOfDay = themeData.theme.timeOfDay ?? 12;
        engine.current.isPreviewMode = false;

        engine.current.player = createPlayer(1, 1, startDirX, startDirY, startAngle);
        engine.current.tickAccumulator = 0;

        if (levelOverride === 1) {
            livesRef.current = 3;
            setLives(3);
        }

        engine.current.overlay = null;
        engine.current.deathFade = null;
        setGameState('playing');
        gameStateRef.current = 'playing';
        lastTimeRef.current = 0;
        setActiveModal(null);
        setMenuOpen(false);
    }, []);

    const clearOverlay = () => {
        engine.current.overlay = null;
        if (overlayCanvasRef.current) overlayCanvasRef.current.style.pointerEvents = 'none';
    };

    const pickDeathQuip = () => {
        const set = themeRef.current?.deathMessageSet;
        const msgs = set && DEATH_MESSAGES[set];
        return msgs?.length ? msgs[Math.floor(Math.random() * msgs.length)] : null;
    };

    const handleOverlayMouseMove = (e) => {
        const ov = engine.current.overlay;
        const canvas = overlayCanvasRef.current;
        if (!ov || !ov._itemRects?.length || !canvas) return;
        const scaleX = canvas.width / canvas.clientWidth;
        const scaleY = canvas.height / canvas.clientHeight;
        const ix = e.nativeEvent.offsetX * scaleX;
        const iy = e.nativeEvent.offsetY * scaleY;
        let found = -1;
        ov._itemRects.forEach((r, i) => {
            if (ix >= r.x && ix <= r.x + r.w && iy >= r.y && iy <= r.y + r.h) found = i;
        });
        if (found !== -1 && ov.selectedIdx !== found) ov.selectedIdx = found;
        canvas.style.cursor = found !== -1 ? 'pointer' : 'default';
    };

    const handleOverlayClick = () => {
        const ov = engine.current.overlay;
        if (!ov?.items?.length) return;
        const action = ov.items[ov.selectedIdx ?? 0]?.action;
        if (action) action();
    };

    const handleOverlayMouseLeave = () => {
        if (overlayCanvasRef.current) overlayCanvasRef.current.style.cursor = 'default';
    };

    const showOverlay = (type) => {
        const o = { type, selectedIdx: 0, subtitle: null, supertitle: null };
        if (type === 'pause') {
            o.title = 'PAUSED';
            o.items = [
                { label: 'Resume', action: () => { clearOverlay(); setGameState('playing'); gameStateRef.current = 'playing'; lastTimeRef.current = 0; } },
                { label: 'Quit', action: () => showOverlay('quit') },
            ];
        } else if (type === 'death') {
            const remaining = livesRef.current;
            o.title = 'YOU DIED';
            o.supertitle = pickDeathQuip();
            o.subtitle = remaining > 0 ? `${'♥ '.repeat(remaining).trim()}  ${remaining} ${remaining === 1 ? 'life' : 'lives'} remaining` : null;
            o.items = [
                { label: 'Retry', action: () => { clearOverlay(); initGame(levelRef.current); } },
                { label: 'Quit', action: () => showOverlay('quit') },
            ];
        } else if (type === 'gameover') {
            o.title = 'GAME OVER';
            o.items = [
                { label: 'Play Again', action: () => { clearOverlay(); initGame(1); } },
                { label: 'Quit', action: () => showOverlay('quit') },
            ];
        } else if (type === 'win') {
            o.title = 'ESCAPED';
            o.items = [
                { label: 'Next Level', action: () => { clearOverlay(); initGame(levelRef.current + 1); } },
            ];
        } else if (type === 'quit') {
            o.title = 'QUIT?';
            o._prevType = engine.current.overlay?.type ?? null;
            o._prevGameState = gameStateRef.current;
            o.items = [
                { label: 'Yes', action: () => { clearOverlay(); setGameState('menu'); gameStateRef.current = 'menu'; setActiveModal(null); } },
                { label: 'No',  action: () => {
                    const prevType = engine.current.overlay?._prevType;
                    const prevGS = engine.current.overlay?._prevGameState;
                    clearOverlay();
                    if (prevType) {
                        showOverlay(prevType);
                    } else if (prevGS === 'playing') {
                        setGameState('playing'); gameStateRef.current = 'playing'; lastTimeRef.current = 0;
                    }
                }},
            ];
        }
        engine.current.overlay = o;
        if (overlayCanvasRef.current) {
            overlayCanvasRef.current.style.pointerEvents = o.items?.length > 0 ? 'auto' : 'none';
        }
    };

    useEffect(() => {
        if ((themeSubTab !== 'environment' && themeSubTab !== 'menu') || !overlayPreviewCanvasRef.current || activeModal !== 'level_designer') return;
        const lvl = gameData.themes.find(t => t.id === editingThemeId);
        if (!lvl) return;
        const msgSet = lvl.theme.deathMessageSet;
        const msgs = msgSet && DEATH_MESSAGES[msgSet];
        const sampleQuip = msgs?.length ? msgs[Math.floor(Math.random() * msgs.length)] : null;
        const syntheticOverlay = {
            type: 'death', title: 'YOU DIED',
            supertitle: sampleQuip, subtitle: null, selectedIdx: 0,
            items: [{ label: 'Retry Level' }],
        };
        drawOverlay(overlayPreviewCanvasRef.current, syntheticOverlay, null, lvl.theme);
    }, [themeSubTab, editingThemeId, gameData, activeModal]);

    useEffect(() => {
        const handleKeyDown = (e) => {
            if (document.activeElement && ['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName)) return;

            // Overlay navigation — intercepts all movement/confirm/cancel keys
            const ov = engine.current.overlay;
            if (ov?.items?.length) {
                if (e.key === 'ArrowUp' || e.key === 'w' || e.key === 'W') {
                    ov.selectedIdx = (ov.selectedIdx - 1 + ov.items.length) % ov.items.length;
                    e.preventDefault(); return;
                }
                if (e.key === 'ArrowDown' || e.key === 's' || e.key === 'S') {
                    ov.selectedIdx = (ov.selectedIdx + 1) % ov.items.length;
                    e.preventDefault(); return;
                }
                if (e.code === 'Enter' || e.code === 'Space') {
                    ov.items[ov.selectedIdx].action();
                    e.preventDefault(); return;
                }
                if (e.code === 'Escape') {
                    if (ov.type === 'pause') {
                        clearOverlay();
                        setGameState('playing'); gameStateRef.current = 'playing'; lastTimeRef.current = 0;
                    } else if (ov.type === 'quit') {
                        ov.items[ov.items.length - 1].action(); // No
                    }
                    // death / gameover / win: ESC does nothing
                    e.preventDefault(); return;
                }
            }

            if (engine.current.keys.hasOwnProperty(e.key)) {
                // Arrow keys/Space would otherwise scroll the page (or, on itch.io,
                // the embedding page around the iframe) — they're fully claimed by
                // movement during play, so stop the browser's default scroll.
                e.preventDefault();
                engine.current.keys[e.key] = true;
                // Buffer intent for classic-mode tick consumption
                if (themeRef.current?.moveMode === 'classic') {
                    const ki = (viewModeRef.current === 'map' && activeModalRef.current !== 'level_designer') ? getMapIntent(engine.current.keys) : getIntent(engine.current.keys);
                    if (ki.action !== null) pendingIntentRef.current = ki;
                }
            }

            if (e.code === 'Enter' && activeModalRef.current && activeModalRef.current !== 'level_designer') {
                e.preventDefault();
                if (activeModalRef.current === 'data') {
                    if (!clearConfirmRef.current) {
                        setClearConfirm(true);
                    } else {
                        localStorage.removeItem('maze_data_v2');
                        const defaults = { themes: DEFAULT_THEMES, entities: DEFAULT_ENTITIES };
                        setGameData(defaults); dataRef.current = defaults;
                        themeRef.current = DEFAULT_THEMES[0].theme;
                        setStartThemeId(DEFAULT_THEMES[0].id);
                        setEditingThemeId(DEFAULT_THEMES[0].id);
                        setActiveModal(null); setMenuOpen(false); setClearConfirm(false);
                    }
                }
                return;
            }

            if (e.code === 'Space') {
                e.preventDefault();
                if (gameStateRef.current === 'playing') {
                    setGameState('paused'); gameStateRef.current = 'paused';
                    showOverlay('pause');
                } else if (gameStateRef.current === 'paused' && !engine.current.overlay) {
                    setGameState('playing'); gameStateRef.current = 'playing';
                    lastTimeRef.current = 0;
                }
            }

            if (e.code === 'Escape') {
                if (activeModalRef.current === 'data' && clearConfirmRef.current) {
                    setClearConfirm(false);
                } else if (activeModalRef.current !== null) {
                    setActiveModal(null); setMenuOpen(false);
                } else if (gameStateRef.current === 'playing') {
                    setGameState('paused'); gameStateRef.current = 'paused';
                    showOverlay('pause');
                }
                // paused with overlay: ESC handled in overlay block above
                // dying / gameover / win / menu: ESC does nothing
            }

            let devToggled = false;
            if (e.key.length === 1 || e.key === '/') {
                keyBufferRef.current = (keyBufferRef.current + e.key).slice(-3);
                if (keyBufferRef.current === '///') {
                    const newMode = !devModeRef.current;
                    setDevMode(newMode); devModeRef.current = newMode;
                    keyBufferRef.current = ''; devToggled = true;
                }
            }

            if (e.key.toLowerCase() === 'v' && gameStateRef.current !== 'menu' && !devToggled && activeModalRef.current !== 'level_designer') {
                const newView = viewModeRef.current === 'map' ? '3d' : 'map';
                setViewMode(newView); viewModeRef.current = newView;
            }

            if (devModeRef.current) {
                const size = gridSizeRef.current;
                if (e.key === 'n' || e.key === 'N') initGame(levelRef.current + 1);
                else if (e.key === 'g' || e.key === 'G') { devGridRef.current = !devGridRef.current; }
                else if (e.key === 'i' || e.key === 'I') { devInvisRef.current = !devInvisRef.current; setDevInvis(devInvisRef.current); }
                else if (e.key === 'r' || e.key === 'R') { devRadarRef.current = !devRadarRef.current; setDevRadar(devRadarRef.current); }
                else if (e.key === '+' || e.key === '=') { const occupied = new Set([`${Math.round(engine.current.player.x)},${Math.round(engine.current.player.y)}`, ...engine.current.entities.map(e => `${e.x},${e.y}`)]); const spawned = spawnEntities(engine.current.map, 1, size, themeRef.current, levelRef.current, dataRef.current.entities, occupied); if (spawned.length) engine.current.entities = [...engine.current.entities, ...spawned]; }
                else if (e.key === '-' || e.key === '_') { if (engine.current.entities.length > 0) engine.current.entities = engine.current.entities.slice(0, -1); }
                else if (e.key === 'h') {
                    for (let y = 1; y < size - 1; y++)
                        for (let x = 1; x < size - 1; x++)
                            if (engine.current.map[y][x] === 1 && Math.random() < 0.3) engine.current.map[y][x] = 0;
                    // New reference so WebGL renderer detects the change (it checks referential equality)
                    engine.current.map = engine.current.map.slice();
                }
            }
        };

        const handleKeyUp = (e) => {
            if (document.activeElement && ['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName)) return;
            if (engine.current.keys.hasOwnProperty(e.key)) engine.current.keys[e.key] = false;
        };

        window.addEventListener('keydown', handleKeyDown);
        window.addEventListener('keyup', handleKeyUp);
        return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); };
    }, []);

    useEffect(() => { debugFlagsRef.current = debugFlags; }, [debugFlags]);

    const updateEngine = useCallback(async (time) => {
        if (lastTimeRef.current === 0) lastTimeRef.current = time;
        const deltaTime = time - lastTimeRef.current;
        lastTimeRef.current = time;

        const state = engine.current;

        // FPS Tracking
        const now = performance.now();
        state.lastFrames.push(now);
        while (state.lastFrames.length > 0 && state.lastFrames[0] <= now - 1000) {
            state.lastFrames.shift();
        }
        if (devModeRef.current) {
            if (fpsRef.current) fpsRef.current.innerText = state.lastFrames.length;
            if (entitiesRef.current) entitiesRef.current.innerText = engine.current.entities.length;
            
            if (fpsCanvasRef.current && (!engine.current.lastCanvasUpdate || now - engine.current.lastCanvasUpdate > 250)) {
                engine.current.lastCanvasUpdate = now;
                const ctx = fpsCanvasRef.current.getContext('2d', { willReadFrequently: true });
                const w = fpsCanvasRef.current.width;
                const h = fpsCanvasRef.current.height;
                const imageData = ctx.getImageData(1, 0, w - 1, h);
                ctx.putImageData(imageData, 0, 0);
                
                ctx.fillStyle = '#0f172a';
                ctx.fillRect(w - 1, 0, 1, h);
                
                const frames = state.lastFrames.length;
                const normalizedFps = Math.min(60, frames) / 60;
                const barHeight = Math.max(1, Math.floor(normalizedFps * h));
                
                if (frames >= 55) ctx.fillStyle = '#4ade80';
                else if (frames >= 30) ctx.fillStyle = '#fbbf24';
                else ctx.fillStyle = '#ef4444';
                
                ctx.fillRect(w - 1, h - barHeight, 1, barHeight);
            }
        }
        if (entityCountRef.current) {
            entityCountRef.current.innerText = engine.current.entities.length;
        }

        // Death fade update — runs even when not playable
        if (gameStateRef.current === 'dying' && engine.current.deathFade) {
            const df = engine.current.deathFade;
            const fadeInMs  = themeRef.current?.deathFadeInMs   ?? 800;
            const maxAlpha  = themeRef.current?.deathFadeMaxAlpha ?? 0.5;
            if (df.phase === 'in') {
                df.alpha = Math.min(maxAlpha, df.alpha + deltaTime / fadeInMs * maxAlpha);
                if (df.alpha >= maxAlpha) {
                    const remaining = livesRef.current;
                    setGameState('gameover'); gameStateRef.current = 'gameover';
                    showOverlay(remaining > 0 ? 'death' : 'gameover');
                    df.phase = 'done';
                }
            }
        }

        const isPlayable = gameStateRef.current === 'playing' || activeModalRef.current === 'level_designer';

        // Real-time synchronization of entity templates for the Designer preview
        state.entities.forEach(ent => {
            if (ent.template && ent.template.id) {
                const freshTemplate = dataRef.current.entities.find(e => e.id === ent.template.id);
                if (freshTemplate) ent.template = freshTemplate;
            }
        });

        if (!isPlayable) {
            const isMenuOrEnd = ['menu', 'gameover', 'gamewin'].includes(gameStateRef.current);
            if (!isMenuOrEnd) {
                if (viewModeRef.current === '3d') {
                    await webglRendererRef.current?.render(state, themeRef.current, devModeRef.current && devGridRef.current, deltaTime, debugFlagsRef.current);
                } else {
                    drawMapView();
                }
            } else if (viewModeRef.current === 'map') {
                const canvas = canvasRef.current;
                if (canvas) {
                    const ctx = canvas.getContext('2d');
                    ctx.fillStyle = '#0f172a';
                    ctx.fillRect(0, 0, canvas.width, canvas.height);
                }
            }
            drawOverlay(overlayCanvasRef.current, engine.current.overlay ?? null, engine.current.deathFade ?? null, themeRef.current);
            requestRef.current = requestAnimationFrame(updateEngine);
            return;
        }

        if (themeRef.current.timeSpeed > 0) {
            state.timeOfDay = (state.timeOfDay + (deltaTime / 1000) * themeRef.current.timeSpeed) % 24;
        }

        const timeStr = formatTime(state.timeOfDay);
        if (previewClockRef.current) previewClockRef.current.innerText = timeStr;

        const p = state.player;
        const isClassic = themeRef.current?.moveMode === 'classic';
        let skipFrameLogic = false;

        if (isClassic) {
            state.tickAccumulator = (state.tickAccumulator || 0) + deltaTime;
            const effectiveInterval = Math.max(200,
                (themeRef.current.tickInterval ?? 1000) *
                Math.pow(themeRef.current.tickSpeedFactor ?? 0.95, levelRef.current - 1));
            if (state.tickAccumulator >= effectiveInterval) {
                state.tickAccumulator -= effectiveInterval;
                const heldIntent = (viewModeRef.current === 'map' && activeModalRef.current !== 'level_designer') ? getMapIntent(state.keys) : getIntent(state.keys);
                const intent = heldIntent.action !== null ? heldIntent : (pendingIntentRef.current || { action: null, sprint: false });
                pendingIntentRef.current = null;
                const { hitExit } = updatePlayerSnap(p, intent, state.map);
                if (hitExit && !state.isPreviewMode) { setGameState('gamewin'); gameStateRef.current = 'gamewin'; showOverlay('win'); recordScore(startThemeIdRef.current, levelRef.current); }
                if (!debugFlagsRef.current || debugFlagsRef.current.ai !== false) {
                    updateEntitiesTick(state.entities, state.map, p,
                        (debugFlagsRef.current?.raycast === false) ? 0 : (themeRef.current?.sightRadius ?? Infinity),
                        themeRef.current?.entityBaseSpeed ?? 1.0,
                        themeRef.current?.entitySpeedScaling ?? 0.0,
                        levelRef.current,
                        themeRef.current?.chaseMemory ?? 10,
                        true, state.isPreviewMode || devInvisRef.current);
                }
            } else {
                skipFrameLogic = true;
            }
        } else {
            const intent = viewModeRef.current === 'map' ? getMapIntent(state.keys) : getIntent(state.keys);
            const { hitExit } = updatePlayer(p, intent, state.map, deltaTime);
            if (hitExit && !state.isPreviewMode) { setGameState('gamewin'); gameStateRef.current = 'gamewin'; showOverlay('win'); recordScore(startThemeIdRef.current, levelRef.current); }

            if (staminaBarFillRef.current) {
                const pct = (p.stamina / p.maxStamina) * 100;
                staminaBarFillRef.current.style.width = pct + '%';
                staminaBarFillRef.current.style.backgroundColor = pct > 25 ? '#10b981' : pct > 10 ? '#f59e0b' : '#ef4444';
            }

            updateEntities(state.entities, state.map, p, deltaTime, state.isPreviewMode || devInvisRef.current,
                           themeRef.current?.sightRadius ?? Infinity);
        }

        if (!skipFrameLogic) {
            // Room reveal: entering any cell of a room reveals its whole interior
            if (state.rooms && state.rooms.length > 0) {
                const px = Math.round(p.renderX), py = Math.round(p.renderY);
                const prev = state.prevRoomPos;
                if (!prev || prev.x !== px || prev.y !== py) {
                    state.prevRoomPos = { x: px, y: py };
                    const room = state.rooms.find(r => px >= r.x && px < r.x + r.w && py >= r.y && py < r.y + r.h);
                    if (room) {
                        const mapH = state.map.length, mapW = state.map[0].length;
                        for (let ry = room.y - 1; ry <= room.y + room.h; ry++)
                            for (let rx = room.x - 1; rx <= room.x + room.w; rx++)
                                if (ry >= 0 && ry < mapH && rx >= 0 && rx < mapW)
                                    state.discovered[ry][rx] = true;
                    }
                }
            }

            // Line-of-sight discovery — 180° FOV centred on player facing direction.
            // renderAngle 0=north, 90=east; convert to math angle (0=east): subtract 90.
            const centerRayAngle = p.renderAngle - 90;
            const maxRayDist = themeRef.current?.sightRadius ?? null;
            const initDiscMapX = Math.floor(p.renderX + 0.5);
            const initDiscMapY = Math.floor(p.renderY + 0.5);
            for (let offset = -90; offset <= 90; offset += 2) {
                const rad = (centerRayAngle + offset) * (Math.PI / 180);
                const dirX = Math.cos(rad);
                const dirY = Math.sin(rad);

                let mapX = Math.floor(p.renderX + 0.5);
                let mapY = Math.floor(p.renderY + 0.5);

                const deltaDistX = Math.abs(1 / (dirX || 1e-10));
                const deltaDistY = Math.abs(1 / (dirY || 1e-10));

                let stepX, stepY, sideDistX, sideDistY;

                if (dirX < 0) { stepX = -1; sideDistX = (p.renderX + 0.5 - mapX) * deltaDistX; }
                else { stepX = 1; sideDistX = (mapX + 1.0 - (p.renderX + 0.5)) * deltaDistX; }

                if (dirY < 0) { stepY = -1; sideDistY = (p.renderY + 0.5 - mapY) * deltaDistY; }
                else { stepY = 1; sideDistY = (mapY + 1.0 - (p.renderY + 0.5)) * deltaDistY; }

                if (mapY >= 0 && mapY < state.map.length && mapX >= 0 && mapX < state.map[0].length) {
                    state.discovered[mapY][mapX] = true;
                }

                let hit = false;
                while (!hit) {
                    if (sideDistX < sideDistY) { sideDistX += deltaDistX; mapX += stepX; }
                    else { sideDistY += deltaDistY; mapY += stepY; }

                    if (mapX < 0 || mapX >= state.map[0].length || mapY < 0 || mapY >= state.map.length) break;
                    if (maxRayDist !== null && Math.hypot(mapX - initDiscMapX, mapY - initDiscMapY) > maxRayDist) break;

                    state.discovered[mapY][mapX] = true;
                    if (state.map[mapY][mapX] > 0) hit = true;
                }
            }

            // Fill isolated clusters whose every perimeter cell is now discovered
            if (state.clusterPerimeters) {
                state.clusterPerimeters.forEach((perim, ci) => {
                    if (perim.length > 0 && perim.every(c => state.discovered[c.y]?.[c.x])) {
                        state.isolatedClusters[ci].forEach(c => { state.discovered[c.y][c.x] = true; });
                    }
                });
            }
        }

        // Update entity render positions
        if (!isClassic) {
            state.entities.forEach(ent => {
                const eFloat = getFloatPos(ent);
                ent.renderX = eFloat.x; ent.renderY = eFloat.y; ent.renderAngle = getFloatAngle(ent);
            });
        } else {
            state.entities.forEach(ent => {
                ent.renderX = ent.x; ent.renderY = ent.y; ent.renderAngle = ent.angle ?? 180;
            });
        }

        // Collision / catch check
        state.entities.forEach(ent => {
            if (!state.isPreviewMode && !devInvisRef.current && Math.hypot(p.renderX - (ent.renderX ?? ent.x), p.renderY - (ent.renderY ?? ent.y)) < 0.8 && gameStateRef.current === 'playing') {
                const newLives = Math.max(0, livesRef.current - 1);
                livesRef.current = newLives;
                setLives(newLives);
                const dm = themeRef.current?.deathMode ?? 'instant';
                if (dm === 'fade') {
                    setGameState('dying'); gameStateRef.current = 'dying';
                    engine.current.deathFade = {
                        alpha: 0, phase: 'in',
                        color: themeRef.current?.deathFadeColor ?? '#cc0000',
                    };
                } else {
                    setGameState('gameover'); gameStateRef.current = 'gameover';
                    showOverlay(newLives > 0 ? 'death' : 'gameover');
                }
            }
        });

        if (activeModalRef.current === 'level_designer') {
            const pwr = webglPreviewRendererRef.current;
            const previewCanvas = webglPreviewCanvasRef.current;
            if (pwr && previewCanvas) {
                if (!pwr.threeRenderer || pwr.threeRenderer.domElement !== previewCanvas) {
                    pwr.dispose();
                    pwr.init(previewCanvas);
                }
                await pwr.render(state, themeRef.current, devModeRef.current && devGridRef.current, deltaTime);
                const ps = themeRef.current?.pixelScale ?? 1;
                if (ps < 1 && pixelPreviewCanvasRef.current) {
                    const wc = previewCanvas, pc = pixelPreviewCanvasRef.current;
                    if (pc.width !== wc.width || pc.height !== wc.height) {
                        pc.width = wc.width; pc.height = wc.height;
                        pc.style.width = wc.width + 'px'; pc.style.height = wc.height + 'px';
                    }
                    pc.getContext('2d')?.drawImage(wc, 0, 0);
                }
            }
        } else if (viewModeRef.current === '3d') {
            await webglRendererRef.current?.render(state, themeRef.current, devModeRef.current && devGridRef.current, deltaTime, debugFlagsRef.current);
        } else {
            drawMapView();
        }

        if (activeModalRef.current === null && viewModeRef.current !== 'map') renderMinimap();
        if (viewModeRef.current !== 'map' && (themeRef.current?.showRadar ?? true)) {
            drawProximityRadar(proximityCanvasRef.current, engine.current);
        } else {
            const pc = proximityCanvasRef.current;
            if (pc) pc.getContext('2d').clearRect(0, 0, pc.width, pc.height);
        }
        drawOverlay(overlayCanvasRef.current, engine.current.overlay ?? null, engine.current.deathFade ?? null, themeRef.current);
        requestRef.current = requestAnimationFrame(updateEngine);
    }, []);

    useEffect(() => {
        requestRef.current = requestAnimationFrame(updateEngine);
        return () => cancelAnimationFrame(requestRef.current);
    }, [updateEngine]);

    const drawMapView = () => {
        const canvas = canvasRef.current;
        if (!canvas) return;
        const ctx = canvas.getContext('2d');
        const theme = themeRef.current;
        const state = engine.current;
        if (!state.map || !state.discovered || state.map.length === 0) return;

        const W = canvas.width, H = canvas.height;
        const size = state.map.length;
        const cs = Math.ceil(Math.min(W, H) / size);
        const ox = 0, oy = 0;

        ctx.fillStyle = '#0f172a';
        ctx.fillRect(0, 0, W, H);

        const baseWallColor = resolveMapWallColor(theme);

        for (let y = 0; y < size; y++) {
            for (let x = 0; x < size; x++) {
                if (!state.discovered[y]?.[x] && !devModeRef.current) continue;
                const dx = ox + x * cs, dy = oy + y * cs;
                const v = state.map[y][x];
                if (v === 1) ctx.fillStyle = baseWallColor;
                else if (v === 2) ctx.fillStyle = resolveMapExitColor(theme);
                else ctx.fillStyle = resolveMapFloorColor(theme);
                ctx.fillRect(dx, dy, cs + 1, cs + 1);
            }
        }

        // Exit outline — mirrors minimap logic
        if (theme.exitOutlineColor) {
            ctx.strokeStyle = theme.exitOutlineColor;
            ctx.lineWidth = Math.max(1, cs * 0.2);
            for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
                if ((state.discovered[y]?.[x] || devModeRef.current) && state.map[y][x] === 2) {
                    const lw = ctx.lineWidth;
                    ctx.strokeRect(ox + x * cs + lw * 0.5, oy + y * cs + lw * 0.5, cs + 1 - lw, cs + 1 - lw);
                }
            }
        }

        const floorHex = resolveMapFloorColor(theme).replace('#', '');
        const floorLuma = 0.299 * parseInt(floorHex.slice(0,2), 16) + 0.587 * parseInt(floorHex.slice(2,4), 16) + 0.114 * parseInt(floorHex.slice(4,6), 16);
        const devOutlineColor = floorLuma > 128 ? 'rgba(0,0,0,0.75)' : 'rgba(255,255,255,0.75)';
        const sightR = theme.sightRadius ?? Infinity;
        const p = state.player;

        state.entities.forEach(ent => {
            const ex = ox + (ent.renderX + 0.5) * cs, ey = oy + (ent.renderY + 0.5) * cs;
            if (devModeRef.current) {
                ctx.strokeStyle = devOutlineColor; ctx.lineWidth = 1;
                ctx.beginPath(); ctx.arc(ex, ey, cs * 0.3, 0, Math.PI * 2); ctx.stroke();
            }
            const inLOS = checkLineOfSight(ent.x, ent.y, p.x, p.y, state.map, sightR);
            if (inLOS || (devModeRef.current && (debugFlagsRef.current?.radar ?? devRadarRef.current))) {
                const t = ent.template;
                const entState = ent.state === 'chasing' ? 'chasing' : 'wandering';
                if (t?.renderType === 'file' || !t) {
                    ctx.fillStyle = getEntityMapColor(t, entState);
                    ctx.beginPath(); ctx.arc(ex, ey, cs * 0.3, 0, Math.PI * 2); ctx.fill();
                } else {
                    const bodyColor    = entState === 'chasing' ? (t.chaseColor || '#ef4444') : (t.primaryColor || '#ffff00');
                    const featureColor = entState === 'chasing' ? (t.chaseFeatureColor || t.featureColor || '#000000') : (t.featureColor || '#000000');
                    ctx.save();
                    ctx.translate(ex - cs / 2, ey - cs / 2);
                    drawEntityFace(ctx, cs, cs, t.type, bodyColor, featureColor, t.faceScale2D ?? 1.0, entState, 0, true);
                    ctx.restore();
                }
            }
        });

        const px = ox + (p.renderX + 0.5) * cs, py = oy + (p.renderY + 0.5) * cs;
        const pr = cs * 0.4;
        ctx.save(); ctx.translate(px, py); ctx.rotate((p.renderAngle * Math.PI) / 180);
        ctx.fillStyle = theme.player; ctx.beginPath(); ctx.arc(0, 0, pr, 0, Math.PI * 2); ctx.fill();
        ctx.fillStyle = '#ffffff'; ctx.beginPath(); ctx.moveTo(0, -pr * 1.2); ctx.lineTo(-pr * 0.4, -pr * 0.2); ctx.lineTo(pr * 0.4, -pr * 0.2); ctx.fill();
        ctx.restore();

    };

    const drawProximityRadar = (canvas, state) => {
        if (!canvas) return;
        const W = canvas.offsetWidth, H = canvas.offsetHeight;
        if (W === 0 || H === 0) return;
        if (canvas.width !== W || canvas.height !== H) { canvas.width = W; canvas.height = H; }
        const ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, W, H);

        const gs = gameStateRef.current;
        if (gs !== 'playing' && gs !== 'paused') return;

        const player = state.player;
        if (!player || !state.entities) return;

        // Full-canvas centred overlay
        const cx = W / 2, cy = H / 2;
        const R = Math.min(W, H) * 0.46;

        // Ring radii — equal width (0.155R) per ring, 0.03R gaps, center hole 0.28R
        const r1i = R * 0.280, r1o = R * 0.435;
        const r2i = R * 0.465, r2o = R * 0.620;
        const r3i = R * 0.650, r3o = R * 0.805;
        const r4i = R * 0.835, r4o = R * 0.990;

        // Segment half-spans (active arc / 2, in radians)
        const hs1 = 0.3316; // 38° / 2  (45° slot − 7° gap)
        const hs2 = 0.1571; // 18° / 2  (22.5° slot − 4.5° gap)
        const hs3 = 0.1571; // 22.5° spurs, same visual gap as ring 2
        const hs4 = 0.1571;

        const ring1 = new Array(8).fill(null);
        const ring2 = new Array(16).fill(null);
        const spur3 = new Array(4).fill(null);
        const spur4 = new Array(4).fill(null);

        const cardinalRels = [0, Math.PI / 2, Math.PI, -Math.PI / 2];
        const PI2 = Math.PI * 2;
        // Buckets hold { state, color } — chasing beats idle; first entity wins on ties
        const worse = (cur, next) => (!cur || (next.state === 'chasing' && cur.state !== 'chasing')) ? next : cur;
        const playerRad = (player.renderAngle - 90) * Math.PI / 180;

        const radarSightR = themeRef.current?.sightRadius ?? Infinity;
        for (const ent of state.entities) {
            const dx = ent.renderX - player.renderX;
            const dy = ent.renderY - player.renderY;
            const dist = Math.hypot(dx, dy);
            if (dist > 5) continue;
            if (!checkLineOfSight(ent.x, ent.y, player.x, player.y, state.map, radarSightR)) continue;

            let rel = Math.atan2(dy, dx) - playerRad;
            while (rel >  Math.PI) rel -= PI2;
            while (rel < -Math.PI) rel += PI2;

            const isChasing = ent.state === 'chasing';
            const t = ent.template;
            const col = isChasing
                ? getEntityMapColor(t, 'chasing')
                : getEntityMapColor(t, 'wandering');
            const seg = { state: isChasing ? 'chasing' : 'idle', color: col };

            if (dist <= 1.5) {
                const k = Math.floor(((rel + Math.PI / 8 + PI2) % PI2) / (Math.PI / 4)) % 8;
                ring1[k] = worse(ring1[k], seg);
            } else if (dist <= 3) {
                const k = Math.floor(((rel + Math.PI / 16 + PI2) % PI2) / (Math.PI / 8)) % 16;
                ring2[k] = worse(ring2[k], seg);
            } else {
                for (let c = 0; c < 4; c++) {
                    let d = rel - cardinalRels[c];
                    while (d >  Math.PI) d -= PI2;
                    while (d < -Math.PI) d += PI2;
                    if (Math.abs(d) <= Math.PI / 16) {
                        if (dist <= 4) spur3[c] = worse(spur3[c], seg);
                        else           spur4[c] = worse(spur4[c], seg);
                    }
                }
            }
        }

        const segPath = (drawA, halfSpan, ri, ro) => {
            ctx.beginPath();
            ctx.arc(cx, cy, ro, drawA - halfSpan, drawA + halfSpan, false);
            ctx.arc(cx, cy, ri, drawA + halfSpan, drawA - halfSpan, true);
            ctx.closePath();
        };

        const drawSeg = (seg, drawA, halfSpan, ri, ro) => {
            if (!seg) return;
            ctx.globalAlpha = 0.75;
            ctx.fillStyle = seg.color;
            segPath(drawA, halfSpan, ri, ro);
            ctx.fill();
            ctx.globalAlpha = 1;
        };

        const cardinalDraw = [-Math.PI / 2, 0, Math.PI / 2, Math.PI];

        // Dev radar overlay — green outlines of all segments
        if (devModeRef.current && devRadarRef.current) {
            ctx.strokeStyle = 'rgba(74,222,128,0.4)';
            ctx.lineWidth = 1;
            for (let k = 0; k < 8; k++) { segPath(k * Math.PI / 4 - Math.PI / 2, hs1, r1i, r1o); ctx.stroke(); }
            for (let k = 0; k < 16; k++) { segPath(k * Math.PI / 8 - Math.PI / 2, hs2, r2i, r2o); ctx.stroke(); }
            for (let c = 0; c < 4; c++) {
                segPath(cardinalDraw[c], hs3, r3i, r3o); ctx.stroke();
                segPath(cardinalDraw[c], hs4, r4i, r4o); ctx.stroke();
            }
        }

        // Ring 1: 8 segments, forward = draw angle -π/2
        for (let k = 0; k < 8; k++) drawSeg(ring1[k], k * Math.PI / 4 - Math.PI / 2, hs1, r1i, r1o);

        // Ring 2: 16 segments
        for (let k = 0; k < 16; k++) drawSeg(ring2[k], k * Math.PI / 8 - Math.PI / 2, hs2, r2i, r2o);

        // Rings 3 & 4: cardinal spurs (fwd=-π/2, right=0, back=π/2, left=π)
        for (let c = 0; c < 4; c++) {
            drawSeg(spur3[c], cardinalDraw[c], hs3, r3i, r3o);
            drawSeg(spur4[c], cardinalDraw[c], hs4, r4i, r4o);
        }
    };

    const getEntityMapColor = (t, entState) => {
        if (!t) return '#ffffff';
        const isChasing = entState === 'chasing';
        if (isChasing && t.mapChaseColor) return t.mapChaseColor;
        if (!isChasing && t.mapIdleColor) return t.mapIdleColor;
        if (!t.mapColor || t.mapColor === 'primary')
            return isChasing ? (t.chaseColor || t.primaryColor || '#ffffff') : (t.primaryColor || '#ffffff');
        if (t.mapColor === 'feature')
            return isChasing ? (t.chaseFeatureColor || t.featureColor || '#000000') : (t.featureColor || '#000000');
        return t.mapColor;
    };

    const getEntityMapArrowColor = (t, entState) => {
        if (!t) return '#ffffff';
        const isChasing = entState === 'chasing';
        if (t.renderType === 'file') {
            return isChasing ? (t.mapArrowChaseColor || '#ffffff') : (t.mapArrowIdleColor || '#ffffff');
        }
        // Built-in: use the color NOT used for the dot
        if (t.mapColor === 'feature')
            return isChasing ? (t.chaseColor || t.primaryColor || '#ffffff') : (t.primaryColor || '#ffffff');
        return isChasing ? (t.chaseFeatureColor || t.featureColor || '#ffffff') : (t.featureColor || '#ffffff');
    };

    const renderMinimap = () => {
        const mCanvas = minimapRef.current;
        if (!mCanvas) return;
        const mCtx = mCanvas.getContext('2d');
        const theme = themeRef.current;
        const state = engine.current;

        mCtx.fillStyle = '#0f172a'; mCtx.fillRect(0, 0, mCanvas.width, mCanvas.height);
        if (!state.map || state.map.length === 0 || !state.discovered) return;

        const size = state.map.length, mCellSize = mCanvas.width / size;
        const baseWallColor = resolveMapWallColor(theme);
        const baseFloorColor = resolveMapFloorColor(theme);
        let discoveredCount = 0;

        for (let y = 0; y < size; y++) {
            for (let x = 0; x < size; x++) {
                if (state.discovered[y][x]) discoveredCount++;
                if (state.discovered[y][x] || devModeRef.current) {
                    let drawX = Math.floor(x * mCellSize);
                    let drawY = Math.floor(y * mCellSize);
                    let drawW = Math.ceil(mCellSize) + 1;
                    let drawH = Math.ceil(mCellSize) + 1;

                    if (state.map[y][x] === 1) {
                        mCtx.fillStyle = baseWallColor;
                        mCtx.fillRect(drawX, drawY, drawW, drawH);
                    } else if (state.map[y][x] === 2) {
                        mCtx.fillStyle = resolveMapExitColor(theme);
                        mCtx.fillRect(drawX, drawY, drawW, drawH);
                    } else {
                        mCtx.fillStyle = baseFloorColor;
                        mCtx.fillRect(drawX, drawY, drawW, drawH);
                    }
                }
            }
        }

        if (mappedPercentRef.current && size > 0) mappedPercentRef.current.innerText = Math.floor((discoveredCount / (size * size)) * 100);

        // Exit outline drawn after all fills to prevent adjacent cells overwriting the border
        if (theme.exitOutlineColor) {
            mCtx.strokeStyle = theme.exitOutlineColor;
            mCtx.lineWidth = Math.max(1, mCellSize * 0.2);
            for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
                if ((state.discovered[y]?.[x] || devModeRef.current) && state.map[y][x] === 2) {
                    const dx = Math.floor(x * mCellSize), dy = Math.floor(y * mCellSize);
                    const dw = Math.ceil(mCellSize) + 1, dh = Math.ceil(mCellSize) + 1;
                    const lw = mCtx.lineWidth;
                    mCtx.strokeRect(dx + lw * 0.5, dy + lw * 0.5, dw - lw, dh - lw);
                }
            }
        }

        const floorHex = resolveMapFloorColor(theme).replace('#', '');
        const floorLuma = 0.299 * parseInt(floorHex.slice(0,2), 16) + 0.587 * parseInt(floorHex.slice(2,4), 16) + 0.114 * parseInt(floorHex.slice(4,6), 16);
        const devOutlineColor = floorLuma > 128 ? 'rgba(0,0,0,0.75)' : 'rgba(255,255,255,0.75)';

        // Player drawn first so entities render on top when overlapping
        const ppx = (state.player.renderX + 0.5) * mCellSize;
        const ppy = (state.player.renderY + 0.5) * mCellSize;
        const pr2 = mCellSize * 0.4;
        mCtx.save();
        mCtx.translate(ppx, ppy);
        mCtx.rotate((state.player.renderAngle * Math.PI) / 180);
        mCtx.fillStyle = theme.player;
        mCtx.beginPath(); mCtx.arc(0, 0, pr2, 0, Math.PI * 2); mCtx.fill();
        mCtx.fillStyle = '#ffffff';
        mCtx.beginPath(); mCtx.moveTo(0, -pr2 * 1.2); mCtx.lineTo(-pr2 * 0.4, -pr2 * 0.2); mCtx.lineTo(pr2 * 0.4, -pr2 * 0.2); mCtx.fill();
        mCtx.restore();

        state.entities.forEach(ent => {
            const ex = (ent.renderX + 0.5) * mCellSize, ey = (ent.renderY + 0.5) * mCellSize;
            const er = mCellSize * 0.4;
            if (devModeRef.current) {
                mCtx.strokeStyle = devOutlineColor;
                mCtx.lineWidth = 1;
                mCtx.beginPath();
                mCtx.arc(ex, ey, er, 0, Math.PI * 2);
                mCtx.stroke();
            }
            const sightR = themeRef.current?.sightRadius ?? Infinity;
            const inLOS = checkLineOfSight(ent.x, ent.y, state.player.x, state.player.y, state.map, sightR);
            if (inLOS) {
                const t = ent.template;
                const entState = ent.state === 'chasing' ? 'chasing' : 'wandering';
                mCtx.save();
                mCtx.translate(ex, ey);
                mCtx.rotate(((ent.renderAngle ?? 0) * Math.PI) / 180);
                mCtx.fillStyle = getEntityMapColor(t, entState);
                mCtx.beginPath(); mCtx.arc(0, 0, er, 0, Math.PI * 2); mCtx.fill();
                mCtx.fillStyle = getEntityMapArrowColor(t, entState);
                mCtx.beginPath(); mCtx.moveTo(0, -er * 1.2); mCtx.lineTo(-er * 0.4, -er * 0.2); mCtx.lineTo(er * 0.4, -er * 0.2); mCtx.fill();
                mCtx.restore();
            }
        });

    };

    const handleMenuClick = (modalName) => { setActiveModal(modalName); setMenuOpen(false); };
    const closeModal = () => { setActiveModal(null); setMenuOpen(false); setClearConfirm(false); };

    const updateTheme = (thm, t, updates) => {
        const newTheme = { ...t, ...updates };
        themeRef.current = newTheme;
        if (updates.timeOfDay !== undefined) engine.current.timeOfDay = updates.timeOfDay;
        setGameData(prev => ({ ...prev, themes: prev.themes.map(x => x.id === thm.id ? { ...x, theme: newTheme } : x) }));
        return newTheme;
    };

    const updateThemeEntry = (thm, updates) => {
        setGameData(prev => ({ ...prev, themes: prev.themes.map(x => x.id === thm.id ? { ...x, ...updates } : x) }));
    };

    const updateEntity = (ent, updates) => {
        setGameData(prev => ({ ...prev, entities: prev.entities.map(x => x.id === ent.id ? { ...x, ...updates } : x) }));
    };

    const updateMapGen = (lvl, mg, updates) => {
        updateTheme(lvl, lvl.theme, { mapGen: { ...mg, ...updates } });
    };

    const applyMapGen = (lvl, mg, updates) => {
        const newMg = { ...mg, ...updates };
        const freshLvl = { ...lvl, theme: { ...lvl.theme, mapGen: newMg }, gridSize: mapGenPreviewSize };
        updateTheme(lvl, lvl.theme, { mapGen: newMg });
        mapGenPreviewThemeRef.current = editingThemeId;
        syncPreview(freshLvl);
    };

    return (
        <div className="min-h-screen bg-slate-100 dark:bg-slate-950 flex flex-col font-sans text-slate-900 dark:text-slate-100 select-none">
            <header className="fixed top-0 left-0 right-0 h-14 bg-white dark:bg-slate-900 border-b border-slate-200 dark:border-slate-800 z-50 flex items-center px-4">
                <div className="flex items-center gap-3">
                    {gameState === 'menu' && (
                        <button onClick={() => setMenuOpen(!menuOpen)} className={`p-2 -ml-2 rounded-lg transition-colors ${menuOpen ? 'bg-slate-100 dark:bg-slate-800' : 'hover:bg-slate-100 dark:hover:bg-slate-800'} text-slate-600 dark:text-slate-400`}>
                            <MenuIcon />
                        </button>
                    )}
                    <div className="flex items-center gap-2">
                        <span className="text-emerald-500"><LogoIcon /></span>
                        <h1 className="font-bold tracking-widest uppercase text-lg text-slate-900 dark:text-white">M A Z E</h1>
                    </div>
                </div>
                <div className="flex-1" />
                <div className="flex items-center gap-4">
                </div>
            </header>

            {menuOpen && gameState === 'menu' && (
                <div className="fixed top-14 left-4 w-64 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 shadow-xl rounded-xl z-50 overflow-hidden py-2">
                    <button onClick={() => handleMenuClick('appearance')} className="w-full flex items-center gap-3 px-4 py-3 hover:bg-slate-50 dark:hover:bg-slate-700 text-left text-slate-700 dark:text-slate-200">
                        <SettingsIcon /> <span className="font-medium text-sm">Appearance</span>
                    </button>
                    <button onClick={() => handleMenuClick('level_designer')} className="w-full flex items-center gap-3 px-4 py-3 hover:bg-slate-50 dark:hover:bg-slate-700 text-left text-slate-700 dark:text-slate-200">
                        <BrushIcon /> <span className="font-medium text-sm">Theme Designer</span>
                    </button>
                    <button onClick={() => handleMenuClick('data')} className="w-full flex items-center gap-3 px-4 py-3 hover:bg-slate-50 dark:hover:bg-slate-700 text-left text-slate-700 dark:text-slate-200">
                        <DatabaseIcon /> <span className="font-medium text-sm">Data</span>
                    </button>
                    <button onClick={() => handleMenuClick('about')} className="w-full flex items-center gap-3 px-4 py-3 hover:bg-slate-50 dark:hover:bg-slate-700 text-left text-slate-700 dark:text-slate-200">
                        <InfoIcon /> <span className="font-medium text-sm">About</span>
                    </button>
                </div>
            )}

            <main className="flex-1 pt-20 pb-6 px-4 md:pt-24 md:px-6 flex flex-col md:flex-row items-start justify-center gap-6 max-w-6xl mx-auto w-full">
                <div className="relative md:flex-1 flex flex-col gap-4 w-full max-w-2xl shrink-0">
                    <div
                        ref={gameAreaRef}
                        className={`w-full ${isShortViewport ? 'max-h-[50vh]' : ''} ${isMobile ? 'aspect-[3/4]' : 'aspect-square'} bg-slate-900 rounded-2xl shadow-lg overflow-hidden relative`}
                        style={{ cursor: gameState === 'playing' ? 'none' : '', touchAction: 'none' }}
                    >
                        <canvas ref={canvasRef} width={800} height={800} className={`absolute inset-0 w-full h-full object-contain block ${viewMode === '3d' ? 'invisible' : ''}`} />
                        <canvas ref={webglCanvasRef} width={800} height={800} className={`absolute inset-0 w-full h-full ${viewMode === 'map' ? 'invisible' : ''}`} />
                        <canvas ref={proximityCanvasRef} className="absolute inset-0 w-full h-full pointer-events-none" style={{ zIndex: 5 }} />
                        <canvas ref={overlayCanvasRef} className="absolute inset-0 w-full h-full pointer-events-none" style={{ zIndex: 6 }} onMouseMove={handleOverlayMouseMove} onClick={handleOverlayClick} onMouseLeave={handleOverlayMouseLeave} />

                        {gameState !== 'menu' && showTouchControls && (
                            <div className="absolute top-3 right-3 flex flex-col bg-slate-900/40 backdrop-blur-md rounded-full shadow-lg overflow-hidden" style={{ zIndex: 10 }}>
                                <button
                                    onClick={() => {
                                        if (gameStateRef.current === 'playing') {
                                            setGameState('paused'); gameStateRef.current = 'paused';
                                            showOverlay('pause');
                                        } else if (gameStateRef.current === 'paused') {
                                            clearOverlay();
                                            setGameState('playing'); gameStateRef.current = 'playing'; lastTimeRef.current = 0;
                                        }
                                    }}
                                    className="w-9 h-9 flex items-center justify-center text-white active:bg-white/20"
                                    aria-label={gameState === 'playing' ? 'Pause' : 'Play'}
                                >
                                    <span style={{ display: 'inline-flex', transform: 'scale(0.6)' }}>
                                        {gameState === 'playing'
                                            ? <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="5" width="4" height="14" /><rect x="14" y="5" width="4" height="14" /></svg>
                                            : <PlayIcon />}
                                    </span>
                                </button>
                                <div className="h-px bg-white/15 mx-2" />
                                <button
                                    onClick={() => {
                                        if (gameStateRef.current === 'menu' || activeModalRef.current === 'level_designer') return;
                                        const newView = viewModeRef.current === 'map' ? '3d' : 'map';
                                        setViewMode(newView); viewModeRef.current = newView;
                                    }}
                                    className="w-9 h-9 flex items-center justify-center text-white active:bg-white/20"
                                    aria-label={viewMode === 'map' ? 'Switch to 3D' : 'Switch to 2D'}
                                >
                                    <span style={{ display: 'inline-flex', transform: 'scale(0.875)' }}><LayersIcon /></span>
                                </button>
                                <div className="h-px bg-white/15 mx-2" />
                                <button
                                    onClick={() => {
                                        if (gameStateRef.current === 'playing') {
                                            setGameState('paused'); gameStateRef.current = 'paused';
                                        }
                                        showOverlay('quit');
                                    }}
                                    className="w-9 h-9 flex items-center justify-center text-white active:bg-white/20"
                                    aria-label="Quit"
                                >
                                    <span style={{ display: 'inline-flex', transform: 'scale(0.78)' }}><QuitIcon /></span>
                                </button>
                            </div>
                        )}

                    </div>

                    {gameState === 'menu' && (
                        <div className="absolute inset-x-0 top-0 min-h-full bg-slate-900/90 backdrop-blur-md rounded-2xl flex flex-col items-center overflow-visible z-20 p-6" style={{ justifyContent: 'safe center' }}>
                            {!isShortViewport && <div className="text-emerald-500 mb-2"><LogoIcon /></div>}
                            <h2 className={`font-black text-white tracking-widest drop-shadow-lg ${isShortViewport ? 'text-2xl mb-2' : 'text-3xl mb-4'}`}>M A Z E</h2>

                            <div className={`bg-slate-800 rounded-2xl w-full max-w-sm shadow-2xl ${isShortViewport ? 'p-4 space-y-2' : 'p-5 space-y-3'}`}>
                                <div>
                                    <label className="text-xs font-bold text-slate-400 mb-2 block uppercase tracking-wider">Select Theme</label>
                                    <select
                                        value={startThemeId}
                                        onChange={e => setStartThemeId(e.target.value)}
                                        className="w-full border border-slate-700 rounded-lg p-2.5 bg-slate-700 text-white focus:outline-none focus:ring-2 focus:ring-emerald-500"
                                    >
                                        {(() => {
                                            const cats = [];
                                            const seen = new Set();
                                            gameData.themes.forEach(t => { const c = t.category || 'Other'; if (!seen.has(c)) { seen.add(c); cats.push(c); } });
                                            return cats.map(cat => {
                                                const group = gameData.themes.filter(t => (t.category || 'Other') === cat);
                                                return <optgroup key={cat} label={cat}>
                                                    {group.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
                                                </optgroup>;
                                            });
                                        })()}
                                    </select>
                                </div>
                                <p className="text-xs text-slate-400 italic leading-relaxed text-center min-h-[2rem]">
                                    {gameData.themes.find(t => t.id === startThemeId)?.tagline || `Find the exit. Don't get caught.`}
                                </p>
                                <button onClick={() => initGame(1)} className="w-full py-4 bg-white text-slate-900 font-black tracking-wider rounded-xl hover:bg-slate-200 transition-colors shadow-lg">START GAME</button>
                            </div>
                        </div>
                    )}

                    {gameState !== 'menu' && !showTouchControls && (() => {
                        const isClassic = (gameData.themes.find(t => t.id === startThemeId)?.theme?.moveMode ?? 'smooth') === 'classic';
                        return (
                        <div className="text-center text-slate-500 font-mono text-xs px-4 space-x-3">
                            <span>W A S D / ↑↓←→ &nbsp;Move</span>
                            {!isClassic && <><span>&middot;</span><span>Shift &nbsp;Sprint</span></>}
                            <span>&middot;</span>
                            <span>Space &nbsp;Pause</span>
                            <span>&middot;</span>
                            <span>V &nbsp;2D/3D</span>
                            <span>&middot;</span>
                            <span>ESC &nbsp;Quit</span>
                        </div>
                        );
                    })()}

                    {devMode && (
                        <div className="flex flex-col items-center gap-3 mt-2 px-4 w-full">
                            <div className="text-center font-mono text-xs" style={{ color: '#4ade80' }}>
                                [N] Next level &nbsp;&middot;&nbsp; [G] Grid &nbsp;&middot;&nbsp; [R] Radar &nbsp;&middot;&nbsp; [+/-] Entities &nbsp;&middot;&nbsp; [H] Walls &nbsp;&middot;&nbsp; [I] Invincible
                            </div>
                            
                            <div className="flex flex-col gap-3 bg-slate-900/90 p-4 w-full max-w-3xl rounded-lg border border-slate-700/50 shadow-lg text-xs font-mono">
                                <div className="flex justify-between items-start">
                                    <div className="flex flex-col text-left text-slate-300 gap-1 font-bold">
                                        <div className="flex gap-4">
                                            <span className="text-white">DEV MODE</span>
                                            <span className="text-amber-400">v{APP_VERSION}</span>
                                            <span>ENGINE: {localStorage.getItem('maze_webgl_fallback') === 'true' ? 'WebGL2' : 'WebGPU'}</span>
                                            <span ref={fpsRef} className="text-emerald-400">0 FPS</span>
                                            <span className="text-emerald-300">OBJ: <span ref={entitiesRef}>0</span></span>
                                        </div>
                                    </div>
                                    
                                </div>
                                
                                <canvas ref={fpsCanvasRef} width={800} height={40} className="w-full h-[40px] rounded border border-slate-700/50 bg-slate-950" style={{ imageRendering: 'pixelated' }} />
                                
                                <div className="flex justify-between items-end mt-1">
                                    <div className="flex flex-col gap-2">
                                        <div className="flex flex-wrap gap-4 text-slate-300">
                                            {['map', 'lines', 'floor', 'entities', 'ai', 'exits', 'lights'].map(k => (
                                                <label key={k} className="flex items-center gap-1 cursor-pointer hover:text-white">
                                                    <input type="checkbox" checked={debugFlags[k]} onChange={() => {
                                                        setDebugFlags(p => ({ ...p, [k]: !p[k] }));
                                                    }} className="accent-slate-500" />
                                                    {k}
                                                </label>
                                            ))}
                                        </div>
                                        <div className="flex flex-wrap gap-4 text-slate-300">
                                            {['radar', 'raycast'].map(k => (
                                                <label key={k} className="flex items-center gap-1 cursor-pointer hover:text-white">
                                                    <input type="checkbox" checked={k === 'webgl' ? localStorage.getItem('maze_webgl_fallback') === 'true' : debugFlags[k]} onChange={() => {
                                                        if (k === 'webgl') {
                                                            const cur = localStorage.getItem('maze_webgl_fallback') === 'true';
                                                            localStorage.setItem('maze_webgl_fallback', !cur);
                                                            window.location.reload();
                                                        } else {
                                                            setDebugFlags(p => ({ ...p, [k]: !p[k] }));
                                                        }
                                                    }} className="accent-slate-500" />
                                                    {k === 'webgl' ? 'API: WebGPU' : k}
                                                </label>
                                            ))}
                                        </div>
                                    </div>
                                    <button 
                                        onClick={() => {
                                            engine.current.entities = [];
                                            if (entityCountRef.current) entityCountRef.current.innerText = '0';
                                            if (entitiesRef.current) entitiesRef.current.innerText = '0';
                                        }} 
                                        className="bg-red-900/50 hover:bg-red-800 text-red-200 border border-red-700/50 px-3 py-1.5 rounded uppercase font-bold tracking-wider transition-colors"
                                    >
                                        Kill All Entities
                                    </button>
                                </div>
                            </div>
                        </div>
                    )}
                </div>

                {gameState === 'menu' ? (
                    <div ref={sidebarRef} className="hidden md:flex flex-col w-64 gap-4 shrink-0 overflow-hidden" style={{ maxHeight: 'calc(100vh - 120px)' }}>
                        <div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-2xl p-5 shadow-sm flex flex-col gap-3">
                            <h3 className="text-xs font-bold text-slate-400 uppercase tracking-wider">Controls</h3>
                            <div className="space-y-2 text-xs text-slate-600 dark:text-slate-300">
                                {[
                                    ['W A S D / ↑ ↓ ← →', 'Move'],
                                    ['Shift', 'Sprint'],
                                    ['Space', 'Pause'],
                                    ['V', '2D / 3D toggle'],
                                    ['ESC', 'Quit'],
                                ].map(([key, action]) => (
                                    <div key={key} className="flex justify-between gap-2">
                                        <span className="font-mono bg-slate-100 dark:bg-slate-900 px-1.5 py-0.5 rounded text-slate-500 dark:text-slate-400 text-xs">{key}</span>
                                        <span className="text-slate-500 dark:text-slate-400">{action}</span>
                                    </div>
                                ))}
                            </div>
                        </div>
                        {Object.keys(scores).length > 0 && (
                            <div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-2xl p-5 shadow-sm flex flex-col gap-3 flex-1 min-h-0">
                                <h3 className="text-xs font-bold text-slate-400 uppercase tracking-wider shrink-0">High Scores</h3>
                                <div className="space-y-2 overflow-y-auto flex-1 pr-4">
                                    {gameData.themes
                                        .filter(t => scores[t.id])
                                        .sort((a, b) => (scores[b.id]?.bestLevel || 0) - (scores[a.id]?.bestLevel || 0))
                                        .map(t => (
                                            <div key={t.id} className="flex justify-between items-center text-xs">
                                                <span className="text-slate-600 truncate">{t.name}</span>
                                                <span className="font-mono font-bold text-emerald-600 shrink-0 ml-2">Lvl {scores[t.id].bestLevel}</span>
                                            </div>
                                        ))
                                    }
                                </div>
                            </div>
                        )}
                    </div>
                ) : (
                    <HUD
                        minimapRef={minimapRef}
                        mappedPercentRef={mappedPercentRef}
                        staminaBarFillRef={staminaBarFillRef}
                        entityCountRef={entityCountRef}
                        gameState={gameState}
                        level={level}
                        lives={lives}
                        invisible={devInvis}
                        currentGridSize={currentGridSize}
                        engine={engine}
                        viewMode={viewMode}
                        canSprint={(gameData.themes.find(t => t.id === startThemeId)?.theme?.moveMode ?? 'smooth') !== 'classic'}
                        themeName={gameData.themes.find(t => t.id === startThemeId)?.name ?? ''}
                        isMobile={isMobile}
                    />
                )}
            </main>

            {activeModal && activeModal !== 'data' && (
                <div className="fixed inset-0 z-50 flex items-center justify-center p-4 md:p-6 bg-slate-900/40 backdrop-blur-md">

                    {activeModal !== 'level_designer' && activeModal !== 'appearance' && activeModal !== 'about' && (
                        <div className="bg-white dark:bg-slate-800 w-full max-w-md rounded-2xl shadow-2xl overflow-hidden flex flex-col max-h-[80vh]">
                            <div className="flex items-center justify-between p-4 border-b border-slate-100 dark:border-slate-700">
                                <h2 className="font-bold text-lg capitalize text-slate-900 dark:text-white">{activeModal}</h2>
                                <button onClick={closeModal} className="p-1 rounded-md hover:bg-slate-100 dark:hover:bg-slate-700 text-slate-500 dark:text-slate-400"><XIcon /></button>
                            </div>
                            <div className="p-6 overflow-y-auto">
                                {/* other default modals */}
                            </div>
                        </div>
                    )}

                    {activeModal === 'appearance' && (
                        <div className="bg-white dark:bg-slate-800 w-full max-w-md rounded-2xl shadow-2xl p-6 relative">
                            <button onClick={closeModal} className="absolute top-4 right-4 p-2 bg-slate-100 dark:bg-slate-700 rounded-full hover:bg-slate-200 dark:hover:bg-slate-600 text-slate-600 dark:text-slate-300">
                                <XIcon />
                            </button>
                            <h2 className="text-xl font-black text-slate-900 dark:text-white uppercase tracking-widest mb-6 flex items-center gap-2"><SettingsIcon /> Appearance</h2>
                            
                            <div className="space-y-4">
                                <label className="text-xs font-bold text-slate-400 uppercase tracking-wider block mb-2">Theme</label>
                                <div className="flex gap-2">
                                    {['light', 'dark', 'system'].map(t => (
                                        <button key={t} onClick={() => setAppSettings(p => ({...p, theme: t}))} className={`flex-1 py-3 rounded-xl text-sm font-bold capitalize transition-colors ${appSettings.theme === t ? 'bg-emerald-500 text-white shadow-md' : 'bg-slate-100 dark:bg-slate-900 text-slate-500 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700'}`}>{t}</button>
                                    ))}
                                </div>
                                <p className="text-xs text-slate-400 dark:text-slate-500 italic mt-4 text-center">Note: UI theme does not affect 3D game rendering.</p>
                            </div>
                        </div>
                    )}

                    {activeModal === 'about' && (
                        <div className="bg-white dark:bg-slate-800 w-full max-w-md rounded-2xl shadow-2xl p-6 relative">
                            <button onClick={closeModal} className="absolute top-4 right-4 p-2 bg-slate-100 dark:bg-slate-700 rounded-full hover:bg-slate-200 dark:hover:bg-slate-600 text-slate-600 dark:text-slate-300">
                                <XIcon />
                            </button>
                            <div className="flex flex-col items-center justify-center py-6 text-center px-4">
                                <div className="text-emerald-500 scale-[4] mb-12">
                                    <LogoIcon />
                                </div>
                                <h3 className="text-2xl font-black text-slate-900 dark:text-white tracking-widest uppercase mb-2">M A Z E</h3>
                                <span className="text-slate-400 dark:text-slate-500 font-mono text-xs opacity-75 mb-6">v{APP_VERSION}</span>
                                <p className="text-sm text-slate-600 dark:text-slate-400 leading-relaxed max-w-sm">Get lost in a thrilling, endless 3D labyrinth! Explore procedurally generated worlds, discover hidden secrets, and see if you have what it takes to escape.</p>
                            </div>
                        </div>
                    )}

                    {activeModal === 'level_designer' && (
                        <div className="bg-white dark:bg-slate-800 w-full max-w-6xl h-full rounded-2xl shadow-2xl overflow-hidden flex flex-col md:flex-row relative">
                            <button onClick={closeModal} className="absolute top-4 right-4 p-2 bg-slate-900/50 rounded-full hover:bg-slate-900 text-white z-50">
                                <XIcon />
                            </button>

                            <div className="w-full md:w-80 bg-slate-50 dark:bg-slate-900 border-r border-slate-200 dark:border-slate-700 flex flex-col h-1/3 md:h-full shrink-0 z-40">
                                <div className="p-4 border-b border-slate-200 dark:border-slate-700 flex justify-between items-center bg-white dark:bg-slate-800">
                                    <div className="flex bg-slate-100 dark:bg-slate-800/80 p-1 rounded-lg">
                                        <button onClick={() => setDesignerTab('themes')} className={`px-3 py-1 text-sm font-bold rounded ${designerTab === 'themes' ? 'bg-white dark:bg-slate-800 shadow text-emerald-600' : 'text-slate-500 dark:text-slate-400 dark:text-slate-500 hover:text-slate-700 dark:text-slate-300 dark:text-slate-600'}`}>Themes</button>
                                        <button onClick={() => setDesignerTab('entities')} className={`px-3 py-1 text-sm font-bold rounded ${designerTab === 'entities' ? 'bg-white dark:bg-slate-800 shadow text-emerald-600' : 'text-slate-500 dark:text-slate-400 dark:text-slate-500 hover:text-slate-700 dark:text-slate-300 dark:text-slate-600'}`}>Entities</button>
                                    </div>
                                    <div className="flex gap-2">
                                        <button
                                            onClick={() => {
                                                if (designerTab === 'themes') {
                                                    const newThm = {
                                                        id: 'theme-' + Date.now(),
                                                        name: 'New Theme',
                                                        category: 'Other',
                                                        gridSize: 15, gridSizeIncrement: 2,
                                                        theme: {
                                                            style: 'filled',
                                                            wallZones: [{ height: 1.0, color: '#cccccc', textureMode: 'solid' }],
                                                            shadowDarkness: 0.5,
                                                            floor: '#aaaaaa', outerFloor: '#555555',
                                                            hasCeiling: false,
                                                            skyMode: 'solid', skyColor: '#111122',
                                                            ambientLightIntensity: 1,
                                                            wallOutline: true, wallOutlineStyle: 'edge',
                                                            exitColor: '#ff0000', exitText: 'EXIT', exitTextColor: '#ffffff', exitHeight: 1.0,
                                                            player: '#00ff00', entityDensity: 0.5, entityDensityScaling: 0.1, entityBaseSpeed: 0.1, entitySpeedScaling: 0.1,
                                                            activeEntities: [],
                                                            sightRadius: 15,
                                                            mapGen: { mode: 'columns', loopFactor: 0.3, columnDensity: 0.3, columnMaxW: 3, columnMaxH: 3 }
                                                        }
                                                    };
                                                    setGameData(prev => ({ ...prev, themes: [...prev.themes, newThm] }));
                                                    setEditingThemeId(newThm.id);
                                                } else {
                                                    const newEnt = {
                                                        id: 'ent-' + Date.now(),
                                                        name: 'New Entity',
                                                        renderType: 'builtin', type: 'smiley',
                                                        primaryColor: '#ffffff', chaseColor: '#ff0000', featureColor: '#000000',
                                                        faceScale: 1.0, faceScale2D: 1.0
                                                    };
                                                    setGameData(prev => ({ ...prev, entities: [...prev.entities, newEnt] }));
                                                    setEditingEntityId(newEnt.id);
                                                }
                                            }}
                                            className="p-1.5 bg-emerald-500 text-white rounded hover:bg-emerald-600" title="Add Item"
                                        >
                                            <PlusIcon />
                                        </button>
                                    </div>
                                </div>
                                {/* Search / sort / category filter strip */}
                                <div className="px-3 py-2 border-b border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 space-y-2 shrink-0">
                                    <div className="flex gap-2 items-center">
                                        <input
                                            type="text" value={listSearch}
                                            onChange={e => setListSearch(e.target.value)}
                                            placeholder="Search…"
                                            className="flex-1 min-w-0 text-xs border border-slate-200 dark:border-slate-700 rounded-lg px-2 py-1.5 bg-slate-50 dark:bg-slate-900 text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-emerald-400"
                                        />
                                        <button
                                            onClick={() => setListSort(s => s === null ? 'az' : s === 'az' ? 'za' : null)}
                                            className={`shrink-0 px-2 py-1.5 text-xs rounded-lg border font-bold transition-colors ${listSort ? 'border-emerald-500 text-emerald-600 bg-emerald-50' : 'border-slate-200 dark:border-slate-700 text-slate-400 dark:text-slate-500 hover:border-slate-300 dark:border-slate-600 hover:text-slate-600 dark:text-slate-400 dark:text-slate-500'}`}
                                            title={listSort === null ? 'Sort A→Z' : listSort === 'az' ? 'Sort Z→A' : 'Clear sort'}
                                        >{listSort === 'za' ? 'Z→A' : 'A→Z'}</button>
                                    </div>
                                    {designerTab === 'themes' && (() => {
                                        const cats = [];
                                        const seen = new Set();
                                        gameData.themes.forEach(t => { const c = t.category || 'Other'; if (!seen.has(c)) { seen.add(c); cats.push(c); } });
                                        if (cats.length <= 1) return null;
                                        return <div className="flex flex-wrap gap-1">
                                            {cats.map(cat => (
                                                <button key={cat}
                                                    onClick={() => setThemeFilterCat(c => c === cat ? null : cat)}
                                                    className={`px-2 py-0.5 text-xs rounded-full border font-medium transition-colors ${themeFilterCat === cat ? 'bg-slate-800 dark:bg-emerald-500 text-white border-slate-800 dark:border-emerald-500' : 'bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400 dark:text-slate-500 border-slate-200 dark:border-slate-700 hover:border-slate-400 dark:border-slate-500 hover:text-slate-700 dark:text-slate-300 dark:text-slate-600'}`}
                                                >{cat}</button>
                                            ))}
                                        </div>;
                                    })()}
                                </div>

                                <div className="flex-1 overflow-y-auto p-2 space-y-1">
                                    {designerTab === 'themes' && (() => {
                                        const renderThemeRow = (t) => {
                                            const isBuiltIn = DEFAULT_THEMES.some(dt => dt.id === t.id);
                                            const savedState = savedData.themes.find(st => st.id === t.id);
                                            const isDirty = !savedState || JSON.stringify(t) !== JSON.stringify(savedState);
                                            return (
                                                <div key={t.id} className={`flex items-center justify-between p-3 rounded-xl cursor-pointer transition-colors ${editingThemeId === t.id ? 'bg-slate-200 dark:bg-slate-700 font-bold' : 'hover:bg-slate-100 dark:bg-slate-800/80'}`} onClick={() => setEditingThemeId(t.id)}>
                                                    <div className="shrink-0 text-slate-500">
                                                        {isBuiltIn ? <LockIcon /> : <UserIcon />}
                                                    </div>
                                                    <span className="flex-1 ml-2 min-w-0">
                                                        <span className="block truncate text-sm">{t.name}{isDirty ? '*' : ''}</span>
                                                        {t.tagline && <span className="block truncate text-xs text-slate-400 dark:text-slate-500 italic font-normal">{t.tagline}</span>}
                                                    </span>
                                                    <div className="flex gap-1 opacity-50 hover:opacity-100 transition-opacity">
                                                        {isBuiltIn ? (
                                                            <>
                                                                <button onClick={(e) => {
                                                                    e.stopPropagation();
                                                                    const dup = JSON.parse(JSON.stringify(t));
                                                                    dup.id = 'theme-' + Date.now();
                                                                    dup.name += ' (Copy)';
                                                                    setGameData(prev => {
                                                                        const next = { ...prev, themes: [...prev.themes, dup] };
                                                                        return next;
                                                                    });
                                                                    savePreset(dup, 'theme');
                                                                }} className="p-1 hover:text-blue-500 opacity-100" title="Save as copy"><CopyIcon /></button>
                                                                <button onClick={(e) => {
                                                                    e.stopPropagation();
                                                                    const def = DEFAULT_THEMES.find(dt => dt.id === t.id);
                                                                    setGameData(prev => ({ ...prev, themes: prev.themes.map(th => th.id === t.id ? JSON.parse(JSON.stringify(def)) : th) }));
                                                                    if (editingThemeId === t.id) themeRef.current = def.theme;
                                                                }} className={`p-1 transition-colors ${isDirty ? 'hover:text-amber-500 opacity-100' : 'text-slate-400 dark:text-slate-500 cursor-default opacity-50'}`} title="Reset to default" disabled={!isDirty}>
                                                                    <RotateCcwIcon />
                                                                </button>
                                                            </>
                                                        ) : (
                                                            <>
                                                                <button 
                                                                    onClick={(e) => { e.stopPropagation(); savePreset(t, 'theme'); }} 
                                                                    className={`p-1 transition-colors ${isDirty ? 'text-emerald-500 hover:text-emerald-400 opacity-100' : 'text-slate-400 dark:text-slate-500 cursor-default opacity-50'}`} 
                                                                    title={isDirty ? "Save Changes" : "Up to date"}
                                                                    disabled={!isDirty}
                                                                >
                                                                    <SaveIcon />
                                                                </button>
                                                                <button onClick={(e) => {
                                                                    e.stopPropagation();
                                                                    if (!savedState) return;
                                                                    setGameData(prev => ({ ...prev, themes: prev.themes.map(th => th.id === t.id ? JSON.parse(JSON.stringify(savedState)) : th) }));
                                                                    if (editingThemeId === t.id) themeRef.current = savedState.theme;
                                                                }} className={`p-1 transition-colors ${isDirty ? 'hover:text-amber-500 opacity-100' : 'text-slate-400 dark:text-slate-500 cursor-default opacity-50'}`} title="Revert to saved" disabled={!isDirty}>
                                                                    <RotateCcwIcon />
                                                                </button>
                                                                <button onClick={(e) => {
                                                                    e.stopPropagation();
                                                                    const dup = JSON.parse(JSON.stringify(t));
                                                                    dup.id = 'theme-' + Date.now();
                                                                    dup.name += ' (Copy)';
                                                                    setGameData(prev => ({ ...prev, themes: [...prev.themes, dup] }));
                                                                    savePreset(dup, 'theme');
                                                                    setEditingThemeId(dup.id);
                                                                }} className="p-1 hover:text-blue-500 opacity-100" title="Duplicate"><CopyIcon /></button>
                                                                {gameData.themes.length > 1 && (
                                                                    <button onClick={(e) => {
                                                                        e.stopPropagation();
                                                                        setDeleteConfirmItem({ id: t.id, type: 'theme', name: t.name });
                                                                    }} className="p-1 hover:text-rose-500 opacity-100" title="Delete"><TrashIcon /></button>
                                                                )}
                                                            </>
                                                        )}
                                                    </div>
                                                </div>
                                            );
                                        };
                                        let themes = gameData.themes;
                                        if (themeFilterCat) themes = themes.filter(t => (t.category || 'Other') === themeFilterCat);
                                        if (listSearch) themes = themes.filter(t => t.name.toLowerCase().includes(listSearch.toLowerCase()));
                                        if (listSort === 'az') themes = [...themes].sort((a, b) => a.name.localeCompare(b.name));
                                        if (listSort === 'za') themes = [...themes].sort((a, b) => b.name.localeCompare(a.name));
                                        if (themes.length === 0) return <div className="px-4 py-6 text-xs text-slate-400 dark:text-slate-500 text-center">No themes match</div>;
                                        return themes.map(renderThemeRow);
                                    })()}
                                    {designerTab === 'entities' && (() => {
                                        let ents = gameData.entities;
                                        if (listSearch) ents = ents.filter(e => e.name.toLowerCase().includes(listSearch.toLowerCase()));
                                        if (listSort === 'az') ents = [...ents].sort((a, b) => a.name.localeCompare(b.name));
                                        if (listSort === 'za') ents = [...ents].sort((a, b) => b.name.localeCompare(a.name));
                                        return ents;
                                    })().map(e => {
                                        const isBuiltIn = DEFAULT_ENTITIES.some(de => de.id === e.id);
                                        const savedState = savedData.entities.find(se => se.id === e.id);
                                        const isDirty = !savedState || JSON.stringify(e) !== JSON.stringify(savedState);
                                        return (
                                            <div key={e.id} className={`flex items-center justify-between p-3 rounded-xl cursor-pointer transition-colors ${editingEntityId === e.id ? 'bg-slate-200 dark:bg-slate-700 font-bold' : 'hover:bg-slate-100 dark:bg-slate-800/80'}`} onClick={() => setEditingEntityId(e.id)}>
                                                <div className="shrink-0 text-slate-500">
                                                    {isBuiltIn ? <LockIcon /> : <UserIcon />}
                                                </div>
                                                <span className="truncate flex-1 text-sm ml-2">{e.name}{isDirty ? '*' : ''}</span>
                                                <div className="flex gap-1 opacity-50 hover:opacity-100 transition-opacity">
                                                    {isBuiltIn ? (
                                                        <>
                                                            <button onClick={(ev) => {
                                                                ev.stopPropagation();
                                                                const dup = JSON.parse(JSON.stringify(e));
                                                                dup.id = 'ent-' + Date.now();
                                                                dup.name += ' (Copy)';
                                                                setGameData(prev => {
                                                                    const next = { ...prev, entities: [...prev.entities, dup] };
                                                                    return next;
                                                                });
                                                                savePreset(dup, 'entity');
                                                            }} className="p-1 hover:text-blue-500 opacity-100" title="Save as copy"><CopyIcon /></button>
                                                            <button onClick={(ev) => {
                                                                ev.stopPropagation();
                                                                const def = DEFAULT_ENTITIES.find(de => de.id === e.id);
                                                                setGameData(prev => ({ ...prev, entities: prev.entities.map(en => en.id === e.id ? JSON.parse(JSON.stringify(def)) : en) }));
                                                            }} className={`p-1 transition-colors ${isDirty ? 'hover:text-amber-500 opacity-100' : 'text-slate-400 dark:text-slate-500 cursor-default opacity-50'}`} title="Reset to default" disabled={!isDirty}>
                                                                <RotateCcwIcon />
                                                            </button>
                                                        </>
                                                    ) : (
                                                        <>
                                                            <button 
                                                                onClick={(ev) => { ev.stopPropagation(); savePreset(e, 'entity'); }} 
                                                                className={`p-1 transition-colors ${isDirty ? 'text-emerald-500 hover:text-emerald-400 opacity-100' : 'text-slate-400 dark:text-slate-500 cursor-default opacity-50'}`} 
                                                                title={isDirty ? "Save Changes" : "Up to date"}
                                                                disabled={!isDirty}
                                                            >
                                                                <SaveIcon />
                                                            </button>
                                                            <button onClick={(ev) => {
                                                                ev.stopPropagation();
                                                                if (!savedState) return;
                                                                setGameData(prev => ({ ...prev, entities: prev.entities.map(en => en.id === e.id ? JSON.parse(JSON.stringify(savedState)) : en) }));
                                                            }} className={`p-1 transition-colors ${isDirty ? 'hover:text-amber-500 opacity-100' : 'text-slate-400 dark:text-slate-500 cursor-default opacity-50'}`} title="Revert to saved" disabled={!isDirty}>
                                                                <RotateCcwIcon />
                                                            </button>
                                                            <button onClick={(ev) => {
                                                                ev.stopPropagation();
                                                                const dup = JSON.parse(JSON.stringify(e));
                                                                dup.id = 'ent-' + Date.now();
                                                                dup.name += ' (Copy)';
                                                                setGameData(prev => ({ ...prev, entities: [...prev.entities, dup] }));
                                                                savePreset(dup, 'entity');
                                                                setEditingEntityId(dup.id);
                                                            }} className="p-1 hover:text-blue-500 opacity-100" title="Duplicate"><CopyIcon /></button>
                                                            {gameData.entities.length > 1 && (
                                                                <button onClick={(ev) => {
                                                                    ev.stopPropagation();
                                                                    setDeleteConfirmItem({ id: e.id, type: 'entity', name: e.name });
                                                                }} className="p-1 hover:text-rose-500 opacity-100" title="Delete"><TrashIcon /></button>
                                                            )}
                                                        </>
                                                    )}
                                                </div>
                                            </div>
                                        );
                                    })}
                                </div>
                            </div>

                            <div className="flex-1 flex flex-col bg-slate-100 dark:bg-slate-800/80 overflow-hidden relative">
                                {designerTab === 'themes' && themeSubTab === 'map' && (
                                    <div className="w-full h-64 md:h-96 bg-slate-900 relative shrink-0 border-b border-slate-300 dark:border-slate-600 shadow-sm z-30 flex justify-center items-center">
                                        <canvas ref={mapGenPreviewCanvasRef} width={400} height={400} className="h-full aspect-square" />
                                        <button
                                            onClick={() => { const lvl = gameData.themes.find(t => t.id === editingThemeId); if (lvl) regenerateMapGenPreview(lvl); }}
                                            className="absolute bottom-3 right-3 bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-bold px-3 py-1.5 rounded-lg shadow"
                                        >
                                            ↺ Regenerate
                                        </button>
                                        <div className="absolute bottom-3 left-3 bg-slate-900/80 backdrop-blur-md px-3 py-1 rounded text-emerald-400 text-xs font-mono font-bold flex items-center gap-2 pointer-events-none">
                                            <span className="w-2 h-2 rounded-full bg-emerald-500"></span> 2D PREVIEW
                                        </div>
                                    </div>
                                )}

                                {designerTab === 'themes' && themeSubTab !== 'map' && (
                                    <div className="w-full h-64 md:h-96 bg-slate-900 relative shrink-0 border-b border-slate-300 dark:border-slate-600 shadow-sm z-30 flex justify-center">
                                        <canvas
                                            ref={webglPreviewCanvasRef}
                                            width={800} height={800}
                                            className="h-full aspect-square cursor-grab active:cursor-grabbing shrink-0"
                                            onMouseDown={handlePreviewMouseDown}
                                            onMouseMove={handlePreviewMouseMove}
                                            onMouseUp={handlePreviewMouseUp}
                                            onMouseLeave={handlePreviewMouseUp}
                                            onTouchStart={(e) => { isDraggingRef.current = true; lastMouseXRef.current = e.touches[0].clientX; }}
                                            onTouchMove={(e) => {
                                                if (!isDraggingRef.current) return;
                                                let d = e.touches[0].clientX - lastMouseXRef.current;
                                                engine.current.player.renderAngle -= d * 0.5;
                                                lastMouseXRef.current = e.touches[0].clientX;
                                            }}
                                            onTouchEnd={() => { isDraggingRef.current = false; }}
                                        />
                                        {(gameData.themes.find(t => t.id === editingThemeId)?.theme?.pixelScale ?? 1) < 1 && (
                                            <div className="flex-1 border-l border-slate-700 bg-slate-950 flex flex-col items-center justify-center gap-2">
                                                <canvas ref={pixelPreviewCanvasRef} style={{ imageRendering: 'pixelated', display: 'block' }} />
                                                <div className="text-xs text-slate-600 dark:text-slate-400 dark:text-slate-500 font-mono">native px</div>
                                            </div>
                                        )}
                                        <div className="absolute top-3 right-3 bg-slate-900/80 backdrop-blur-md px-3 py-1 rounded text-slate-300 dark:text-slate-600 text-xs font-mono font-bold flex items-center gap-2 pointer-events-none">
                                            <ClockIcon /> <span ref={previewClockRef}>12:00</span>
                                        </div>
                                        <div className="absolute bottom-3 left-3 bg-slate-900/80 backdrop-blur-md px-3 py-1 rounded text-emerald-400 text-xs font-mono font-bold flex items-center gap-2 pointer-events-none">
                                            <span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span> LIVE PREVIEW
                                        </div>
                                        <div className="absolute bottom-3 right-3 bg-slate-900/80 backdrop-blur-md px-3 py-1 rounded text-slate-300 dark:text-slate-600 text-xs font-mono flex items-center gap-2 pointer-events-none hidden sm:flex">
                                            [W/A/S/D] move
                                        </div>
                                    </div>
                                )}

                                <div className="flex-1 overflow-y-auto p-6 md:p-10 bg-white dark:bg-slate-800">
                                    {designerTab === 'themes' && gameData.themes.find(t => t.id === editingThemeId) && (
                                        (() => {
                                            const lvl = gameData.themes.find(t => t.id === editingThemeId);
                                            const t = lvl.theme || {};
                                            const wallZones = t.wallZones || [{ height: 1.0, color: '#334155' }];

                                            const surfaceOn = (s) => {
                                                const f = t[`${s}Outline`];
                                                if (f !== undefined) return !!f;
                                                return t.style === 'outlined';
                                            };
                                            const subKeys = (s) => {
                                                const leg = s === 'exterior' ? 'outerFloor' : null;
                                                return {
                                                    outColor: `${s}OutlineColor`,
                                                    legacyOutColor: leg ? `${leg}OutlineColor` : null,
                                                    outWidth: `${s}OutlineWidth`,
                                                    outStyle: `${s}OutlineStyle`,
                                                    sx: `${s}SubdivX`,
                                                    legacySx: leg ? `${leg}SubdivX` : null,
                                                    sy: `${s}SubdivY`,
                                                    legacySy: leg ? `${leg}SubdivY` : null,
                                                };
                                            };
                                            const setZone = (i, patch) => updateTheme(lvl, t, { wallZones: wallZones.map((z, idx) => idx === i ? { ...z, ...patch } : z) });
                                            const addZone = () => updateTheme(lvl, t, { wallZones: [...wallZones, { height: 0.5, color: '#ffffff', outlineColor: '#000000', subdivX: 1, subdivY: 1 }] });
                                            const removeZone = (i) => updateTheme(lvl, t, { wallZones: wallZones.filter((_, idx) => idx !== i) });
                                            const perimWallZones = t.perimeterWallZones || [{ height: 1.0, color: '#334155' }];
                                            const setPerimZone = (i, patch) => updateTheme(lvl, t, { perimeterWallZones: perimWallZones.map((z, idx) => idx === i ? { ...z, ...patch } : z) });

                                            const texCtrls = (data, setter, noFile, solidColorStr, solidColorSetter) => {
                                                const tmode = data.textureMode ?? 'solid';
                                                const modes = [['solid','Solid'],['brick','Brick'],['tiles','Tiles'],['hex','Hex'],['wood','Wood'],['concrete','Concrete'],['checker','Checker'],['metal','Metal'],['stucco','Stucco'],['circuit','Circuit'],...(noFile?[]:[['file','File']])];
                                                return (<>
                                                    <div className="flex gap-1 flex-wrap">
                                                        {modes.map(([m,lbl]) => (<button key={m} onClick={() => setter({textureMode:m})} className={`px-2 py-1 rounded text-xs font-bold border transition-colors ${tmode===m?'bg-emerald-500 text-white border-emerald-500':'bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400 dark:text-slate-500 border-slate-200 dark:border-slate-700 hover:border-emerald-400'}`}>{lbl}</button>))}
                                                    </div>
                                                    {tmode==='solid' && (<div className="grid grid-cols-3 gap-3">
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Colour</label><input type="color" value={solidColorStr??'#334155'} onChange={e=>solidColorSetter&&solidColorSetter(e.target.value)} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                    </div>)}
                                                    {tmode==='brick' && (<div className="grid grid-cols-3 gap-3">
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Brick</label><input type="color" value={data.brickColor??'#c0522a'} onChange={e=>setter({brickColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Mortar</label><input type="color" value={data.mortarColor??'#8a7a70'} onChange={e=>setter({mortarColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Scale ({(data.textureScale??2).toFixed(1)})</label><input type="range" min="1" max="8" step="0.5" value={data.textureScale??2} onChange={e=>setter({textureScale:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Cols ({data.brickCols??4})</label><input type="range" min="2" max="8" step="1" value={data.brickCols??4} onChange={e=>setter({brickCols:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div className="col-span-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Aspect ({(data.brickAspect??2.5).toFixed(1)})</label><input type="range" min="1" max="5" step="0.5" value={data.brickAspect??2.5} onChange={e=>setter({brickAspect:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                    </div>)}
                                                    {tmode==='tiles' && (<div className="grid grid-cols-3 gap-3">
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Tile</label><input type="color" value={data.tileColor??'#d4d4d4'} onChange={e=>setter({tileColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Grout</label><input type="color" value={data.groutColor??'#8a8a8a'} onChange={e=>setter({groutColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Scale ({(data.textureScale??2).toFixed(1)})</label><input type="range" min="1" max="8" step="0.5" value={data.textureScale??2} onChange={e=>setter({textureScale:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Cols ({data.tileCols??4})</label><input type="range" min="2" max="8" step="1" value={data.tileCols??4} onChange={e=>setter({tileCols:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Grout px ({data.groutWidth??4})</label><input type="range" min="1" max="12" step="1" value={data.groutWidth??4} onChange={e=>setter({groutWidth:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Height ({(data.heightScale??1.0).toFixed(2)})</label><input type="range" min="0.25" max="2.0" step="0.05" value={data.heightScale??1.0} onChange={e=>setter({heightScale:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                    </div>)}
                                                    {tmode==='hex' && (<div className="grid grid-cols-3 gap-3">
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Tile</label><input type="color" value={data.tileColor??'#e0e0e0'} onChange={e=>setter({tileColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Grout</label><input type="color" value={data.groutColor??'#999999'} onChange={e=>setter({groutColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Scale ({(data.textureScale??2).toFixed(1)})</label><input type="range" min="1" max="8" step="0.5" value={data.textureScale??2} onChange={e=>setter({textureScale:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Cols ({data.hexCols??4})</label><input type="range" min="2" max="8" step="1" value={data.hexCols??4} onChange={e=>setter({hexCols:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Grout px ({data.groutWidth??3})</label><input type="range" min="1" max="8" step="1" value={data.groutWidth??3} onChange={e=>setter({groutWidth:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Height ({(data.heightScale??1.0).toFixed(2)})</label><input type="range" min="0.3" max="2.0" step="0.05" value={data.heightScale??1.0} onChange={e=>setter({heightScale:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                    </div>)}
                                                    {tmode==='wood' && (<div className="grid grid-cols-3 gap-3">
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Wood</label><input type="color" value={data.woodColor??'#a0693a'} onChange={e=>setter({woodColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Grain</label><input type="color" value={data.grainColor??'#7a4a20'} onChange={e=>setter({grainColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Scale ({(data.textureScale??2).toFixed(1)})</label><input type="range" min="1" max="8" step="0.5" value={data.textureScale??2} onChange={e=>setter({textureScale:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div className="col-span-3"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Planks ({data.plankCols??4})</label><input type="range" min="2" max="8" step="1" value={data.plankCols??4} onChange={e=>setter({plankCols:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                    </div>)}
                                                    {tmode==='concrete' && (<div className="grid grid-cols-3 gap-3">
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Panel</label><input type="color" value={data.panelColor??'#9a9a9a'} onChange={e=>setter({panelColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Seam</label><input type="color" value={data.seamColor??'#707070'} onChange={e=>setter({seamColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Scale ({(data.textureScale??2).toFixed(1)})</label><input type="range" min="1" max="8" step="0.5" value={data.textureScale??2} onChange={e=>setter({textureScale:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Cols ({data.panelsX??2})</label><input type="range" min="1" max="6" step="1" value={data.panelsX??2} onChange={e=>setter({panelsX:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div className="col-span-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Rows ({data.panelsY??2})</label><input type="range" min="1" max="6" step="1" value={data.panelsY??2} onChange={e=>setter({panelsY:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                    </div>)}
                                                    {tmode==='checker' && (<div className="grid grid-cols-3 gap-3">
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Colour A</label><input type="color" value={data.colorA??'#ffffff'} onChange={e=>setter({colorA:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Colour B</label><input type="color" value={data.colorB??'#000000'} onChange={e=>setter({colorB:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Scale ({(data.textureScale??2).toFixed(1)})</label><input type="range" min="1" max="8" step="0.5" value={data.textureScale??2} onChange={e=>setter({textureScale:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div className="col-span-3"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Squares ({data.squares??4})</label><input type="range" min="2" max="8" step="1" value={data.squares??4} onChange={e=>setter({squares:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                    </div>)}
                                                    {tmode==='metal' && (<div className="grid grid-cols-3 gap-3">
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Metal</label><input type="color" value={data.metalColor??'#707888'} onChange={e=>setter({metalColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Seam</label><input type="color" value={data.seamColor??'#3a3f4a'} onChange={e=>setter({seamColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Scale ({(data.textureScale??2).toFixed(1)})</label><input type="range" min="1" max="8" step="0.5" value={data.textureScale??2} onChange={e=>setter({textureScale:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Highlight</label><input type="color" value={data.highlightColor??'#a0aabb'} onChange={e=>setter({highlightColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Cols ({data.panelsX??2})</label><input type="range" min="1" max="6" step="1" value={data.panelsX??2} onChange={e=>setter({panelsX:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Rows ({data.panelsY??3})</label><input type="range" min="1" max="6" step="1" value={data.panelsY??3} onChange={e=>setter({panelsY:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                    </div>)}
                                                    {tmode==='stucco' && (<div className="grid grid-cols-3 gap-3">
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Base</label><input type="color" value={data.baseColor??'#d4c8aa'} onChange={e=>setter({baseColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Dark</label><input type="color" value={data.darkColor??'#b0a48a'} onChange={e=>setter({darkColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Scale ({(data.textureScale??2).toFixed(1)})</label><input type="range" min="1" max="8" step="0.5" value={data.textureScale??2} onChange={e=>setter({textureScale:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                    </div>)}
                                                    {tmode==='circuit' && (<div className="grid grid-cols-3 gap-3">
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">BG</label><input type="color" value={data.bgColor??'#1a2a1a'} onChange={e=>setter({bgColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Trace</label><input type="color" value={data.traceColor??'#2d6b2d'} onChange={e=>setter({traceColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Scale ({(data.textureScale??2).toFixed(1)})</label><input type="range" min="1" max="8" step="0.5" value={data.textureScale??2} onChange={e=>setter({textureScale:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                        <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Node</label><input type="color" value={data.nodeColor??'#44ff44'} onChange={e=>setter({nodeColor:e.target.value})} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                        <div className="col-span-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Grid ({data.grid??16})</label><input type="range" min="8" max="32" step="2" value={data.grid??16} onChange={e=>setter({grid:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                    </div>)}
                                                    {tmode==='file' && !noFile && (<div className="grid grid-cols-2 gap-3">
                                                        <div className="col-span-2">
                                                            {textureFiles.length===0
                                                                ? <p className="text-xs text-slate-400 dark:text-slate-500 italic">No textures found. Add PNG files to assets/textures/ and update manifest.json, then click Rescan.</p>
                                                                : <select value={data.textureFile??''} onChange={e=>setter({textureFile:e.target.value})} className="w-full border border-slate-200 dark:border-slate-700 rounded p-2 text-sm"><option value="">— select file —</option>{textureFiles.map(f=><option key={f} value={f}>{f}</option>)}</select>}
                                                        </div>
                                                        <div className="col-span-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Scale ({(data.textureScale??2).toFixed(1)})</label><input type="range" min="1" max="8" step="0.5" value={data.textureScale??2} onChange={e=>setter({textureScale:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                    </div>)}
                                                    {tmode!=='solid' && (<div className="flex items-center gap-3 pt-1 border-t border-slate-100">
                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 shrink-0">Tint</label>
                                                        <input type="color" value={data.tintColor??'#ffffff'} onChange={e=>setter({tintColor:e.target.value})} className="w-10 h-7 rounded cursor-pointer" title="Multiply-tint the texture (white = no tint)" />
                                                        <span className="text-xs text-slate-400 dark:text-slate-500">{data.tintColor && data.tintColor!=='#ffffff' ? data.tintColor : 'none'}</span>
                                                    </div>)}
                                                </>);
                                            };
                                            const outlineCtrls = (surfKey) => {
                                                const sk = subKeys(surfKey);
                                                const state = t[`${surfKey}Outline`];
                                                const outCol = t[sk.outColor] ?? (sk.legacyOutColor ? t[sk.legacyOutColor] : undefined) ?? t.wallOutlineColor ?? '#000000';
                                                const outStyle = t[sk.outStyle] ?? (surfKey === 'exit' ? t.wallOutlineStyle ?? 'box' : 'box');
                                                const subX = t[sk.sx] ?? (sk.legacySx ? t[sk.legacySx] : undefined) ?? 1;
                                                const subY = t[sk.sy] ?? (sk.legacySy ? t[sk.legacySy] : undefined) ?? 1;
                                                return (
                                                    <div className="flex items-center gap-2 flex-wrap">
                                                        <span className="text-xs font-bold text-slate-500 dark:text-slate-400">Outline:</span>
                                                        <select
                                                            value={state === undefined ? 'default' : (state ? 'custom' : 'off')}
                                                            onChange={e => {
                                                                const v = e.target.value;
                                                                if (v === 'default') updateTheme(lvl, t, { [`${surfKey}Outline`]: undefined, [sk.outStyle]: undefined });
                                                                else if (v === 'custom') updateTheme(lvl, t, { [`${surfKey}Outline`]: true, [sk.outColor]: outCol, [sk.outStyle]: outStyle });
                                                                else if (v === 'off') updateTheme(lvl, t, { [`${surfKey}Outline`]: false, [sk.outStyle]: undefined });
                                                            }}
                                                            className="text-xs bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded px-2 py-1 text-slate-700 dark:text-slate-300"
                                                        >
                                                            <option value="default">Theme Default</option>
                                                            <option value="custom">Custom</option>
                                                            <option value="off">Off</option>
                                                        </select>

                                                        {state === undefined && (
                                                            <div className="flex items-center gap-2 opacity-50 pointer-events-none" title="Inheriting from Theme">
                                                                <input type="color" readOnly value={t.wallOutlineColor || '#000000'} className="w-8 h-7 rounded cursor-not-allowed bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                {surfKey === 'exit' && <span className="text-xs capitalize">{t.wallOutlineStyle ?? 'box'}</span>}
                                                            </div>
                                                        )}
                                                        {state === true && (
                                                            <div className="flex items-center gap-2 flex-wrap">
                                                                <input type="color" title="Outline Color" value={t[sk.outColor] ?? t.wallOutlineColor ?? '#000000'} onChange={e => updateTheme(lvl, t, { [sk.outColor]: e.target.value })} className="w-8 h-7 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                {surfKey === 'exit' && (
                                                                    ['box', 'edge', 'none'].map(style => (
                                                                        <button key={style} onClick={() => updateTheme(lvl, t, { [sk.outStyle]: style })}
                                                                            className={`px-3 py-1 rounded text-xs font-bold border transition-colors ${(t[sk.outStyle] ?? t.wallOutlineStyle ?? 'box') === style ? 'bg-emerald-500 text-white border-emerald-500' : 'bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400 border-slate-300 dark:border-slate-600 hover:border-emerald-400'}`}>
                                                                            {style === 'box' ? 'Box' : style === 'edge' ? 'Edge' : 'None'}
                                                                        </button>
                                                                    ))
                                                                )}
                                                                {((t[sk.outStyle] ?? t.wallOutlineStyle ?? 'box') === 'box' || surfKey !== 'exit') && <>
                                                                    <span className="text-xs text-slate-400 dark:text-slate-500" title="X Subdivision">÷X</span>
                                                                    <input type="number" title="X Subdivision" min="1" max="10" value={subX} onChange={e => updateTheme(lvl, t, { [sk.sx]: parseInt(e.target.value)||1 })} className="w-12 bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 text-slate-800 dark:text-slate-200 rounded p-1 text-center text-xs" />
                                                                    <span className="text-xs text-slate-400 dark:text-slate-500" title="Y Subdivision">÷Y</span>
                                                                    <input type="number" title="Y Subdivision" min="1" max="10" value={subY} onChange={e => updateTheme(lvl, t, { [sk.sy]: parseInt(e.target.value)||1 })} className="w-12 bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 text-slate-800 dark:text-slate-200 rounded p-1 text-center text-xs" />
                                                                </>}
                                                            </div>
                                                        )}
                                                    </div>
                                                );
                                            };
                                            const pillItem = (key, label, pillHeader, pillBody) => {
                                                const open = surfacePill === key;
                                                return (
                                                    <div key={key} className="rounded-2xl border border-slate-200 dark:border-slate-700 overflow-hidden bg-white dark:bg-slate-800">
                                                        <button onClick={() => setSurfacePill(open ? null : key)} className="w-full flex items-center gap-3 px-4 py-3 hover:bg-slate-50 dark:bg-slate-900 text-left transition-colors">
                                                            <span className="font-bold text-slate-700 dark:text-slate-300 dark:text-slate-600 text-sm">{label}</span>
                                                            {pillHeader}
                                                            <span className="ml-auto text-slate-400 dark:text-slate-500 text-xs">{open ? '▲' : '▼'}</span>
                                                        </button>
                                                        {open && <div className="border-t border-slate-100 bg-slate-50 dark:bg-slate-900 p-4 space-y-4">{pillBody}</div>}
                                                    </div>
                                                );
                                            };
                                            const wallZoneEditor = (z, i, setZ, removable) => (
                                                <div key={i} className="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-4 space-y-3">
                                                    <div className="flex items-center gap-3 flex-wrap">
                                                        <span className="text-xs font-mono text-slate-400 dark:text-slate-500">Zone #{i+1}</span>
                                                        <label className="text-xs text-slate-400 dark:text-slate-500 ml-auto">Height</label>
                                                        <input type="range" min="0.1" max="2.0" step="0.05" value={z.height??1.0} onChange={e=>setZ({height:parseFloat(e.target.value)})} className="w-24" />
                                                        <span className="text-xs font-mono text-slate-400 dark:text-slate-500 w-9">{(z.height??1.0).toFixed(2)}</span>
                                                        {removable && <button onClick={()=>removable()} className="text-rose-400 hover:text-rose-600"><TrashIcon /></button>}
                                                    </div>
                                                    {texCtrls(z, setZ, false, z.color||'#334155', c=>setZ({color:c}))}
                                                    <div className="flex items-center gap-3 flex-wrap pt-3 mt-1 border-t border-slate-100 dark:border-slate-700">
                                                        <span className="text-xs font-bold text-slate-500 dark:text-slate-400">Outline:</span>
                                                        <select
                                                            value={z.outline === undefined ? 'default' : (z.outline ? 'custom' : 'off')}
                                                            onChange={e => {
                                                                const v = e.target.value;
                                                                if (v === 'default') setZ({outline: undefined, outlineStyle: undefined});
                                                                else if (v === 'custom') setZ({outline: true, outlineColor: z.outlineColor ?? t.wallOutlineColor ?? '#000000', outlineStyle: z.outlineStyle ?? t.wallOutlineStyle ?? 'box'});
                                                                else if (v === 'off') setZ({outline: false, outlineStyle: undefined});
                                                            }}
                                                            className="text-xs bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded px-2 py-1 text-slate-700 dark:text-slate-300"
                                                        >
                                                            <option value="default">Theme Default</option>
                                                            <option value="custom">Custom</option>
                                                            <option value="off">Off</option>
                                                        </select>

                                                        {z.outline === undefined && (
                                                            <div className="flex items-center gap-2 opacity-50 pointer-events-none" title="Inheriting from Theme">
                                                                <input type="color" readOnly value={t.wallOutlineColor || '#000000'} className="w-8 h-7 rounded cursor-not-allowed bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                <span className="text-xs capitalize">{t.wallOutlineStyle ?? 'box'}</span>
                                                            </div>
                                                        )}
                                                        {z.outline === true && (
                                                            <div className="flex items-center gap-2 flex-wrap">
                                                                <input type="color" title="Outline Color" value={z.outlineColor ?? t.wallOutlineColor ?? '#000000'} onChange={e => setZ({ outlineColor: e.target.value })} className="w-8 h-7 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                {['box', 'edge', 'none'].map(style => (
                                                                    <button key={style} onClick={() => setZ({ outlineStyle: style })}
                                                                        className={`px-3 py-1 rounded text-xs font-bold border transition-colors ${(z.outlineStyle ?? t.wallOutlineStyle ?? 'box') === style ? 'bg-emerald-500 text-white border-emerald-500' : 'bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400 border-slate-300 dark:border-slate-600 hover:border-emerald-400'}`}>
                                                                        {style === 'box' ? 'Box' : style === 'edge' ? 'Edge' : 'None'}
                                                                    </button>
                                                                ))}
                                                                {(z.outlineStyle ?? t.wallOutlineStyle ?? 'box') === 'box' && <>
                                                                    <span className="text-xs text-slate-400 dark:text-slate-500" title="X Subdivision">÷X</span>
                                                                    <input type="number" title="X Subdivision" min="1" max="10" value={z.subdivX??1} onChange={e => setZ({ subdivX: parseInt(e.target.value)||1 })} className="w-12 bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 text-slate-800 dark:text-slate-200 rounded p-1 text-center text-xs" />
                                                                    <span className="text-xs text-slate-400 dark:text-slate-500" title="Y Subdivision">÷Y</span>
                                                                    <input type="number" title="Y Subdivision" min="1" max="10" value={z.subdivY??1} onChange={e => setZ({ subdivY: parseInt(e.target.value)||1 })} className="w-12 bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 text-slate-800 dark:text-slate-200 rounded p-1 text-center text-xs" />
                                                                </>}
                                                            </div>
                                                        )}
                                                    </div>
                                                </div>
                                            );


                                            return (
                                                <div className="max-w-3xl mx-auto pb-12">
                                                    <div className="mb-4 flex gap-3">
                                                        <div className="flex-1">
                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-2 uppercase">Theme Name</label>
                                                            <input type="text" value={lvl.name} onChange={e => {
                                                                setGameData(prev => ({ ...prev, themes: prev.themes.map(x => x.id === lvl.id ? { ...x, name: e.target.value } : x) }));
                                                            }} className="w-full border border-slate-200 dark:border-slate-700 rounded-lg p-2.5 bg-slate-50 dark:bg-slate-900 focus:outline-none focus:ring-2 focus:ring-emerald-500" />
                                                        </div>
                                                        <div className="w-36">
                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-2 uppercase">Category</label>
                                                            <input type="text" value={lvl.category || ''} onChange={e => {
                                                                setGameData(prev => ({ ...prev, themes: prev.themes.map(x => x.id === lvl.id ? { ...x, category: e.target.value } : x) }));
                                                            }} className="w-full border border-slate-200 dark:border-slate-700 rounded-lg p-2.5 bg-slate-50 dark:bg-slate-900 focus:outline-none focus:ring-2 focus:ring-emerald-500" placeholder="e.g. Neon" />
                                                        </div>
                                                    </div>
                                                    <div className="mb-6">
                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-2 uppercase">Tagline <span className="normal-case font-normal text-slate-400 dark:text-slate-500">— shown on menu when this theme is selected</span></label>
                                                        <input type="text" value={lvl.tagline || ''} onChange={e => {
                                                            setGameData(prev => ({ ...prev, themes: prev.themes.map(x => x.id === lvl.id ? { ...x, tagline: e.target.value } : x) }));
                                                        }} className="w-full border border-slate-200 dark:border-slate-700 rounded-lg p-2.5 bg-slate-50 dark:bg-slate-900 focus:outline-none focus:ring-2 focus:ring-emerald-500 italic text-slate-600 dark:text-slate-400 dark:text-slate-500" placeholder="e.g. The lights don't flicker. They watch." />
                                                    </div>

                                                    <div className="flex flex-wrap gap-1 mb-6 bg-slate-100 dark:bg-slate-800/80 p-1 rounded-lg w-fit">
                                                        {['map', 'levels', 'surfaces', 'environment', 'modes', 'player', 'menu'].map(tab => (
                                                            <button key={tab} onClick={() => setThemeSubTab(tab)}
                                                                className={`px-4 py-1.5 rounded text-sm font-bold capitalize transition-colors ${themeSubTab === tab ? 'bg-white dark:bg-slate-800 shadow text-emerald-600' : 'text-slate-500 dark:text-slate-400 dark:text-slate-500 hover:text-slate-700 dark:text-slate-300 dark:text-slate-600'}`}>
                                                                {tab}
                                                            </button>
                                                        ))}
                                                    </div>

                                                    {themeSubTab === 'map' && (() => {
                                                        const mg = lvl.theme.mapGen || {};
                                                        const mode = mg.mode ?? 'maze';
                                                        return (
                                                            <div className="max-w-3xl mx-auto pb-12 space-y-6">
                                                                <div className="p-6 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 space-y-4">
                                                                    <div className="border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Preview size</label></div>
                                                                    <div>
                                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Grid size ({mapGenPreviewSize}×{mapGenPreviewSize})</label>
                                                                        <input type="range" min="11" max="101" step="2" value={mapGenPreviewSize}
                                                                            onChange={e => setMapGenPreviewSize(parseInt(e.target.value))}
                                                                            onMouseUp={e => { const s = parseInt(e.target.value); setMapGenPreviewSize(s); regenerateMapGenPreview(lvl, s); }}
                                                                            className="w-full mt-1" />
                                                                    </div>
                                                                </div>
                                                                <div className="p-6 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 space-y-4">
                                                                    <div className="border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Mode</label></div>
                                                                    <div className="flex gap-2">
                                                                        {['maze', 'rooms', 'columns'].map(m => (
                                                                            <button key={m} onClick={() => applyMapGen(lvl, mg, { mode: m })}
                                                                                className={`flex-1 py-2 px-3 rounded-xl font-bold text-sm capitalize transition-colors ${mode === m ? 'bg-emerald-500 text-white' : 'bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400 dark:text-slate-500 border border-slate-200 dark:border-slate-700 hover:border-emerald-400'}`}>
                                                                                {m === 'maze' ? 'Maze' : m === 'rooms' ? 'Rooms' : 'Columns'}
                                                                            </button>
                                                                        ))}
                                                                    </div>
                                                                    {mode === 'maze' && (
                                                                        <div>
                                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Loop factor ({(mg.loopFactor ?? 0.08).toFixed(2)} — 0 = perfect maze)</label>
                                                                            <input type="range" min="0" max="0.5" step="0.01" value={mg.loopFactor ?? 0.08}
                                                                                onChange={e => updateMapGen(lvl, mg, { loopFactor: parseFloat(e.target.value) })}
                                                                                onMouseUp={e => applyMapGen(lvl, mg, { loopFactor: parseFloat(e.target.value) })}
                                                                                className="w-full mt-1" />
                                                                        </div>
                                                                    )}
                                                                    {mode === 'rooms' && (
                                                                        <div className="grid grid-cols-3 gap-4">
                                                                            <div>
                                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Density ({(mg.roomDensity ?? 0.15).toFixed(2)})</label>
                                                                                <input type="range" min="0.01" max="0.5" step="0.01" value={mg.roomDensity ?? 0.15}
                                                                                    onChange={e => updateMapGen(lvl, mg, { roomDensity: parseFloat(e.target.value) })}
                                                                                    onMouseUp={e => applyMapGen(lvl, mg, { roomDensity: parseFloat(e.target.value) })}
                                                                                    className="w-full mt-1" />
                                                                            </div>
                                                                            <div>
                                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Min size ({mg.roomMinSize ?? 4})</label>
                                                                                <input type="range" min="2" max="8" step="1" value={mg.roomMinSize ?? 4}
                                                                                    onChange={e => updateMapGen(lvl, mg, { roomMinSize: parseInt(e.target.value) })}
                                                                                    onMouseUp={e => applyMapGen(lvl, mg, { roomMinSize: parseInt(e.target.value) })}
                                                                                    className="w-full mt-1" />
                                                                            </div>
                                                                            <div>
                                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Max size ({mg.roomMaxSize ?? 8})</label>
                                                                                <input type="range" min="3" max="15" step="1" value={mg.roomMaxSize ?? 8}
                                                                                    onChange={e => updateMapGen(lvl, mg, { roomMaxSize: parseInt(e.target.value) })}
                                                                                    onMouseUp={e => applyMapGen(lvl, mg, { roomMaxSize: parseInt(e.target.value) })}
                                                                                    className="w-full mt-1" />
                                                                            </div>
                                                                        </div>
                                                                    )}
                                                                    {mode === 'columns' && (
                                                                        <div className="grid grid-cols-3 gap-4">
                                                                            <div>
                                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Density ({(mg.columnDensity ?? 0.15).toFixed(2)})</label>
                                                                                <input type="range" min="0.01" max="0.4" step="0.01" value={mg.columnDensity ?? 0.15}
                                                                                    onChange={e => updateMapGen(lvl, mg, { columnDensity: parseFloat(e.target.value) })}
                                                                                    onMouseUp={e => applyMapGen(lvl, mg, { columnDensity: parseFloat(e.target.value) })}
                                                                                    className="w-full mt-1" />
                                                                            </div>
                                                                            <div>
                                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Max width ({mg.columnMaxW ?? 3})</label>
                                                                                <input type="range" min="1" max="8" step="1" value={mg.columnMaxW ?? 3}
                                                                                    onChange={e => updateMapGen(lvl, mg, { columnMaxW: parseInt(e.target.value) })}
                                                                                    onMouseUp={e => applyMapGen(lvl, mg, { columnMaxW: parseInt(e.target.value) })}
                                                                                    className="w-full mt-1" />
                                                                            </div>
                                                                            <div>
                                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Max height ({mg.columnMaxH ?? 2})</label>
                                                                                <input type="range" min="1" max="8" step="1" value={mg.columnMaxH ?? 2}
                                                                                    onChange={e => updateMapGen(lvl, mg, { columnMaxH: parseInt(e.target.value) })}
                                                                                    onMouseUp={e => applyMapGen(lvl, mg, { columnMaxH: parseInt(e.target.value) })}
                                                                                    className="w-full mt-1" />
                                                                            </div>
                                                                        </div>
                                                                    )}
                                                                </div>
                                                                <div className="p-6 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 space-y-4">
                                                                    <div className="border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Seed</label></div>
                                                                    <div className="flex gap-2 items-center">
                                                                        <input
                                                                            type="number"
                                                                            placeholder="Blank = random each game"
                                                                            value={mg.seed ?? ''}
                                                                            onChange={e => updateMapGen(lvl, mg, { seed: e.target.value === '' ? null : parseInt(e.target.value) || null })}
                                                                            onBlur={e => applyMapGen(lvl, mg, { seed: e.target.value === '' ? null : parseInt(e.target.value) || null })}
                                                                            className="flex-1 border border-slate-200 dark:border-slate-700 rounded-lg p-2 font-mono text-sm"
                                                                        />
                                                                        <button onClick={() => applyMapGen(lvl, mg, { seed: Math.floor(Math.random() * 999999) })} className="px-3 py-2 bg-slate-200 hover:bg-slate-300 text-slate-700 dark:text-slate-300 dark:text-slate-600 rounded-lg text-xs font-bold">Random</button>
                                                                        <button onClick={() => applyMapGen(lvl, mg, { seed: null })} className="px-3 py-2 bg-slate-200 hover:bg-slate-300 text-slate-700 dark:text-slate-300 dark:text-slate-600 rounded-lg text-xs font-bold">Clear</button>
                                                                    </div>
                                                                    <p className="text-xs text-slate-400 dark:text-slate-500">Set a seed to get the same layout every game (incremented per level). Leave blank for a new layout each run.</p>
                                                                    {!mg.seed && (
                                                                        <button onClick={() => regenerateMapGenPreview({ ...lvl, theme: { ...lvl.theme, mapGen: mg } })}
                                                                            className="w-full py-2 px-4 bg-emerald-500 hover:bg-emerald-600 text-white rounded-xl font-bold text-sm transition-colors">
                                                                            ↺ New random
                                                                        </button>
                                                                    )}
                                                                </div>
                                                                <div className="p-6 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 space-y-5">
                                                                    <div className="border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Map Preview Colours</label></div>
                                                                    {[
                                                                        {
                                                                            rowLabel: 'Wall',
                                                                            mode: t.mapWallMode || 'z0',
                                                                            resolved: resolveMapWallColor(t),
                                                                            swatches: [
                                                                                ...wallZones.map((z, i) => ({ key: `z${i}`, label: `zone ${i+1}`, color: zoneMapColor(z) })),
                                                                                ...(t.capColor ? [{ key: 'cap', label: 'cap', color: t.capColor }] : []),
                                                                                ...(t.wallOutlineColor ? [{ key: 'wallOutline', label: 'outline', color: t.wallOutlineColor }] : []),
                                                                            ],
                                                                            onSelect: (key) => updateTheme(lvl, t, {mapWallMode: key === 'z0' ? undefined : key, mapWallColor: undefined}),
                                                                            onCustom: (hex) => updateTheme(lvl, t, {mapWallMode: 'custom', mapWallColor: hex}),
                                                                            customVal: t.mapWallColor || resolveMapWallColor({...t, mapWallMode: undefined}),
                                                                        },
                                                                        {
                                                                            rowLabel: 'Floor',
                                                                            mode: t.mapFloorMode || 'auto',
                                                                            resolved: resolveMapFloorColor(t),
                                                                            swatches: [
                                                                                { key: 'auto', label: 'floor', color: t.floor || '#f8fafc' },
                                                                                ...(t.floorOutlineColor ? [{ key: 'floorOutline', label: 'outline', color: t.floorOutlineColor }] : []),
                                                                            ],
                                                                            onSelect: (key) => updateTheme(lvl, t, {mapFloorMode: key === 'auto' ? undefined : key, mapFloorColor: undefined}),
                                                                            onCustom: (hex) => updateTheme(lvl, t, {mapFloorMode: 'custom', mapFloorColor: hex}),
                                                                            customVal: t.mapFloorColor || t.floor || '#f8fafc',
                                                                        },
                                                                        {
                                                                            rowLabel: 'Exit',
                                                                            mode: t.mapExitMode || 'auto',
                                                                            resolved: resolveMapExitColor(t),
                                                                            swatches: [
                                                                                { key: 'auto', label: 'exit', color: t.exitColor || '#ef4444' },
                                                                                ...(t.exitOutlineColor ? [{ key: 'exitOutline', label: 'outline', color: t.exitOutlineColor }] : []),
                                                                            ],
                                                                            onSelect: (key) => updateTheme(lvl, t, {mapExitMode: key === 'auto' ? undefined : key, mapExitColor: undefined}),
                                                                            onCustom: (hex) => updateTheme(lvl, t, {mapExitMode: 'custom', mapExitColor: hex}),
                                                                            customVal: t.mapExitColor || t.exitColor || '#ef4444',
                                                                        },
                                                                    ].map(({rowLabel, mode, resolved, swatches, onSelect, onCustom, customVal}) => (
                                                                        <div key={rowLabel} className="flex items-start gap-3">
                                                                            <span className="text-xs font-bold text-slate-500 dark:text-slate-400 dark:text-slate-500 w-12 flex-shrink-0 pt-1.5">{rowLabel}</span>
                                                                            <div className="flex items-start gap-2 flex-wrap">
                                                                                {/* Current — display only */}
                                                                                <div className="flex flex-col items-center gap-0.5">
                                                                                    <div className="w-8 h-8 rounded border-2 border-slate-200 dark:border-slate-700 cursor-default" style={{background: resolved}} />
                                                                                    <span className="text-[9px] text-slate-400 dark:text-slate-500 italic leading-tight">current</span>
                                                                                </div>
                                                                                <div className="w-px bg-slate-200 self-stretch mx-0.5" />
                                                                                {/* Selectable swatches */}
                                                                                {swatches.map(({key, label, color}) => (
                                                                                    <div key={key} className="flex flex-col items-center gap-0.5">
                                                                                        <button title={label} onClick={() => onSelect(key)}
                                                                                            className={`w-7 h-7 rounded transition-all ${mode === key ? 'ring-2 ring-emerald-500 ring-offset-1' : 'ring-1 ring-slate-300 hover:ring-slate-400'}`}
                                                                                            style={{background: color}} />
                                                                                        <span className="text-[9px] text-slate-400 dark:text-slate-500 leading-tight">{label}</span>
                                                                                    </div>
                                                                                ))}
                                                                                {/* Custom */}
                                                                                <div className="flex flex-col items-center gap-0.5">
                                                                                    <label title="Custom colour"
                                                                                        className={`w-7 h-7 rounded cursor-pointer overflow-hidden relative block transition-all ${mode === 'custom' ? 'ring-2 ring-emerald-500 ring-offset-1' : 'ring-1 ring-slate-300 hover:ring-slate-400'}`}
                                                                                        style={mode === 'custom' ? {background: customVal} : {background:'conic-gradient(#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)'}}>
                                                                                        <input type="color" value={customVal} onChange={e => onCustom(e.target.value)}
                                                                                            className="opacity-0 absolute inset-0 w-full h-full cursor-pointer" />
                                                                                    </label>
                                                                                    <span className="text-[9px] text-slate-400 dark:text-slate-500 leading-tight">custom</span>
                                                                                </div>
                                                                            </div>
                                                                        </div>
                                                                    ))}
                                                                </div>
                                                            </div>
                                                        );
                                                    })()}

                                                    {themeSubTab === 'surfaces' && (
                                                        <div className="space-y-2">
                                                            <div className="bg-white dark:bg-slate-800 rounded-2xl border border-slate-200 dark:border-slate-700 p-4 mb-4">
                                                                <label className="flex items-center gap-2 mb-2">
                                                                    <span className="font-bold text-slate-700 dark:text-slate-300">Default Wall Outline</span>
                                                                </label>
                                                                <div className="flex items-center gap-3">
                                                                    <input type="color" title="Default Outline Color" value={t.wallOutlineColor || '#000000'} onChange={e=>updateTheme(lvl,t,{wallOutlineColor:e.target.value})} className="w-8 h-7 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                    <span className="text-xs font-bold text-slate-400 dark:text-slate-500 ml-2">Style:</span>
                                                                    {['box','edge','none'].map(style=>(
                                                                        <button key={style} onClick={()=>updateTheme(lvl,t,{wallOutlineStyle:style})}
                                                                            className={`px-3 py-1 rounded text-xs font-bold border transition-colors ${(t.wallOutlineStyle||'box')===style?'bg-emerald-500 text-white border-emerald-500':'bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400 border-slate-300 dark:border-slate-600 hover:border-emerald-400'}`}>
                                                                            {style==='box'?'Box':style==='edge'?'Edge':'None'}
                                                                        </button>
                                                                    ))}
                                                                </div>
                                                            </div>
                                                            {/* ── Wall ── */}
                                                            {pillItem('wall', 'Wall Zones',
                                                                <div className="flex gap-1 ml-2">{wallZones.map((z,i)=><span key={i} className="w-4 h-4 rounded border border-slate-300 dark:border-slate-600 inline-block" style={{background:z.color||'#334155'}} />)}</div>,
                                                                <div className="space-y-4">
                                                                    {wallZones.map((z,i)=>wallZoneEditor(z,i,patch=>setZone(i,patch),wallZones.length>1?()=>removeZone(i):null))}
                                                                    <div className="flex items-center gap-3 flex-wrap pt-2 border-t border-slate-200 dark:border-slate-700">
                                                                        <button onClick={addZone} className="text-xs bg-emerald-100 text-emerald-700 px-3 py-1.5 rounded font-bold hover:bg-emerald-200">+ Add zone</button>
                                                                        <button onClick={loadTextureManifest} className="text-xs bg-slate-100 dark:bg-slate-800/80 text-slate-600 dark:text-slate-400 dark:text-slate-500 px-2 py-1.5 rounded font-bold hover:bg-slate-200 dark:hover:bg-slate-600">↺ Rescan textures</button>
                                                                    </div>
                                                                </div>
                                                            )}
                                                            {/* ── Floor ── */}
                                                            {pillItem('floor', 'Floor',
                                                                <span className="w-4 h-4 rounded border border-slate-300 dark:border-slate-600 inline-block ml-2" style={{background:t.floor||'#f8fafc'}} />,
                                                                <div className="space-y-3">
                                                                    {texCtrls(t.floorTexture||{}, patch=>updateTheme(lvl,t,{floorTexture:{...(t.floorTexture||{}),...patch}}), false, t.floor||'#f8fafc', c=>updateTheme(lvl,t,{floor:c}))}
                                                                    {outlineCtrls('floor')}
                                                                </div>
                                                            )}
                                                            {/* ── Exit ── */}
                                                            {pillItem('exit', 'Exit',
                                                                <span className="w-4 h-4 rounded border border-slate-300 dark:border-slate-600 inline-block ml-2" style={{background:t.exitColor||'#ef4444'}} />,
                                                                <div className="space-y-3">
                                                                    {texCtrls(t.exitTexture||{}, patch=>updateTheme(lvl,t,{exitTexture:{...(t.exitTexture||{}),...patch}}), false, t.exitColor||'#ef4444', c=>updateTheme(lvl,t,{exitColor:c}))}
                                                                    {outlineCtrls('exit')}
                                                                    <div className="pt-2 border-t border-slate-200 dark:border-slate-700 space-y-2">
                                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase block">Height</label>
                                                                        <label className="flex items-center gap-2 cursor-pointer"><input type="checkbox" checked={t.linkExitToCeiling!==false} onChange={e=>updateTheme(lvl,t,{linkExitToCeiling:e.target.checked})} className="w-4 h-4 text-emerald-500" /><span className="text-sm font-bold text-slate-700 dark:text-slate-300 dark:text-slate-600">Match ceiling height</span></label>
                                                                        {t.linkExitToCeiling===false && <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Block height ({(t.exitHeight??1.0).toFixed(1)})</label><input type="range" min="0.1" max="2.0" step="0.1" value={t.exitHeight??1.0} onChange={e=>updateTheme(lvl,t,{exitHeight:parseFloat(e.target.value)})} className="w-full mt-1" /></div>}
                                                                    </div>
                                                                    <div className="pt-2 border-t border-slate-200 dark:border-slate-700 space-y-2">
                                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase block">Text</label>
                                                                        <div className="grid grid-cols-2 gap-3">
                                                                            <div className="col-span-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Display text</label><input type="text" value={t.exitText||'EXIT'} onChange={e=>updateTheme(lvl,t,{exitText:e.target.value})} className="w-full border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 text-slate-900 dark:text-white rounded p-2 text-center font-bold font-mono" /></div>
                                                                            <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Text colour</label><input type="color" value={t.exitTextColor||'#ffffff'} onChange={e=>updateTheme(lvl,t,{exitTextColor:e.target.value})} className="w-full h-10 rounded cursor-pointer" /></div>
                                                                            <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Font size ({t.exitTextSize??52}px)</label><input type="range" min="16" max="80" step="2" value={t.exitTextSize??52} onChange={e=>updateTheme(lvl,t,{exitTextSize:parseInt(e.target.value)})} className="w-full mt-2" /></div>
                                                                            <div className="col-span-2"><label className="flex items-center gap-2 cursor-pointer"><input type="checkbox" checked={t.linkExitTextToMid!==false} onChange={e=>updateTheme(lvl,t,{linkExitTextToMid:e.target.checked})} className="w-4 h-4 text-emerald-500" /><span className="text-sm font-bold text-slate-700 dark:text-slate-300 dark:text-slate-600">Centre text vertically</span></label></div>
                                                                            {t.linkExitTextToMid===false && <div className="col-span-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Text height ({(t.exitTextY??0.5).toFixed(2)} — 0=bottom, 1=top)</label><input type="range" min="0" max="1" step="0.05" value={t.exitTextY??0.5} onChange={e=>updateTheme(lvl,t,{exitTextY:parseFloat(e.target.value)})} className="w-full mt-1" /></div>}
                                                                        </div>
                                                                    </div>
                                                                </div>
                                                            )}
                                                            {/* ── Ceiling ── */}
                                                            {pillItem('ceiling', 'Ceiling',
                                                                <><span className="w-4 h-4 rounded border border-slate-300 dark:border-slate-600 inline-block ml-2" style={{background:t.ceiling||'#1e293b'}} />{!t.hasCeiling&&<span className="text-xs text-slate-400 dark:text-slate-500 ml-1">off</span>}</>,
                                                                <div className="space-y-3">
                                                                    <label className="flex items-center gap-2 cursor-pointer"><input type="checkbox" checked={!!t.hasCeiling} onChange={e=>updateTheme(lvl,t,{hasCeiling:e.target.checked})} className="w-4 h-4 text-emerald-500" /><span className="text-sm font-bold text-slate-700 dark:text-slate-300 dark:text-slate-600">Enable solid ceiling</span></label>
                                                                    {t.hasCeiling && <>
                                                                        {texCtrls(t.ceilingTexture||{}, patch=>updateTheme(lvl,t,{ceilingTexture:{...(t.ceilingTexture||{}),...patch}}), true, t.ceiling||'#1e293b', c=>updateTheme(lvl,t,{ceiling:c}))}
                                                                        {outlineCtrls('ceiling')}
                                                                        <div className="pt-2 border-t border-slate-200 dark:border-slate-700 space-y-2">
                                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase block">Height</label>
                                                                            <label className="flex items-center gap-2 cursor-pointer"><input type="checkbox" checked={t.linkCeilingToWall!==false} onChange={e=>updateTheme(lvl,t,{linkCeilingToWall:e.target.checked})} className="w-4 h-4 text-emerald-500" /><span className="text-sm font-bold text-slate-700 dark:text-slate-300 dark:text-slate-600">Lock to wall height</span></label>
                                                                            {t.linkCeilingToWall===false && <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Manual height ({(t.manualCeilingHeight??1.0).toFixed(1)})</label><input type="range" min="0.6" max="3.0" step="0.1" value={t.manualCeilingHeight??1.0} onChange={e=>updateTheme(lvl,t,{manualCeilingHeight:parseFloat(e.target.value)})} className="w-full mt-1" /></div>}
                                                                        </div>
                                                                        <div className="pt-2 border-t border-slate-200 dark:border-slate-700 space-y-2">
                                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase block">Lights</label>
                                                                            <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Density ({(t.ceilingLightDensity||0).toFixed(2)})</label><input type="range" min="0" max="1" step="0.05" value={t.ceilingLightDensity||0} onChange={e=>updateTheme(lvl,t,{ceilingLightDensity:parseFloat(e.target.value)})} className="w-full" /></div>
                                                                            {(t.ceilingLightDensity||0)>0 && <div className="flex gap-3">
                                                                                <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Colour</label><input type="color" value={t.ceilingLightColor||'#ffffff'} onChange={e=>updateTheme(lvl,t,{ceilingLightColor:e.target.value})} className="w-16 h-10 rounded cursor-pointer" /></div>
                                                                                <div className="flex-1"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Intensity ({(t.ceilingLightIntensity??0.8).toFixed(2)})</label><input type="range" min="0" max="1.5" step="0.05" value={t.ceilingLightIntensity??0.8} onChange={e=>updateTheme(lvl,t,{ceilingLightIntensity:parseFloat(e.target.value)})} className="w-full mt-2" /></div>
                                                                            </div>}
                                                                        </div>
                                                                    </>}
                                                                </div>
                                                            )}
                                                            {/* ── Cap ── */}
                                                            {pillItem('cap', 'Cap',
                                                                <><span className="w-4 h-4 rounded border border-slate-300 dark:border-slate-600 inline-block ml-2" style={{background:t.capColor||(wallZones[wallZones.length-1]?.color||'#334155')}} />{!t.capColor&&<span className="text-xs text-slate-400 dark:text-slate-500 ml-1">uses wall</span>}</>,
                                                                <div className="space-y-3">
                                                                    <p className="text-xs text-slate-400 dark:text-slate-500">Cap is the top face of walls. Unset = inherits top wall zone colour.</p>
                                                                    <div className="flex items-center gap-3">
                                                                        {t.capColor && <button onClick={()=>updateTheme(lvl,t,{capColor:undefined})} className="text-xs text-slate-400 dark:text-slate-500 hover:text-rose-500">Clear (use wall)</button>}
                                                                    </div>
                                                                    {texCtrls(t.capTexture||{}, patch=>updateTheme(lvl,t,{capTexture:{...(t.capTexture||{}),...patch}}), true, t.capColor||(wallZones[wallZones.length-1]?.color||'#334155'), c=>updateTheme(lvl,t,{capColor:c}))}
                                                                    {outlineCtrls('cap')}
                                                                </div>
                                                            )}
                                                            {/* ── Exterior Floor ── */}
                                                            {pillItem('exterior', 'Exterior Floor',
                                                                <span className="w-4 h-4 rounded border border-slate-300 dark:border-slate-600 inline-block ml-2" style={{background:t.exterior||t.outerFloor||'#1e293b'}} />,
                                                                <div className="space-y-3">
                                                                    {texCtrls(t.exteriorTexture||{}, patch=>updateTheme(lvl,t,{exteriorTexture:{...(t.exteriorTexture||{}),...patch}}), false, t.exterior||t.outerFloor||'#1e293b', c=>updateTheme(lvl,t,{exterior:c}))}
                                                                    {outlineCtrls('exterior')}
                                                                </div>
                                                            )}
                                                            {/* ── Perimeter Wall ── */}
                                                            {pillItem('perimeterWall', 'Perimeter Wall',
                                                                <>{t.usePerimeterWall?<div className="flex gap-1 ml-2">{perimWallZones.map((z,i)=><span key={i} className="w-4 h-4 rounded border border-slate-300 dark:border-slate-600 inline-block" style={{background:z.color||'#334155'}} />)}</div>:<span className="text-xs text-slate-400 dark:text-slate-500 ml-2">uses wall</span>}</>,
                                                                <div className="space-y-3">
                                                                    <p className="text-xs text-slate-400 dark:text-slate-500">The outer boundary wall of the maze grid. Relevant for Maze and Columns map types.</p>
                                                                    <label className="flex items-center gap-2 cursor-pointer"><input type="checkbox" checked={!!t.usePerimeterWall} onChange={e=>updateTheme(lvl,t,{usePerimeterWall:e.target.checked})} className="w-4 h-4 text-emerald-500" /><span className="text-sm font-bold text-slate-700 dark:text-slate-300 dark:text-slate-600">Use separate perimeter wall</span></label>
                                                                    {t.usePerimeterWall && <>
                                                                        {perimWallZones.map((z,i)=>wallZoneEditor(z,i,patch=>setPerimZone(i,patch),perimWallZones.length>1?()=>updateTheme(lvl,t,{perimeterWallZones:perimWallZones.filter((_,idx)=>idx!==i)}):null))}
                                                                        <div className="flex items-center gap-3 pt-2 border-t border-slate-200 dark:border-slate-700">
                                                                            <button onClick={()=>updateTheme(lvl,t,{perimeterWallZones:[...perimWallZones,{height:0.5,color:'#ffffff',outlineColor:'#000000',subdivX:1,subdivY:1}]})} className="text-xs bg-emerald-100 text-emerald-700 px-3 py-1.5 rounded font-bold hover:bg-emerald-200">+ Add zone</button>
                                                                            <span className="text-xs text-slate-400 dark:text-slate-500 italic">Renderer support coming soon</span>
                                                                        </div>
                                                                    </>}
                                                                </div>
                                                            )}
                                                            {/* spacer so the last pill isn't flush with the tab bottom */}
                                                            <div className="h-4" />
                                                        </div>
                                                    )}

                                                    {themeSubTab === 'levels' && (
                                                        <div className="space-y-6">
                                                        <div className="p-6 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 grid grid-cols-2 gap-6">
                                                            <div className="col-span-2 border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Grid size</label></div>
                                                            <div>
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1 uppercase">Start size (odd)</label>
                                                                <input type="number" min="7" max="99" step="2" value={lvl.gridSize ?? 21}
                                                                    onChange={e => { const v = parseInt(e.target.value) || 21; updateThemeEntry(lvl, { gridSize: v % 2 === 0 ? v + 1 : Math.max(7, v) }); }}
                                                                    className="w-full border border-slate-200 dark:border-slate-700 rounded p-2 bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                            </div>
                                                            <div>
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1 uppercase">+ Per level (even)</label>
                                                                <input type="number" min="0" max="20" step="2" value={lvl.gridSizeIncrement ?? 0}
                                                                    onChange={e => { const v = parseInt(e.target.value) || 0; updateThemeEntry(lvl, { gridSizeIncrement: v % 2 === 0 ? v : v + 1 }); }}
                                                                    className="w-full border border-slate-200 dark:border-slate-700 rounded p-2 bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                            </div>
                                                            <div className="col-span-2 text-xs text-slate-400 dark:text-slate-500">Level {levelRef.current} size: {(lvl.gridSize ?? 21) + ((levelRef.current - 1) * (lvl.gridSizeIncrement ?? 0))}×{(lvl.gridSize ?? 21) + ((levelRef.current - 1) * (lvl.gridSizeIncrement ?? 0))}</div>
                                                        </div>
                                                        <div className="p-6 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 grid grid-cols-2 gap-6">
                                                            <div className="col-span-2 border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Entity spawning</label></div>
                                                            <div>
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1 uppercase">Density (per 100 cells)</label>
                                                                <input type="number" step="0.1" min="0" value={t.entityDensity ?? 1.0} onChange={e => {
                                                                    const newDensity = parseFloat(e.target.value) || 0;
                                                                    const newTheme = updateTheme(lvl, t, { entityDensity: newDensity });
                                                                    let fc = 0; const ms = engine.current.map.length;
                                                                    for (let y = 1; y < ms-1; y++) for (let x = 1; x < ms-1; x++) if (engine.current.map[y][x] !== 1) fc++;
                                                                    const nc = Math.max(0, Math.floor(fc * ((newDensity) + 4 * (newTheme.entityDensityScaling ?? 0)) / 100));
                                                                    engine.current.entities = spawnEntities(engine.current.map, nc, ms, newTheme, 5, dataRef.current.entities);
                                                                }} className="w-full border border-slate-200 dark:border-slate-700 rounded p-2 bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                            </div>
                                                            <div>
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1 uppercase">Density scaling (+/lvl)</label>
                                                                <input type="number" step="0.01" min="0" value={t.entityDensityScaling ?? 0.12} onChange={e => updateTheme(lvl, t, { entityDensityScaling: parseFloat(e.target.value) || 0 })} className="w-full border border-slate-200 dark:border-slate-700 rounded p-2 bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                            </div>
                                                            <div className="col-span-2 text-xs text-slate-400 dark:text-slate-500">{(() => { const gameSize = (lvl.gridSize ?? 11) + (levelRef.current - 1) * (lvl.gridSizeIncrement ?? 2); const approxFloor = 2 * Math.floor(gameSize / 2) ** 2 - 1; const approxCount = Math.floor(approxFloor * ((t.entityDensity ?? 0) + (levelRef.current - 1) * (t.entityDensityScaling ?? 0)) / 100); return `≈ ${approxCount} entities at level ${levelRef.current} (${gameSize}×${gameSize} maze)`; })()}</div>
                                                            <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1 uppercase">Base speed (tiles/s)</label><input type="number" step="0.1" min="0.1" value={t.entityBaseSpeed ?? 0.6} onChange={e => updateTheme(lvl, t, { entityBaseSpeed: parseFloat(e.target.value) })} className="w-full border border-slate-200 dark:border-slate-700 rounded p-2 bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                            <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1 uppercase">Speed scaling (+/lvl)</label><input type="number" step="0.01" min="0" value={t.entitySpeedScaling ?? 0.1} onChange={e => updateTheme(lvl, t, { entitySpeedScaling: parseFloat(e.target.value) })} className="w-full border border-slate-200 dark:border-slate-700 rounded p-2 bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" /></div>
                                                            <div className="col-span-2 mt-2">
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-3 uppercase">Allowed entities</label>
                                                                <div className="space-y-3 bg-white dark:bg-slate-800 p-4 rounded-xl border border-slate-200 dark:border-slate-700">
                                                                    {gameData.entities.map(ent => {
                                                                        const activeConfig = (t.activeEntities || []).find(ae => ae.id === ent.id);
                                                                        return (
                                                                            <div key={ent.id} className="flex items-center justify-between gap-4 p-2 bg-slate-50 dark:bg-slate-900 border border-slate-100 rounded-lg hover:border-slate-300 dark:border-slate-600 transition-colors">
                                                                                <label className="flex items-center gap-3 cursor-pointer flex-1 py-1 px-2">
                                                                                    <input type="checkbox" className="w-4 h-4 text-emerald-500 rounded cursor-pointer" checked={!!activeConfig} onChange={e => {
                                                                                        let current = [...(t.activeEntities || [])];
                                                                                        if (e.target.checked) current.push({ id: ent.id });
                                                                                        else current = current.filter(ae => ae.id !== ent.id);
                                                                                        const newTheme = updateTheme(lvl, t, { activeEntities: current });
                                                                                        let fc2 = 0; const ms2 = engine.current.map.length;
                                                                                        for (let y = 1; y < ms2-1; y++) for (let x = 1; x < ms2-1; x++) if (engine.current.map[y][x] !== 1) fc2++;
                                                                                        const nc2 = Math.max(1, Math.floor(fc2 * ((newTheme.entityDensity ?? 1) + 4 * (newTheme.entityDensityScaling ?? 0.12)) / 100));
                                                                                        engine.current.entities = spawnEntities(engine.current.map, nc2, ms2, newTheme, 5, dataRef.current.entities);
                                                                                    }} />
                                                                                    <span className="text-sm font-bold">{ent.name}</span>
                                                                                </label>
                                                                            </div>
                                                                        );
                                                                    })}
                                                                </div>
                                                            </div>
                                                        </div>

                                                        </div>
                                                    )}

                                                    {themeSubTab === 'modes' && (
                                                        <div className="space-y-6">
                                                        <div className="p-5 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 space-y-4">
                                                            <div className="border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Movement mode</label></div>
                                                            <div className="flex gap-6">
                                                                {['smooth', 'classic'].map(mode => (
                                                                    <label key={mode} className="flex items-center gap-2 cursor-pointer text-sm">
                                                                        <input type="radio" name={`${lvl.id}_moveMode`} checked={(t.moveMode ?? 'smooth') === mode} onChange={() => updateTheme(lvl, t, { moveMode: mode })} />
                                                                        <span className="capitalize font-medium">{mode}</span>
                                                                    </label>
                                                                ))}
                                                            </div>
                                                            {t.moveMode === 'classic' && (
                                                                <div className="space-y-4 pt-1">
                                                                    <div>
                                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Tick interval — {t.tickInterval ?? 1000}ms</label>
                                                                        <input type="range" min="200" max="2000" step="100" value={t.tickInterval ?? 1000} onChange={e => updateTheme(lvl, t, { tickInterval: parseInt(e.target.value) })} className="w-full" />
                                                                    </div>
                                                                    <div>
                                                                        {(() => { const pct = Math.round((1 - (t.tickSpeedFactor ?? 0.95)) * 100); return (
                                                                            <>
                                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Speed-up per level — {pct === 0 ? 'no speed increase' : `${pct}% faster each level`}</label>
                                                                                <input type="range" min="0" max="20" step="1" value={pct} onChange={e => updateTheme(lvl, t, { tickSpeedFactor: 1 - parseInt(e.target.value) / 100 })} className="w-full" />
                                                                            </>
                                                                        ); })()}
                                                                    </div>
                                                                </div>
                                                            )}
                                                        </div>
                                                        <div className="p-5 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 space-y-4">
                                                            <div className="border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Radar</label></div>
                                                            <div className="flex gap-6">
                                                                {[['on', true], ['off', false]].map(([label, val]) => (
                                                                    <label key={label} className="flex items-center gap-2 cursor-pointer text-sm">
                                                                        <input type="radio" name={`${lvl.id}_showRadar`} checked={(t.showRadar ?? true) === val} onChange={() => updateTheme(lvl, t, { showRadar: val })} />
                                                                        <span className="capitalize font-medium">{label}</span>
                                                                    </label>
                                                                ))}
                                                            </div>
                                                        </div>
                                                        </div>
                                                    )}

                                                    {themeSubTab === 'environment' && (() => {
                                                        const skyMode = t.skyMode ?? 'realistic';
                                                        const SKY_MODES = [
                                                            { id: 'realistic', label: 'Realistic' },
                                                        ];
                                                        return (
                                                        <div className="space-y-5">

                                                        {/* Lighting + render resolution */}
                                                        <div className="p-5 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 grid grid-cols-2 gap-5">
                                                            <div className="col-span-2 border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Lighting & render</label></div>
                                                            <div className="col-span-2">
                                                                <label className="flex items-center gap-2 cursor-pointer text-xs text-slate-500 dark:text-slate-400 dark:text-slate-500 mb-2">
                                                                    <input type="checkbox" checked={t.flatLighting ?? false} onChange={e => updateTheme(lvl, t, { flatLighting: e.target.checked })} />
                                                                    Flat colours (no lighting)
                                                                </label>
                                                            </div>
                                                            <div className={`col-span-2${t.flatLighting ? ' opacity-40 pointer-events-none' : ''}`}>
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Light intensity ({(t.ambientLightIntensity ?? 0.6).toFixed(2)})</label>
                                                                <input type="range" min="0" max="2" step="0.05" value={t.ambientLightIntensity ?? 0.6} onChange={e => updateTheme(lvl, t, { ambientLightIntensity: parseFloat(e.target.value) })} className="w-full mt-2" />
                                                            </div>
                                                            <div className="col-span-2">
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Pixel scale ({(t.pixelScale ?? 1).toFixed(2)} — 1.0=native, 0.25=retro)</label>
                                                                <input type="range" min="0.1" max="1" step="0.05" value={t.pixelScale ?? 1} onChange={e => updateTheme(lvl, t, { pixelScale: parseFloat(e.target.value) })} className="w-full mt-2" />
                                                                {(t.pixelScale ?? 1) < 1 && (
                                                                    <label className="flex items-center gap-2 mt-2 cursor-pointer text-xs text-slate-500 dark:text-slate-400 dark:text-slate-500">
                                                                        <input type="checkbox" checked={t.pixelInterpolation ?? false} onChange={e => updateTheme(lvl, t, { pixelInterpolation: e.target.checked })} />
                                                                        Smooth upscaling (interpolation)
                                                                    </label>
                                                                )}
                                                            </div>
                                                            <div className="col-span-2">
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Sight radius ({t.sightRadius ? `${t.sightRadius} tiles` : 'unlimited'})</label>
                                                                <input type="range" min="0" max="30" step="1" value={t.sightRadius ?? 0} onChange={e => { const v = parseInt(e.target.value); updateTheme(lvl, t, { sightRadius: v === 0 ? null : v }); }} className="w-full mt-2" />
                                                            </div>
                                                        </div>

                                                        {/* Sky mode */}
                                                        <div className="p-5 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 space-y-4">
                                                            <div className="border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Sky</label></div>
                                                            {t.hasCeiling ? (
                                                                <p className="text-sm text-slate-500 dark:text-slate-400 dark:text-slate-500 italic">Sky disabled — ceiling is on (Surfaces tab).</p>
                                                            ) : (<>
                                                                <div className="flex gap-2">
                                                                    {SKY_MODES.map(sm => (
                                                                        <button key={sm.id} onClick={() => updateTheme(lvl, t, { skyMode: sm.id })}
                                                                            className={`flex-1 py-2 px-3 rounded-xl font-bold text-sm transition-colors ${skyMode === sm.id ? 'bg-emerald-500 text-white' : 'bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400 dark:text-slate-500 border border-slate-200 dark:border-slate-700 hover:border-emerald-400'}`}>
                                                                            {sm.label}
                                                                        </button>
                                                                    ))}
                                                                </div>

                                                                {/* Time controls — all sky modes */}
                                                                <div className="grid grid-cols-2 gap-4">
                                                                    <div className="col-span-2">
                                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Time of day ({formatTime(t.timeOfDay ?? 12)})</label>
                                                                        <input type="range" min="0" max="24" step="0.25" value={t.timeOfDay ?? 12} onChange={e => updateTheme(lvl, t, { timeOfDay: parseFloat(e.target.value) })} className="w-full mt-1" />
                                                                    </div>
                                                                    <div className="col-span-2">
                                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Time speed ({(t.timeSpeed ?? 0).toFixed(2)} hrs/sec)</label>
                                                                        <input type="range" min="0" max="5" step="0.05" value={t.timeSpeed ?? 0} onChange={e => updateTheme(lvl, t, { timeSpeed: parseFloat(e.target.value) })} className="w-full mt-1" />
                                                                    </div>
                                                                </div>

                                                                {/* Realistic: sun size + lat + day-of-year */}
                                                                {skyMode === 'realistic' && (
                                                                    <div className="grid grid-cols-2 gap-4 pt-2 border-t border-slate-200 dark:border-slate-700">
                                                                        <div>
                                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Sun size ({(t.sunSize ?? 1.0).toFixed(1)}×)</label>
                                                                            <input type="range" min="0.5" max="3.0" step="0.1" value={t.sunSize ?? 1.0} onChange={e => updateTheme(lvl, t, { sunSize: parseFloat(e.target.value) })} className="w-full mt-1" />
                                                                        </div>
                                                                        <div>
                                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Sun colour</label>
                                                                            <div className="flex items-center gap-2 mt-1">
                                                                                <input type="color" value={t.sunColor || '#ffffff'} onChange={e => updateTheme(lvl, t, { sunColor: e.target.value })} className="w-10 h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                                {t.sunColor && <button onClick={() => updateTheme(lvl, t, { sunColor: '' })} className="text-xs text-slate-400 dark:text-slate-500 hover:text-rose-500">Auto</button>}
                                                                            </div>
                                                                        </div>
                                                                        <div>
                                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Latitude ({(t.latitude ?? 51.5).toFixed(1)}° — S negative)</label>
                                                                            <input type="range" min="-89" max="89" step="0.5" value={t.latitude ?? 51.5} onChange={e => updateTheme(lvl, t, { latitude: parseFloat(e.target.value) })} className="w-full mt-1" />
                                                                        </div>
                                                                        <div>
                                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Day of year ({t.dayOfYear ?? 172} — 172=solstice)</label>
                                                                            <input type="range" min="1" max="365" step="1" value={t.dayOfYear ?? 172} onChange={e => updateTheme(lvl, t, { dayOfYear: parseInt(e.target.value) })} className="w-full mt-1" />
                                                                        </div>
                                                                    </div>
                                                                )}
                                                            </>)}
                                                        </div>

                                                        {/* Stars */}
                                                        {!t.hasCeiling && (
                                                        <div className="p-5 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 grid grid-cols-2 gap-4">
                                                            <div className="col-span-2 border-b border-slate-200 dark:border-slate-700 pb-2 flex items-center justify-between">
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase">Stars</label>
                                                                <label className="flex items-center gap-2 text-xs cursor-pointer">
                                                                    <input type="checkbox" checked={t.showMilkyWay !== false} onChange={e => updateTheme(lvl, t, { showMilkyWay: e.target.checked })} className="w-3.5 h-3.5 text-emerald-500" />
                                                                    <span className="text-slate-500 dark:text-slate-400 dark:text-slate-500 font-bold">Milky Way</span>
                                                                </label>
                                                            </div>
                                                            <div>
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Density ({(t.starDensity ?? 0.5).toFixed(2)})</label>
                                                                <input type="range" min="0" max="1" step="0.05" value={t.starDensity ?? 0.5} onChange={e => updateTheme(lvl, t, { starDensity: parseFloat(e.target.value) })} className="w-full mt-1" />
                                                            </div>
                                                            <div>
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Brightness ({(t.starBrightness ?? 1.0).toFixed(2)})</label>
                                                                <input type="range" min="0" max="1" step="0.05" value={t.starBrightness ?? 1.0} onChange={e => updateTheme(lvl, t, { starBrightness: parseFloat(e.target.value) })} className="w-full mt-1" />
                                                            </div>
                                                            <div>
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Twinkle ({(t.starTwinkle ?? 0.3).toFixed(2)})</label>
                                                                <input type="range" min="0" max="1" step="0.05" value={t.starTwinkle ?? 0.3} onChange={e => updateTheme(lvl, t, { starTwinkle: parseFloat(e.target.value) })} className="w-full mt-1" />
                                                            </div>
                                                            <div>
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Rotation speed ({(t.starRotationSpeed ?? 1.0).toFixed(1)}×)</label>
                                                                <input type="range" min="0" max="10" step="0.1" value={t.starRotationSpeed ?? 1.0} onChange={e => updateTheme(lvl, t, { starRotationSpeed: parseFloat(e.target.value) })} className="w-full mt-1" />
                                                            </div>
                                                            <div>
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Star size ({(t.starSize ?? 1.0).toFixed(1)}×)</label>
                                                                <input type="range" min="1" max="5" step="0.1" value={t.starSize ?? 1.0} onChange={e => updateTheme(lvl, t, { starSize: parseFloat(e.target.value) })} className="w-full mt-1" />
                                                            </div>
                                                        </div>
                                                        )}

                                                        {/* Overlay */}
                                                        <div className="p-5 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 space-y-4">
                                                            <div className="border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Overlay</label></div>
                                                            <canvas ref={overlayPreviewCanvasRef} width={960} height={540} className="w-full rounded-lg" style={{ aspectRatio: '16/9' }} />
                                                            <div className="grid grid-cols-2 gap-4">
                                                                <div>
                                                                    <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Accent colour</label>
                                                                    <div className="flex items-center gap-2">
                                                                        <input type="color" value={t.overlayAccentColor || (t.capColor ?? '#ffffff')} onChange={e => updateTheme(lvl, t, { overlayAccentColor: e.target.value })} className="w-10 h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                        {t.overlayAccentColor && <button onClick={() => updateTheme(lvl, t, { overlayAccentColor: null })} className="text-xs text-slate-400 dark:text-slate-500 hover:text-rose-500">Auto</button>}
                                                                    </div>
                                                                </div>
                                                                <div>
                                                                    <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Text colour</label>
                                                                    <div className="flex items-center gap-2">
                                                                        <input type="color" value={t.overlayTextColor || '#ffffff'} onChange={e => updateTheme(lvl, t, { overlayTextColor: e.target.value })} className="w-10 h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                        {t.overlayTextColor && <button onClick={() => updateTheme(lvl, t, { overlayTextColor: null })} className="text-xs text-slate-400 dark:text-slate-500 hover:text-rose-500">Auto</button>}
                                                                    </div>
                                                                </div>
                                                                <div className="col-span-2">
                                                                    <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Box background colour</label>
                                                                    <div className="flex items-center gap-2">
                                                                        <input type="color" value={t.overlayBgColor || '#1a1a1a'} onChange={e => updateTheme(lvl, t, { overlayBgColor: e.target.value })} className="w-10 h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                        {t.overlayBgColor && <button onClick={() => updateTheme(lvl, t, { overlayBgColor: null })} className="text-xs text-slate-400 dark:text-slate-500 hover:text-rose-500">Auto</button>}
                                                                    </div>
                                                                </div>
                                                            </div>
                                                            <div>
                                                                <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-2">Death transition</label>
                                                                <div className="flex gap-4">
                                                                    {[{value: 'instant', label: 'None'}, {value: 'fade', label: 'Fade'}].map(({value, label}) => (
                                                                        <label key={value} className="flex items-center gap-2 cursor-pointer text-sm">
                                                                            <input type="radio" name={`${lvl.id}_deathMode`} checked={(t.deathMode ?? 'instant') === value} onChange={() => updateTheme(lvl, t, { deathMode: value })} />
                                                                            <span className="font-medium">{label}</span>
                                                                        </label>
                                                                    ))}
                                                                </div>
                                                            </div>
                                                            {(t.deathMode ?? 'instant') === 'fade' && (
                                                                <div className="grid grid-cols-2 gap-4 pt-1 border-t border-slate-200 dark:border-slate-700">
                                                                    <div className="col-span-2">
                                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Fade colour</label>
                                                                        <input type="color" value={t.deathFadeColor || '#cc0000'} onChange={e => updateTheme(lvl, t, { deathFadeColor: e.target.value })} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                    </div>
                                                                    <div>
                                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Fade in ({t.deathFadeInMs ?? 800}ms)</label>
                                                                        <input type="range" min="100" max="2000" step="50" value={t.deathFadeInMs ?? 800} onChange={e => updateTheme(lvl, t, { deathFadeInMs: parseInt(e.target.value) })} className="w-full mt-1" />
                                                                    </div>
                                                                    <div>
                                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Max opacity ({Math.round((t.deathFadeMaxAlpha ?? 0.5) * 100)}%)</label>
                                                                        <input type="range" min="0.1" max="1" step="0.05" value={t.deathFadeMaxAlpha ?? 0.5} onChange={e => updateTheme(lvl, t, { deathFadeMaxAlpha: parseFloat(e.target.value) })} className="w-full mt-1" />
                                                                    </div>
                                                                </div>
                                                            )}
                                                        </div>

                                                        </div>
                                                        );
                                                    })()}

                                                    {themeSubTab === 'player' && (
                                                        <div className="p-6 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 grid grid-cols-2 gap-6">
                                                            <div className="col-span-2 border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Player</label></div>
                                                            <div><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1 uppercase">Map colour</label><input type="color" value={t.player || '#10b981'} onChange={e => updateTheme(lvl, t, { player: e.target.value })} className="w-full h-10 rounded cursor-pointer" /></div>
                                                        </div>
                                                    )}

                                                    {themeSubTab === 'menu' && (() => {
                                                        const OVERLAY_FONTS = [
                                                            { name: 'System', value: 'monospace' },
                                                            { name: 'Press Start 2P', value: 'Press Start 2P' },
                                                            { name: 'VT323', value: 'VT323' },
                                                            { name: 'Silkscreen', value: 'Silkscreen' },
                                                        ];
                                                        const curFont = t.overlayFont ?? 'monospace';
                                                        const curPS = t.overlayPixelScale ?? (t.pixelScale ?? 1);
                                                        return (
                                                        <div className="space-y-5">
                                                            <div className="p-5 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 space-y-3">
                                                                <div className="border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Preview</label></div>
                                                                <canvas ref={overlayPreviewCanvasRef} width={960} height={540} className="w-full rounded-lg" style={{ aspectRatio: '16/9' }} />
                                                            </div>
                                                            <div className="p-5 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 space-y-4">
                                                                <div className="border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Font</label></div>
                                                                <div className="flex flex-wrap gap-2">
                                                                    {OVERLAY_FONTS.map(f => (
                                                                        <button key={f.value} onClick={() => updateTheme(lvl, t, { overlayFont: f.value === 'monospace' ? null : f.value })}
                                                                            style={{ fontFamily: f.value === 'monospace' ? 'monospace' : `'${f.value}'` }}
                                                                            className={`px-3 py-2 rounded-lg text-sm border transition-colors ${curFont === f.value ? 'bg-emerald-500 text-white border-emerald-500' : 'bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-300 dark:text-slate-600 border-slate-200 dark:border-slate-700 hover:border-emerald-400'}`}>
                                                                            {f.name}
                                                                        </button>
                                                                    ))}
                                                                </div>
                                                                <div>
                                                                    <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Pixel scale ({curPS.toFixed(2)}×)</label>
                                                                    <input type="range" min="0.1" max="1" step="0.05" value={curPS} onChange={e => updateTheme(lvl, t, { overlayPixelScale: parseFloat(e.target.value) })} className="w-full mt-1" />
                                                                    <p className="text-xs text-slate-400 dark:text-slate-500 mt-1">Values below 0.3 are clamped at render time</p>
                                                                </div>
                                                            </div>
                                                            <div className="p-5 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 space-y-4">
                                                                <div className="border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Colours</label></div>
                                                                <div className="grid grid-cols-2 gap-4">
                                                                    <div>
                                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Accent</label>
                                                                        <div className="flex items-center gap-2">
                                                                            <input type="color" value={t.overlayAccentColor || (t.capColor ?? '#ffffff')} onChange={e => updateTheme(lvl, t, { overlayAccentColor: e.target.value })} className="w-10 h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                            {t.overlayAccentColor && <button onClick={() => updateTheme(lvl, t, { overlayAccentColor: null })} className="text-xs text-slate-400 dark:text-slate-500 hover:text-rose-500">Auto</button>}
                                                                        </div>
                                                                    </div>
                                                                    <div>
                                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Text</label>
                                                                        <div className="flex items-center gap-2">
                                                                            <input type="color" value={t.overlayTextColor || '#ffffff'} onChange={e => updateTheme(lvl, t, { overlayTextColor: e.target.value })} className="w-10 h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                            {t.overlayTextColor && <button onClick={() => updateTheme(lvl, t, { overlayTextColor: null })} className="text-xs text-slate-400 dark:text-slate-500 hover:text-rose-500">Auto</button>}
                                                                        </div>
                                                                    </div>
                                                                    <div className="col-span-2">
                                                                        <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Box background</label>
                                                                        <div className="flex items-center gap-2">
                                                                            <input type="color" value={t.overlayBgColor || '#1a1a1a'} onChange={e => updateTheme(lvl, t, { overlayBgColor: e.target.value })} className="w-10 h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                            {t.overlayBgColor && <button onClick={() => updateTheme(lvl, t, { overlayBgColor: null })} className="text-xs text-slate-400 dark:text-slate-500 hover:text-rose-500">Auto</button>}
                                                                        </div>
                                                                    </div>
                                                                </div>
                                                            </div>
                                                            <div className="p-5 bg-slate-50 dark:bg-slate-900 rounded-2xl border border-slate-100 space-y-4">
                                                                <div className="border-b border-slate-200 dark:border-slate-700 pb-2"><label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Death Transition</label></div>
                                                                <label className="flex items-center gap-3 cursor-pointer">
                                                                    <input type="checkbox" checked={!!t.deathMessageSet}
                                                                        onChange={e => updateTheme(lvl, t, { deathMessageSet: e.target.checked ? 'rex-run' : null })}
                                                                        className="w-4 h-4 rounded" />
                                                                    <span className="text-sm font-medium">Random death quips</span>
                                                                    {t.deathMessageSet && <span className="text-xs text-slate-400 dark:text-slate-500">({(DEATH_MESSAGES[t.deathMessageSet] ?? []).length} messages)</span>}
                                                                </label>
                                                                <div className="flex gap-4">
                                                                    {[{value: 'instant', label: 'None'}, {value: 'fade', label: 'Fade'}].map(({value, label}) => (
                                                                        <label key={value} className="flex items-center gap-2 cursor-pointer text-sm">
                                                                            <input type="radio" name={`${lvl.id}_deathMode`} checked={(t.deathMode ?? 'instant') === value} onChange={() => updateTheme(lvl, t, { deathMode: value })} />
                                                                            <span className="font-medium">{label}</span>
                                                                        </label>
                                                                    ))}
                                                                </div>
                                                                {(t.deathMode ?? 'instant') === 'fade' && (
                                                                    <div className="grid grid-cols-2 gap-4 pt-1 border-t border-slate-200 dark:border-slate-700">
                                                                        <div className="col-span-2">
                                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Fade colour</label>
                                                                            <input type="color" value={t.deathFadeColor || '#cc0000'} onChange={e => updateTheme(lvl, t, { deathFadeColor: e.target.value })} className="w-full h-8 rounded cursor-pointer bg-transparent dark:bg-slate-900 text-slate-900 dark:text-white" />
                                                                        </div>
                                                                        <div>
                                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Fade in ({t.deathFadeInMs ?? 800}ms)</label>
                                                                            <input type="range" min="100" max="2000" step="50" value={t.deathFadeInMs ?? 800} onChange={e => updateTheme(lvl, t, { deathFadeInMs: parseInt(e.target.value) })} className="w-full mt-1" />
                                                                        </div>
                                                                        <div>
                                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block mb-1">Max opacity ({Math.round((t.deathFadeMaxAlpha ?? 0.5) * 100)}%)</label>
                                                                            <input type="range" min="0.1" max="1" step="0.05" value={t.deathFadeMaxAlpha ?? 0.5} onChange={e => updateTheme(lvl, t, { deathFadeMaxAlpha: parseFloat(e.target.value) })} className="w-full mt-1" />
                                                                        </div>
                                                                    </div>
                                                                )}
                                                            </div>
                                                        </div>
                                                        );
                                                    })()}
                                                </div>
                                            );
                                        })()
                                    )}


                                    {designerTab === 'entities' && gameData.entities.find(e => e.id === editingEntityId) && (
                                        (() => {
                                            const ent = gameData.entities.find(e => e.id === editingEntityId);
                                            return (
                                                <div className="max-w-xl mx-auto space-y-8 pb-12">
                                                    <div className="bg-slate-50 dark:bg-slate-900 p-6 rounded-2xl border border-slate-200 dark:border-slate-700 space-y-6">
                                                        <div className="border-b border-slate-200 dark:border-slate-700 pb-2 mb-4">
                                                            <label className="text-xs font-bold text-slate-400 dark:text-slate-500 block uppercase">Entity Configuration</label>
                                                        </div>

                                                        <EntityEditor entity={ent} onUpdate={updateEntity} entityFolders={entityFolders} />

                                                    </div>
                                                </div>
                                            );
                                        })()
                                    )}
                                </div>
                            </div>
                        </div>
                    )}
                </div>
            )}
            {deleteConfirmItem && (
                <div className="fixed inset-0 bg-slate-900/50 backdrop-blur-sm z-[100] flex items-center justify-center p-4">
                    <div className="bg-white dark:bg-slate-900 rounded-2xl max-w-sm w-full p-6 shadow-2xl border border-slate-200 dark:border-slate-800">
                        <h3 className="text-xl font-bold mb-2">Delete {deleteConfirmItem.type === 'theme' ? 'Theme' : 'Entity'}</h3>
                        <p className="text-slate-500 mb-6">Are you sure you want to delete "{deleteConfirmItem.name}"? This cannot be undone.</p>
                        <div className="flex justify-end gap-3">
                            <button onClick={() => setDeleteConfirmItem(null)} className="px-4 py-2 font-bold text-slate-500 hover:text-slate-700 dark:hover:text-slate-300 transition-colors">Cancel</button>
                            <button onClick={() => {
                                setGameData(prev => {
                                    const next = { ...prev };
                                    if (deleteConfirmItem.type === 'theme') {
                                        next.themes = next.themes.filter(t => t.id !== deleteConfirmItem.id);
                                        if (editingThemeId === deleteConfirmItem.id) setEditingThemeId(next.themes[0]?.id);
                                    } else {
                                        next.entities = next.entities.filter(e => e.id !== deleteConfirmItem.id);
                                        if (editingEntityId === deleteConfirmItem.id) setEditingEntityId(next.entities[0]?.id);
                                    }
                                    return next;
                                });
                                setSavedData(prev => {
                                    const next = { ...prev };
                                    if (deleteConfirmItem.type === 'theme') {
                                        next.themes = next.themes.filter(t => t.id !== deleteConfirmItem.id);
                                    } else {
                                        next.entities = next.entities.filter(e => e.id !== deleteConfirmItem.id);
                                    }
                                    localStorage.setItem('maze_data_v1', JSON.stringify(next));
                                    return next;
                                });
                                setDeleteConfirmItem(null);
                            }} className="px-4 py-2 bg-rose-500 hover:bg-rose-600 text-white font-bold rounded-xl transition-colors">Delete</button>
                        </div>
                    </div>
                </div>
            )}
            
            {activeModal === 'data' && (
                <DataHUD gameData={gameData} setGameData={setGameData} onClose={() => setActiveModal(null)} />
            )}

        </div>
    );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
