/* ============================================================================
   THEME LAB — control panel (React, Studio Kit panel chrome).
   Renders the kit's Tweak* controls (globals from studio-panels.jsx) inside a
   real .twk-panel surface. The kit stylesheet is injected via
   window.StudioKit.Panels.styleCss — the documented path for hosting controls
   OUTSIDE a mounted <TweaksPanel> (an always-open docked panel must not run
   the host edit-mode protocol). The panel is NEUTRAL by design: fixed light
   glass, its own font stack — it never re-skins with the demo tokens, so
   controls don't move or restyle when a preset lands. Never co-load
   preview/tweaks-panel.jsx beside this file.
   State lives in window.ThemeLab (theme-lab/presets.js); this file is UI only.
   ============================================================================ */

const TL = () => window.ThemeLab;

/* Wipe origin: remember the last pointer press as a rect-shim so select-driven
   changes can anchor the circular wipe where the user actually clicked. */
let __tlLastPoint = null;
document.addEventListener('pointerdown', (e) => {
  __tlLastPoint = { x: e.clientX, y: e.clientY };
}, true);
function tlAnchor() {
  if (!__tlLastPoint) return null;
  const { x, y } = __tlLastPoint;
  return { getBoundingClientRect: () => ({ left: x, top: y, width: 0, height: 0 }) };
}

/* Shared option builders for the per-role shape/border selects. */
const tlRadiusRoleOpts = (t) => [
  { value: 'auto', label: t.rAuto }, { value: '0', label: t.rZero },
  { value: '2', label: '2 px' }, { value: '4', label: '4 px' }, { value: '6', label: '6 px' },
  { value: '8', label: '8 px' }, { value: '12', label: '12 px' }, { value: '16', label: '16 px' },
  { value: '999', label: t.rPillOpt },
];
const tlBwRoleOpts = (t) => [
  { value: 'auto', label: t.bwAuto }, { value: 'none', label: t.bwOff },
  { value: '05', label: t.bw05 }, { value: '1', label: '1 px' },
  { value: '2', label: '2 px' }, { value: '3', label: '3 px' },
];
const tlGlassRoleOpts = (t) => [
  { value: 'auto', label: t.gAuto }, { value: 'on', label: t.gOn }, { value: 'off', label: t.gOff },
];

/* ── i18n (panel chrome; scenes handle their own strings) ────────────────── */
const TL_I18N = {
  cs: {
    panel: 'Theme Lab', lang: 'Jazyk stránky', presets: 'Předvolby', themes: 'Motivy (barvy · písmo · značka)',
    randomize: 'Náhodně — promíchá nepřipnuté skupiny', reset: 'Reset na DSGCZ',
    pin: 'Připnout — Náhodně tuto skupinu nezmění', pinned: 'Připnuto — Náhodně skupinu zachová',
    mode: 'Režim', light: 'Světlý', dark: 'Tmavý',
    transition: 'Přechod změn', wipe: 'Vlna', fade: 'Prolnutí', instant: 'Hned',
    colors: 'Barvy', palette: 'Paleta', neutrals: 'Neutrály',
    nCool: 'Chladné (výchozí)', nPure: 'Čistě šedé', nWarm: 'Teplé',
    nSlate: 'Ocelové', nStone: 'Kamenné', nSage: 'Šalvějové', nMauve: 'Fialkové',
    type: 'Písmo', pair: 'Rodina', scale: 'Velikost',
    sCompact: 'Kompaktní', sBase: 'Základní', sLarge: 'Velkorysá',
    shape: 'Tvar', radius: 'Zaoblení',
    rSharp: 'Ostré — 0', rSoft: 'Jemné — 2 px', rBase: 'Výchozí — 4–8 px', rRound: 'Zaoblené — 12 px', rPill: 'Pilulkové',
    space: 'Rozestupy', spTight: 'Těsné', spBase: 'Výchozí', spAiry: 'Vzdušné',
    table: 'Tabulka a chipy', stripe: 'Řádky tabulky', density: 'Hustota', chips: 'Chipy typů',
    stZebra: 'Zebra (výchozí)', stLines: 'Linky', stBare: 'Bez oddělení',
    deCompact: 'Kompaktní', deComfort: 'Pohodlná', deRoomy: 'Volná',
    chFilled: 'Plné', chOutlined: 'Obrysové', chLetter: 'Tečka + text',
    fx: 'Stíny a efekty', shadows: 'Stíny',
    shNone: 'Žádné', shBase: 'Výchozí — ploché', shSoft: 'Měkké', shDrama: 'Výrazné', shGlow: 'Záře',
    glass: 'Sklo — průhlednost + blur',
    motion: 'Animace',
    mNone: 'Bez animací', mFast: 'Rychlé — výchozí', mSmooth: 'Plynulé — 220 ms', mCalm: 'Klidné — 320 ms', mCt: 'ČT — 500 ms', mSpring: 'Pružné',
    buttons: 'Tlačítka', btnStyle: 'Styl tlačítek',
    brand: 'Značka', brandText: 'Název v hlavičce', brandPh: 'např. Knihovna Kroměříž',
    exportS: 'Export motivu', copy: 'Zkopírovat CSS', show: 'Zobrazit CSS',
    icons: 'Ikony', iconSet: 'Sada ikon', iSetB: 'Bootstrap (výchozí)', iSetM: 'Material Symbols', iSetF: 'Font Awesome', iSetE: 'Emoji',
    iconStyle: 'Varianta (Material)', iStOut: 'Obrysové', iStFill: 'Vyplněné', iStRound: 'Zaoblené', iStSharp: 'Ostré',
    iconW: 'Síla (Material)', iwThin: 'Tenké', iwBase: 'Základní', iwHeavy: 'Silné',
    fontMainL: 'Hlavní písmo', fontMonoL: 'Mono písmo', fontCustomL: 'Vlastní Google Font', fontCustomPh: 'např. Sora',
    weightsL: 'Tloušťky', wLight: 'Lehčí', wBase: 'Výchozí', wHeavy: 'Těžší',
    sizeL: 'Velikost prvků', szCompact: 'Kompaktní', szBase: 'Výchozí', szLarge: 'Větší',
    spXTight: 'Velmi těsné', spXAiry: 'Velkorysé',
    borders: 'Ohraničení', borderWL: 'Síla linek', bwNone: 'Bez', bwHairline: 'Vlasové — 0,5 px', bwBase: 'Tenké (výchozí)', bwThick: 'Silné — 2 px', bwHeavy: 'Výrazné — 3 px',
    bwRolePanel: 'Panely', bwRoleCtl: 'Ovládací prvky', bwRoleLine: 'Oddělovací linky',
    bwAuto: 'Auto — dle síly', bwOff: 'Žádné', bw05: '0,5 px',
    fgStyleL: 'Panely filtrů', fgBase: 'Výplň + rámeček (výchozí)', fgOutline: 'Jen rámeček', fgFlat: 'Jen výplň', fgBare: 'Bez obojího', fgTree: 'Strom — konektory',
    variants: 'Varianty komponent', segStyleL: 'Přepínač zobrazení', segFill: 'Výplň (výchozí)', segUnderline: 'Podtržení (taby)',
    pagStyleL: 'Stránkování', pagFrame: 'Rámečky (výchozí)', pagBare: 'Bez rámečků',
    comboHlL: 'Zvýraznění v dropdownu', cbTint: 'Tónování (výchozí)', cbRail: 'Boční linka', cbRounded: 'Zaoblené vložené',
    checkAnimL: 'Animace zaškrtnutí', caBase: 'Okamžité (výchozí)', caFade: 'Prolnutí', caPop: 'Zvětšení', caWipe: 'Rozkrytí',
    linkStyleL: 'Podtržení odkazů', lkBase: 'Standardní (výchozí)', lkDashed: 'Čárkované', lkDotted: 'Tečkované', lkThick: 'Silné', lkTonal: 'Tónované — světlejší linka',
    caDraw: 'Kreslení', tabStyleL: 'Záložky v hlavičce', tabBase: 'Podtržení (výchozí)', tabPills: 'Pilulky', tabBlock: 'Blok',
    fxRoleSurf: 'Sklo — plochy', fxRoleOver: 'Sklo — překryvy', fxRoleCtl: 'Sklo — ovládací prvky', gAuto: 'Auto — dle efektu', gOn: 'Zapnuto', gOff: 'Vypnuto',
    rolesR: 'Zaoblení rolí — přepíší sadu výše',
    rRoleBtn: 'Tlačítka', rRoleCtl: 'Ovládací prvky', rRolePanel: 'Panely', rRoleOverlay: 'Překryvy (menu, dialogy)', rRoleChip: 'Štítky (chipy)',
    rAuto: 'Auto — dle sady', rZero: 'Ostré — 0', rPillOpt: 'Pilulka',
    focus: 'Fokus — zvýraznění', focusWL: 'Síla prstence', fwThin: 'Tenký — 2 px', fwBase: 'Základní — 3 px', fwBold: 'Silný — 4 px',
    focusGapL: 'Mezera', fgapNone: 'Bez mezery', fgapBase: 'Základní — 2 px', fgapWide: 'Volná — 4 px',
    focusGlowL: 'Záře', fglNone: 'Bez záře', fglSoft: 'Jemná', fglStrong: 'Výrazná',
    borderCL: 'Barva linek', bcBase: 'Výchozí', bcStrong: 'Tmavší', bcPrimary: 'Primární', bcContrast: 'Kontrastní',
    revealL: 'Nástup obsahu', rvUp: 'Zdola (výchozí)', rvLeft: 'Zleva', rvZoom: 'Přiblížení', rvFade: 'Prolnutí', rvNone: 'Bez',
    pagefxL: 'Přechod scén', pfRise: 'Zdola (rise)', pfFall: 'Shora (fall)', pfSlip: 'Ze strany (slip)', pfFade: 'Prolnutí (fade)', pfScale: 'Zvětšení (scale)', pfNone: 'Bez',
    fxSelL: 'Efekt skla', fxNone: 'Bez', fxGlass: 'Vzdušné — plochy', fxHeavy: 'Sklo — i ovládací prvky',
    bg: 'Pozadí', bgPageL: 'Stránka', bgBarL: 'Hlavička aplikace', bgPanelsL: 'Panely a karty', bgOverlayL: 'Dialogy a toasty', bgUrlL: 'Adresa obrázku (URL)',
    bgNone: 'Bez (výchozí)', bgWash: 'Nádech — jemný přeliv', bgAurora: 'Aurora — barevné závoje', bgDots: 'Tečky', bgGrid: 'Mřížka', bgHatch: 'Šrafování', bgPaper: 'Papír — jemné zrno', bgMist: 'Mlha — měkké skvrny', bgImage: 'Vlastní obrázek (URL)…',
    bgBarBase: 'Dle presetu (výchozí)', bgBarTint: 'Tónovaná', bgBarGrad: 'Gradient značky', bgBarAurora: 'Aurora',
    bgPanBase: 'Výchozí', bgPanTint: 'Tónované', bgPanWash: 'Přeliv',
    bgOvBase: 'Výchozí', bgOvWash: 'Přeliv', bgOvAurora: 'Aurora',
    fontDispL: 'Titulkové písmo', dispReachL: 'Dosah titulků', reachH1: 'H1 + logotyp (výchozí)', reachH2: '+ nadpisy sekcí (H2)', reachSub: '+ podtitulky a eyebrow',
    userS: 'Uživatelské předvolby', upEmpty: 'Zatím žádné — nalaďte vzhled a uložte si ho.', upNameL: 'Název', upNamePh: 'např. Můj motiv',
    upSave: 'Uložit', upDl: 'Stáhnout .json', upLink: 'Kopírovat odkaz', upJson: 'Kopírovat JSON',
    upImportL: 'Import', upImportPh: 'Vložte JSON motivu…', upImport: 'Importovat', upFile: 'Ze souboru…', upDel: 'Smazat předvolbu',
    upSavedT: 'Předvolba uložena', upLinkT: 'Odkaz zkopírován', upLinkSub: 'Odkaz nese celý motiv — stačí poslat.', upJsonT: 'JSON motivu zkopírován',
    upImportOkT: 'Motiv importován', upImportOkSub: 'Uložte si ho jako předvolbu.', upImportErrT: 'Import se nezdařil', upImportErrSub: 'Vložte JSON vyexportovaný z Theme Labu.', upNameNeedT: 'Zadejte název předvolby',
    copied: 'CSS motivu zkopírováno', copiedSub: 'Vložte do stylu za styles.css — nic dalšího není potřeba.',
    cssTitle: 'CSS aktuálního motivu', cssEyebrow: 'Export', close: 'Zavřít',
    expert: 'Expert', expertTip: 'Zobrazit všechna nastavení', secCollapse: 'Sbalit sekci', secExpand: 'Rozbalit sekci',
    secAllCollapse: 'Sbalit všechny sekce', secAllExpand: 'Rozbalit všechny sekce',
    searchPh: 'Hledat nastavení…', searchClear: 'Vymazat hledání',
    searchPrev: 'Předchozí shoda', searchNext: 'Další shoda (Enter)',
    searchShowAll: 'Zobrazit i neodpovídající (ztlumeně)', searchHideAll: 'Skrýt neodpovídající',
    pinAllOn: 'Připnout všechny skupiny — Náhodně pak nic nezmění', pinSomeTip: 'Připnuto {n} z {m} — kliknutím připnete vše', pinAllOff: 'Odepnout všechny skupiny',
  },
  en: {
    panel: 'Theme Lab', lang: 'Page language', presets: 'Presets', themes: 'Themes (colors · type · brand)',
    randomize: 'Randomize — shuffles unpinned groups', reset: 'Reset to DSGCZ',
    pin: 'Pin — Randomize won\u2019t change this group', pinned: 'Pinned — Randomize keeps this group',
    mode: 'Mode', light: 'Light', dark: 'Dark',
    transition: 'Change transition', wipe: 'Wipe', fade: 'Crossfade', instant: 'Instant',
    colors: 'Colors', palette: 'Palette', neutrals: 'Neutrals',
    nCool: 'Cool (default)', nPure: 'Pure gray', nWarm: 'Warm',
    nSlate: 'Slate', nStone: 'Stone', nSage: 'Sage', nMauve: 'Mauve',
    type: 'Type', pair: 'Family', scale: 'Scale',
    sCompact: 'Compact', sBase: 'Base', sLarge: 'Large',
    shape: 'Shape', radius: 'Corner radius',
    rSharp: 'Sharp — 0', rSoft: 'Subtle — 2 px', rBase: 'Default — 4–8 px', rRound: 'Rounded — 12 px', rPill: 'Pill',
    space: 'Spacing', spTight: 'Tight', spBase: 'Default', spAiry: 'Airy',
    table: 'Table & chips', stripe: 'Table rows', density: 'Density', chips: 'Type chips',
    stZebra: 'Zebra (default)', stLines: 'Lines', stBare: 'Bare',
    deCompact: 'Compact', deComfort: 'Comfortable', deRoomy: 'Roomy',
    chFilled: 'Filled', chOutlined: 'Outlined', chLetter: 'Dot + text',
    fx: 'Elevation & effects', shadows: 'Shadows',
    shNone: 'None', shBase: 'Default — flat', shSoft: 'Soft', shDrama: 'Dramatic', shGlow: 'Glow',
    glass: 'Glass — translucency + blur',
    motion: 'Motion',
    mNone: 'No animation', mFast: 'Fast — default', mSmooth: 'Smooth — 220 ms', mCalm: 'Calm — 320 ms', mCt: 'ČT — 500 ms', mSpring: 'Springy',
    buttons: 'Buttons', btnStyle: 'Button style',
    brand: 'Brand', brandText: 'Header wordmark', brandPh: 'e.g. Kroměříž Library',
    exportS: 'Theme export', copy: 'Copy CSS', show: 'Show CSS',
    icons: 'Icons', iconSet: 'Icon set', iSetB: 'Bootstrap (default)', iSetM: 'Material Symbols', iSetF: 'Font Awesome', iSetE: 'Emoji',
    iconStyle: 'Variant (Material)', iStOut: 'Outlined', iStFill: 'Filled', iStRound: 'Rounded', iStSharp: 'Sharp',
    iconW: 'Weight (Material)', iwThin: 'Thin', iwBase: 'Base', iwHeavy: 'Heavy',
    fontMainL: 'Main font', fontMonoL: 'Mono font', fontCustomL: 'Custom Google Font', fontCustomPh: 'e.g. Sora',
    weightsL: 'Weights', wLight: 'Lighter', wBase: 'Default', wHeavy: 'Heavier',
    sizeL: 'Element size', szCompact: 'Compact', szBase: 'Default', szLarge: 'Larger',
    spXTight: 'Very tight', spXAiry: 'Spacious',
    borders: 'Borders', borderWL: 'Thickness', bwNone: 'None', bwHairline: 'Hairline — 0.5 px', bwBase: 'Thin (default)', bwThick: 'Thick — 2 px', bwHeavy: 'Heavy — 3 px',
    bwRolePanel: 'Panels', bwRoleCtl: 'Controls', bwRoleLine: 'Separator lines',
    bwAuto: 'Auto — from thickness', bwOff: 'None', bw05: '0.5 px',
    fgStyleL: 'Filter panels', fgBase: 'Fill + border (default)', fgOutline: 'Border only', fgFlat: 'Fill only', fgBare: 'Neither', fgTree: 'Tree — connectors',
    variants: 'Component variants', segStyleL: 'View switch', segFill: 'Fill (default)', segUnderline: 'Underline (tabs)',
    pagStyleL: 'Pagination', pagFrame: 'Framed (default)', pagBare: 'Borderless',
    comboHlL: 'Dropdown highlight', cbTint: 'Tint (default)', cbRail: 'Left rail', cbRounded: 'Rounded inset',
    checkAnimL: 'Check animation', caBase: 'Instant (default)', caFade: 'Fade', caPop: 'Pop', caWipe: 'Wipe',
    linkStyleL: 'Link underline', lkBase: 'Standard (default)', lkDashed: 'Dashed', lkDotted: 'Dotted', lkThick: 'Thick', lkTonal: 'Tonal — lighter line',
    caDraw: 'Draw', tabStyleL: 'Header tabs', tabBase: 'Underline (default)', tabPills: 'Pills', tabBlock: 'Block',
    fxRoleSurf: 'Glass — surfaces', fxRoleOver: 'Glass — overlays', fxRoleCtl: 'Glass — controls', gAuto: 'Auto — from effect', gOn: 'On', gOff: 'Off',
    rolesR: 'Role radii — override the set above',
    rRoleBtn: 'Buttons', rRoleCtl: 'Controls', rRolePanel: 'Panels', rRoleOverlay: 'Overlays (menus, dialogs)', rRoleChip: 'Chips',
    rAuto: 'Auto — from set', rZero: 'Sharp — 0', rPillOpt: 'Pill',
    focus: 'Focus ring', focusWL: 'Ring thickness', fwThin: 'Thin — 2 px', fwBase: 'Base — 3 px', fwBold: 'Bold — 4 px',
    focusGapL: 'Gap', fgapNone: 'No gap', fgapBase: 'Base — 2 px', fgapWide: 'Wide — 4 px',
    focusGlowL: 'Glow', fglNone: 'No glow', fglSoft: 'Soft', fglStrong: 'Strong',
    borderCL: 'Border color', bcBase: 'Default', bcStrong: 'Stronger', bcPrimary: 'Primary', bcContrast: 'Contrast',
    revealL: 'Content reveal', rvUp: 'From below (default)', rvLeft: 'From left', rvZoom: 'Zoom in', rvFade: 'Fade', rvNone: 'None',
    pagefxL: 'Scene transition', pfRise: 'Rise', pfFall: 'Fall', pfSlip: 'Slip', pfFade: 'Fade', pfScale: 'Scale', pfNone: 'None',
    fxSelL: 'Glass effect', fxNone: 'None', fxGlass: 'Airy — surfaces', fxHeavy: 'Glass — controls too',
    bg: 'Backgrounds', bgPageL: 'Page', bgBarL: 'App header', bgPanelsL: 'Panels & cards', bgOverlayL: 'Dialogs & toasts', bgUrlL: 'Image URL',
    bgNone: 'None (default)', bgWash: 'Wash — subtle gradient', bgAurora: 'Aurora — color veils', bgDots: 'Dots', bgGrid: 'Grid', bgHatch: 'Hatch', bgPaper: 'Paper — fine grain', bgMist: 'Mist — soft blobs', bgImage: 'Custom image (URL)…',
    bgBarBase: 'Preset default', bgBarTint: 'Tinted', bgBarGrad: 'Brand gradient', bgBarAurora: 'Aurora',
    bgPanBase: 'Default', bgPanTint: 'Tinted', bgPanWash: 'Wash',
    bgOvBase: 'Default', bgOvWash: 'Wash', bgOvAurora: 'Aurora',
    fontDispL: 'Display font', dispReachL: 'Display-font reach', reachH1: 'H1 + wordmark (default)', reachH2: '+ section headings (H2)', reachSub: '+ subtitles & eyebrow',
    userS: 'User presets', upEmpty: 'None yet — tune a look and save it.', upNameL: 'Name', upNamePh: 'e.g. My theme',
    upSave: 'Save', upDl: 'Download .json', upLink: 'Copy link', upJson: 'Copy JSON',
    upImportL: 'Import', upImportPh: 'Paste theme JSON…', upImport: 'Import', upFile: 'From file…', upDel: 'Delete preset',
    upSavedT: 'Preset saved', upLinkT: 'Link copied', upLinkSub: 'The link carries the whole theme.', upJsonT: 'Theme JSON copied',
    upImportOkT: 'Theme imported', upImportOkSub: 'Save it as a preset to keep it.', upImportErrT: 'Import failed', upImportErrSub: 'Paste JSON exported from the Theme Lab.', upNameNeedT: 'Enter a preset name',
    copied: 'Theme CSS copied', copiedSub: 'Paste it after styles.css — nothing else is needed.',
    cssTitle: 'Current theme CSS', cssEyebrow: 'Export', close: 'Close',
    expert: 'Expert', expertTip: 'Show all settings', secCollapse: 'Collapse section', secExpand: 'Expand section',
    secAllCollapse: 'Collapse all sections', secAllExpand: 'Expand all sections',
    searchPh: 'Search settings…', searchClear: 'Clear search',
    searchPrev: 'Previous match', searchNext: 'Next match (Enter)',
    searchShowAll: 'Show non-matching (dimmed)', searchHideAll: 'Hide non-matching',
    pinAllOn: 'Pin all groups — Randomize will change nothing', pinSomeTip: 'Pinned {n} of {m} — click to pin all', pinAllOff: 'Unpin all groups',
  },
};

/* Additions ON TOP of the kit stylesheet: preset pills, palette swatches and
   the group-pin button — controls the kit doesn't ship. Same fixed-light
   vocabulary and literals as the kit surface (deliberately NOT DS tokens, so
   the panel never re-skins with the demo). */
const tlPanelExtraStyle = `
  .tlp-pills { display: flex; flex-wrap: wrap; gap: 5px; }
  .tlp-pill { appearance: none; border: .5px solid rgba(0,0,0,.12); background: rgba(255,255,255,.6);
    color: inherit; font: inherit; font-weight: 500; border-radius: 999px; padding: 4px 10px; cursor: default;
    transition: background 120ms, border-color 120ms; }
  .tlp-pill:hover { background: rgba(255,255,255,.95); border-color: rgba(0,0,0,.25); }
  .tlp-pill[data-on="1"] { background: rgba(41,38,27,.88); color: #fff; border-color: rgba(41,38,27,.88); }
  .tlp-pill .bi { font-size: 12px; line-height: 1; }
  .tlp-sw-row { display: flex; flex-wrap: wrap; gap: 6px; }
  .tlp-sw { appearance: none; width: 24px; height: 24px; border-radius: 7px; cursor: default; padding: 0;
    border: .5px solid rgba(0,0,0,.18); box-shadow: inset 0 0 0 1.5px rgba(255,255,255,.55); }
  .tlp-sw[data-on="1"] { outline: 2px solid rgba(41,38,27,.85); outline-offset: 1.5px; }
  .tlp-pinrow { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
  .tlp-pin { appearance: none; border: .5px solid rgba(0,0,0,.12); background: rgba(255,255,255,.55);
    color: rgba(41,38,27,.55); width: 20px; height: 20px; border-radius: 6px; cursor: default;
    display: inline-flex; align-items: center; justify-content: center; font-size: 11px; padding: 0; flex: none; }
  .tlp-pin:hover { border-color: rgba(0,0,0,.28); color: rgba(41,38,27,.9); }
  .tlp-pin.on { background: rgba(41,38,27,.88); border-color: rgba(41,38,27,.88); color: #fff; }
  .tlp-actions { display: flex; gap: 6px; }
  .tlp-actions .twk-btn { flex: 1; }
  .tlp-pill--user { display: inline-flex; align-items: center; gap: 6px; }
  .tlp-x { appearance: none; border: 0; background: transparent; color: inherit; opacity: .55; padding: 0;
    width: 15px; height: 15px; display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%; font-size: 12px; cursor: default; }
  .tlp-x:hover { opacity: 1; background: rgba(0,0,0,.14); }
  .tlp-pill[data-on="1"] .tlp-x:hover { background: rgba(255,255,255,.25); }
  .tlp-note { font-size: 11.5px; color: rgba(41,38,27,.6); }
  /* Basic/expert mode + collapsible sections (expert only). Collapse mechanics
     reuse the kit's .twk-sect-collapse / .twk-sect-body rules (grid-rows 0fr↔1fr
     + overflow-visible-after-open) from the injected kit stylesheet. */
  .tlp-expert { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 500;
    color: rgba(41,38,27,.6); cursor: default; user-select: none; }
  .tlp-expert:hover { color: rgba(41,38,27,.9); }
  .tlp-expert input { appearance: none; width: 13px; height: 13px; margin: 0; border-radius: 4px;
    border: .5px solid rgba(0,0,0,.2); background: rgba(255,255,255,.6);
    display: inline-grid; place-items: center; transition: background .12s, border-color .12s; }
  /* Checked glyph = a real ✓ (CSS-drawn rotated L), not an ambiguous inner square. */
  .tlp-expert input::before { content: ''; width: 8px; height: 4.5px; margin-top: -2px;
    border-left: 1.5px solid #fff; border-bottom: 1.5px solid #fff;
    transform: rotate(-45deg) scale(0); transform-origin: 50% 60%;
    transition: transform .15s cubic-bezier(.3,.7,.4,1); }
  .tlp-expert:hover input:not(:checked) { border-color: rgba(0,0,0,.35); background: rgba(255,255,255,.9); }
  .tlp-expert input:checked { background: rgba(41,38,27,.88); border-color: rgba(41,38,27,.88); }
  .tlp-expert input:checked::before { transform: rotate(-45deg) scale(1); }
  /* The kit's panel-wide focus rule (.twk-panel :is(button,input,…):focus-
     visible) hard-sets border-radius:7px — a CIRCLE on this 13px box, and a
     squared-off shape on the 999px pills. Pin the controls' own radii back
     (the kit's own .twk-slider/.twk-toggle pattern; ≥ its specificity). */
  .twk-panel .tlp-expert input:focus-visible { border-radius: 4px; }
  .twk-panel .tlp-pill:focus-visible { border-radius: 999px; }
  .tlp-hd-tools { display: flex; align-items: center; gap: 2px; }
  .tlp-hd-tools .tlp-expert { margin-left: 6px; }
  .tlp-hbtn { appearance: none; border: 0; background: transparent; color: rgba(41,38,27,.5);
    width: 22px; height: 22px; border-radius: 6px; padding: 0; flex: none;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 13px; line-height: 1; cursor: default; transition: color .12s, background .12s; }
  .tlp-hbtn:hover { color: #29261b; background: rgba(0,0,0,.06); }
  /* Expert search — sticky over the scrolling body; filtering classes below. */
  .tlp-search { position: sticky; top: -6px; z-index: 6; display: flex; align-items: center; gap: 2px;
    margin: -6px -14px 0; padding: 6px 14px 8px;
    background: rgba(252,251,249,.95); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); }
  .tlp-search-box { position: relative; flex: 1 1 auto; min-width: 0; display: flex; align-items: center; }
  .tlp-search-box .tlp-search-ic { position: absolute; left: 8px; font-size: 11px;
    color: rgba(41,38,27,.5); pointer-events: none; transition: color .12s; }
  .tlp-search:focus-within .tlp-search-ic { color: rgba(41,38,27,.8); }
  .tlp-search-box input { padding-left: 26px; padding-right: 62px; }
  .tlp-search-box .tlp-search-x { position: absolute; right: 2px; width: 18px; height: 18px; font-size: 11px; }
  .tlp-search-ct { position: absolute; right: 22px; min-width: 15px; padding: 0 4px; text-align: center;
    border-radius: 999px; background: rgba(41,38,27,.85); color: #fff;
    font-size: 9px; font-weight: 600; line-height: 14px; pointer-events: none; }
  .tlp-kbd { position: absolute; right: 6px; font-size: 9px; line-height: 1; color: rgba(41,38,27,.45);
    border: .5px solid rgba(0,0,0,.16); border-radius: 4px; padding: 2px 4px;
    background: rgba(255,255,255,.55); pointer-events: none; }
  /* In-field overlays ride ABOVE the focused input — the kit's panel focus
     rule lifts a focused control to position:relative + z-index:2, which
     painted the field's near-opaque fill over the icon/count/× and made the
     × unclickable. Only the shortcut hint dims deliberately while typing. */
  .tlp-search-box :is(.tlp-search-ic, .tlp-search-x, .tlp-search-ct, .tlp-kbd) { z-index: 3; }
  .tlp-search-box:focus-within .tlp-kbd { opacity: .45; }
  .tlp-search-nav { width: 20px; height: 20px; font-size: 12px; }
  .tlp-hbtn[disabled] { opacity: .35; pointer-events: none; }
  /* Doubled class beats the LATER .tlp-sec{display:flex} rule (same sheet,
     order trap) — without it “hidden” sections kept their headers. */
  .tlp-sec.tlp-sec--nohit { display: none; }
  .tlp-sec--dim { opacity: .35; }
  .tlp-row-dim { opacity: .32; }
  .tlp-row-hide { display: none !important; }
  .tlp-hit-active { outline: 2px solid rgba(41,38,27,.4); outline-offset: 3px; border-radius: 6px; }
  .tlp-sec-ct { display: inline-block; margin-left: 6px; min-width: 15px; padding: 0 4px; text-align: center;
    border-radius: 999px; background: rgba(41,38,27,.85); color: #fff;
    font-size: 9px; font-weight: 600; line-height: 14px; letter-spacing: 0; vertical-align: 1px; }
  .tlp-pill[data-pinst="some"] .bi { color: rgba(41,38,27,.95); }
  .tlp-sec { display: flex; flex-direction: column; transition: opacity .16s ease; }
  /* Drag-reorder — the header is the drag handle (kit engine; chevron + pin
     excluded via data-twk-noreorder). The fold-during-drag, user-select and
     during-drag cursor come from the kit's .twk-reordering rules; here: the
     resting move cursor, the floating chip, and the recede gate below. */
  .tlp-sec-hd[data-drag="1"] { cursor: move; touch-action: pan-y; }
  .tlp-sec.twk-drag-src { position: relative; z-index: 5; background: rgba(252,251,249,.96);
    border-radius: 8px; box-shadow: 0 6px 18px rgba(0,0,0,.16), 0 0 0 .5px rgba(0,0,0,.08);
    padding: 2px 8px; margin: -2px -8px; }
  .tlp-sec.twk-drag-src .tlp-sec-hd .twk-sect { color: rgba(41,38,27,.95); font-weight: 700; }
  .tlp-sec.twk-drag-src .tlp-sec-sep { border-color: rgba(41,38,27,.4); }
  .tlp-sec-hd { display: flex; align-items: center; gap: 7px; justify-content: space-between;
    user-select: none; -webkit-user-select: none; }
  .tlp-sec-hd .twk-sect { padding: 0; flex: none; transition: color .14s; }
  .tlp-sec-sep { flex: 1 1 auto; border-top: .5px solid rgba(41,38,27,.15); transition: border-color .14s; }
  .tlp-chev { appearance: none; border: 0; background: transparent; color: rgba(41,38,27,.45);
    width: 20px; height: 20px; border-radius: 6px; padding: 0; flex: none;
    display: inline-flex; align-items: center; justify-content: center; cursor: default;
    transition: color .12s, background .12s; }
  .tlp-chev:hover { color: rgba(41,38,27,.9); background: rgba(41,38,27,.06); }
  .tlp-chev svg { transition: transform .22s cubic-bezier(.3,.7,.4,1); }
  .tlp-chev[aria-expanded="false"] svg { transform: rotate(-90deg); }
  /* Header contrast lifts on hover ANYWHERE in the section (header or controls)
     or when focus is inside it. */
  .tlp-sec:hover .tlp-sec-hd .twk-sect, .tlp-sec:focus-within .tlp-sec-hd .twk-sect { color: rgba(41,38,27,.95); font-weight: 700; }
  .tlp-sec:hover .tlp-sec-sep, .tlp-sec:focus-within .tlp-sec-sep { border-color: rgba(41,38,27,.4); }
  .tlp-sec:hover .tlp-chev, .tlp-sec:focus-within .tlp-chev { color: rgba(41,38,27,.75); }
  /* While one section is active (hover or focus inside), the others recede —
     but NOT while a search query filters the panel: there the match/non-match
     opacity split owns the hierarchy (matches stay full-strength even when
     hovering another section; only .tlp-sec--dim/.tlp-row-dim recede). The
     hover CONTRAST lift above still applies during search. Also suppressed
     while a header drag reorders sections (.twk-reordering). */
  .twk-body:not(.tlp-searching):not(.twk-reordering):has(.tlp-sec:is(:hover, :focus-within)) .tlp-sec:not(:hover):not(:focus-within) { opacity: .68; }
`;

function TlSection({ id, label, group, pins, togglePin, t, expert, secs, toggleSec, drag, children }) {
  const open = expert ? !secs[id] : true;
  /* Collapsed controls stay in the DOM (height 0, clipped) — inert removes them
     from focus order + the a11y tree (DOM property, kit TweakSection pattern).
     Basic mode renders the SAME collapse wrapper (just always open, no chevron)
     so the drag-reorder's temporary fold works in both modes. */
  const bodyRef = React.useRef(null);
  React.useEffect(() => { if (bodyRef.current) bodyRef.current.inert = !open; }, [open]);
  const pin = group ? (
    <button type="button" className={pins[group] ? 'tlp-pin on' : 'tlp-pin'} aria-pressed={!!pins[group]}
      data-tip={pins[group] ? t.pinned : t.pin} aria-label={t.pin} data-twk-noreorder=""
      onClick={(e) => { e.stopPropagation(); togglePin(group); }}>
      <i className={pins[group] ? 'bi bi-pin-fill' : 'bi bi-pin-angle'}></i>
    </button>
  ) : null;
  /* The header doubles as the drag handle (kit reorder engine — see
     useTwkSectionReorder in studio-panels.jsx). Chevron + pin opt out via
     data-twk-noreorder; data-drag flags the move cursor. */
  const dragProps = drag ? { onPointerDown: drag.beginDrag(id), 'data-drag': '1' } : {};
  return (
    <section className="tlp-sec" data-sec={id} data-twk-sec={id}>
      {expert ? (
        <div className="tlp-sec-hd" onClick={() => toggleSec(id)} {...dragProps}>
          <div className="twk-sect">{label}</div>
          <span className="tlp-sec-sep" aria-hidden="true"></span>
          <button type="button" className="tlp-chev" aria-expanded={open} data-twk-noreorder=""
            aria-label={open ? t.secCollapse : t.secExpand} data-tip={open ? t.secCollapse : t.secExpand}
            onClick={(e) => { e.stopPropagation(); toggleSec(id); }}>
            <svg width="9" height="6" viewBox="0 0 10 6" aria-hidden="true">
              <path d="M1 1l4 4 4-4" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"></path>
            </svg>
          </button>
          {pin}
        </div>
      ) : (
        <div className="tlp-sec-hd" {...dragProps}>
          <div className="twk-sect">{label}</div>
          {pin}
        </div>
      )}
      <div className="twk-sect-collapse" data-open={open ? '1' : '0'}>
        <div className="twk-sect-body" ref={bodyRef} aria-hidden={!open}>{children}</div>
      </div>
    </section>
  );
}

/* Render the sections in the persisted custom order (kit reorder engine).
   Children are the TlSections in their NATURAL order (expert-only ones already
   nulled out); ids come from props.id. */
function TlOrdered({ order, children }) {
  const byId = new Map();
  React.Children.toArray(children).forEach((k) => {
    if (k && k.props && k.props.id) byId.set(k.props.id, k);
  });
  return order.map((id) => (byId.has(id) ? <React.Fragment key={id}>{byId.get(id)}</React.Fragment> : null));
}

function ThemeLabPanel() {
  const [s, setS] = React.useState(() => ({ ...TL().state }));
  const [lang, setLangState] = React.useState(() => {
    try { return localStorage.getItem('theme-lab-lang') || (new URLSearchParams(location.search).get('lang') === 'en' ? 'en' : 'cs'); } catch (e) { return 'cs'; }
  });
  const [pins, setPins] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('theme-lab-pins') || '{}'); } catch (e) { return {}; }
  });
  const [ups, setUps] = React.useState(() => (TL().user ? TL().user.list() : []));
  const [upName, setUpName] = React.useState('');
  const [upImport, setUpImport] = React.useState('');
  const upFileRef = React.useRef(null);
  const [expert, setExpert] = React.useState(() => {
    try { return localStorage.getItem('theme-lab-expert') === '1'; } catch (e) { return false; }
  });
  const [secs, setSecs] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('theme-lab-sections') || '{}'); } catch (e) { return {}; }
  });
  const [query, setQuery] = React.useState('');
  const [hideNonMatch, setHideNonMatch] = React.useState(() => {
    try { return localStorage.getItem('theme-lab-search-hide') !== '0'; } catch (e) { return true; }
  });
  const [hitCount, setHitCount] = React.useState(0);
  const [hitIdx, setHitIdx] = React.useState(-1);
  const hitsRef = React.useRef([]);
  const lastQueryRef = React.useRef('');
  const panelRef = React.useRef(null);
  const toggleHideMode = () => setHideNonMatch((v) => {
    const n = !v;
    try { localStorage.setItem('theme-lab-search-hide', n ? '1' : '0'); } catch (e) {}
    return n;
  });
  const scrollToEl = (el) => {
    const body = panelRef.current && panelRef.current.querySelector('.twk-body');
    if (!body || !el) return;
    const bt = body.getBoundingClientRect(), et = el.getBoundingClientRect();
    const top = Math.max(0, body.scrollTop + (et.top - bt.top) - 52);
    const rm = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    body.scrollTo({ top: top, behavior: rm ? 'auto' : 'smooth' });
  };
  const navHit = (dir) => {
    const n = hitsRef.current.length;
    if (!n) return;
    setHitIdx((cur) => (cur < 0 ? (dir > 0 ? 0 : n - 1) : (cur + dir + n) % n));
  };
  const onSearchKey = (e) => {
    if (e.key === 'Escape' && query) { e.stopPropagation(); setQuery(''); return; }
    if (e.key === 'Enter') { e.preventDefault(); navHit(e.shiftKey ? -1 : 1); }
    else if (e.key === 'ArrowDown') { e.preventDefault(); navHit(1); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); navHit(-1); }
  };
  const setExpertMode = (v) => {
    setExpert(v);
    if (!v) setQuery('');
    try { localStorage.setItem('theme-lab-expert', v ? '1' : '0'); } catch (e) {}
  };
  const toggleSec = (id) => setSecs((m) => {
    const next = { ...m, [id]: !m[id] };
    try { localStorage.setItem('theme-lab-sections', JSON.stringify(next)); } catch (e) {}
    return next;
  });
  const TL_SECTION_IDS = ['presets', 'themes', 'user', 'mode', 'colors', 'bg', 'type', 'shape', 'space', 'borders', 'variants', 'focus', 'table', 'fx', 'motion', 'buttons', 'icons', 'brand', 'export'];
  const setAllSecs = (collapsed) => {
    const next = {};
    if (collapsed) TL_SECTION_IDS.forEach((id) => { next[id] = true; });
    setSecs(next);
    try { localStorage.setItem('theme-lab-sections', JSON.stringify(next)); } catch (e) {}
  };
  const anyCollapsed = TL_SECTION_IDS.some((id) => secs[id]);

  /* Section drag-reorder — the kit engine (useTwkSectionReorder from
     studio-panels.jsx; persists twk-order:theme-lab). Works in BOTH modes
     (basic re-sequences its visible subset; hidden sections keep their slots
     via the engine's merge-commit). Disabled while a search query filters the
     panel — sections are force-opened there and re-slotting would fight it. */
  const TL_EXPERT_ONLY = ['user', 'borders', 'variants', 'focus', 'table'];
  const visibleSecIds = expert ? TL_SECTION_IDS : TL_SECTION_IDS.filter((id) => TL_EXPERT_ONLY.indexOf(id) === -1);
  const reorder = useTwkSectionReorder({ ns: 'theme-lab', ids: visibleSecIds, enabled: !query.trim() });

  /* Global pin (presets row): none → pin all · some → pin all · all → unpin all. */
  const pinGroups = Object.keys(TL().PIN_GROUPS || {});
  const pinnedCount = pinGroups.filter((g) => pins[g]).length;
  const pinState = pinnedCount === 0 ? 'none' : (pinnedCount === pinGroups.length ? 'all' : 'some');
  const setAllPins = () => {
    const next = {};
    if (pinState !== 'all') pinGroups.forEach((g) => { next[g] = true; });
    setPins(next);
    try { localStorage.setItem('theme-lab-pins', JSON.stringify(next)); } catch (e) {}
  };

  /* Expert search — DOM pass over the rendered sections: fold-aware (Chips.fold
     when present, so „pismo“ finds „Písmo“), section-label OR row-text match;
     the eye toggle picks HIDE (non-matching rows/sections removed) vs DIM
     (kept at low opacity, collapse state respected). Matching sections force-
     open (data-open written directly — the effect re-runs after every render,
     so React re-renders can't leave a matched section closed) and get an
     inverted match-count pill injected next to their name. Clearing restores
     the React-owned collapse state. */
  React.useEffect(() => {
    const root = panelRef.current;
    if (!root) return;
    const fold = (x) => (window.Chips && window.Chips.fold ? window.Chips.fold(x) : String(x || '').toLowerCase());
    const q = fold(query.trim());
    /* Marker gates the hover-recede :has() rule OFF while filtering. */
    const bodyEl = root.querySelector('.twk-body');
    if (bodyEl) bodyEl.classList.toggle('tlp-searching', !!q);
    const hitEls = [];
    root.querySelectorAll('.tlp-sec').forEach((sec) => {
      const id = sec.getAttribute('data-sec');
      const head = sec.querySelector('.tlp-sec-hd .twk-sect');
      const bodyWrap = sec.querySelector('.twk-sect-collapse');
      const body = sec.querySelector('.twk-sect-body');
      const rows = body ? Array.from(body.children) : [];
      const ctOld = head && head.querySelector('.tlp-sec-ct');
      if (!q) {
        sec.classList.remove('tlp-sec--nohit', 'tlp-sec--dim');
        rows.forEach((r) => r.classList.remove('tlp-row-dim', 'tlp-row-hide'));
        if (ctOld) ctOld.remove();
        if (bodyWrap) {
          const openNow = !expert || !secs[id];
          bodyWrap.setAttribute('data-open', openNow ? '1' : '0');
          if (body) body.inert = !openNow;
        }
        return;
      }
      /* Label = the first text node — head.textContent would include the
         injected count pill's digits and false-match numeric queries. */
      const labelText = head && head.childNodes[0] ? head.childNodes[0].textContent : '';
      const secHit = fold(labelText).indexOf(q) !== -1;
      let n = 0;
      rows.forEach((r) => {
        const rHit = secHit || fold(r.textContent).indexOf(q) !== -1;
        if (rHit) { n++; hitEls.push(r); }
        r.classList.toggle('tlp-row-dim', !rHit && !hideNonMatch);
        r.classList.toggle('tlp-row-hide', !rHit && hideNonMatch);
      });
      const show = secHit || n > 0;
      sec.classList.toggle('tlp-sec--nohit', !show && hideNonMatch);
      sec.classList.toggle('tlp-sec--dim', !show && !hideNonMatch);
      if (head) {
        let ct = head.querySelector('.tlp-sec-ct');
        if (show && n > 0) {
          if (!ct) { ct = document.createElement('span'); ct.className = 'tlp-sec-ct'; head.appendChild(ct); }
          ct.textContent = String(n);
        } else if (ct) ct.remove();
      }
      if (bodyWrap) {
        if (show) {
          bodyWrap.setAttribute('data-open', '1');
          if (body) body.inert = false;
        } else if (!hideNonMatch) {
          const openNow = !secs[id];
          bodyWrap.setAttribute('data-open', openNow ? '1' : '0');
          if (body) body.inert = !openNow;
        }
      }
    });
    hitsRef.current = hitEls;
    setHitCount((p) => (p === hitEls.length ? p : hitEls.length));
    if (query !== lastQueryRef.current) {
      lastQueryRef.current = query;
      setHitIdx(-1);
      root.querySelectorAll('.tlp-hit-active').forEach((el) => el.classList.remove('tlp-hit-active'));
      if (q && hitEls.length) scrollToEl(hitEls[0]);
    }
  }, [query, expert, secs, lang, s, hideNonMatch]);

  /* Active match — ring it, scroll it into the body's view, focus its first
     control (manual scrollTo on the panel body; never scrollIntoView). */
  React.useEffect(() => {
    const root = panelRef.current;
    if (!root) return;
    root.querySelectorAll('.tlp-hit-active').forEach((el) => el.classList.remove('tlp-hit-active'));
    const el = hitIdx >= 0 ? hitsRef.current[hitIdx] : null;
    if (!el) return;
    el.classList.add('tlp-hit-active');
    scrollToEl(el);
    const f = el.querySelector('button, input, [tabindex], [role="combobox"]');
    if (f && f.focus) f.focus({ preventScroll: true });
  }, [hitIdx]);

  /* Ctrl/Cmd+K — focus the search (expert mode only). */
  React.useEffect(() => {
    const onKey = (e) => {
      if (!(e.ctrlKey || e.metaKey) || e.altKey || (e.key !== 'k' && e.key !== 'K')) return;
      if (!expert) return;
      e.preventDefault();
      const inp = panelRef.current && panelRef.current.querySelector('.tlp-search input');
      if (inp) { inp.focus(); inp.select(); }
    };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [expert]);
  const t = TL_I18N[lang] || TL_I18N.cs;

  React.useEffect(() => {
    const onChange = () => {
      setS({ ...TL().state });
      const pre = document.getElementById('tl-live-css');
      if (pre) pre.textContent = TL().buildCSS();
    };
    document.addEventListener('themelab:change', onChange);
    onChange();
    return () => document.removeEventListener('themelab:change', onChange);
  }, []);
  React.useEffect(() => {
    document.documentElement.setAttribute('data-tl-lang', lang);
    try { localStorage.setItem('theme-lab-lang', lang); } catch (e) {}
    document.dispatchEvent(new CustomEvent('themelab:lang', { detail: { lang } }));
  }, [lang]);

  const set = (patch) => TL().set({ ...patch, preset: 'custom' }, { anchor: tlAnchor() });
  const togglePin = (g) => setPins((p) => {
    const next = { ...p, [g]: !p[g] };
    try { localStorage.setItem('theme-lab-pins', JSON.stringify(next)); } catch (e) {}
    return next;
  });

  const GROUPS = ['palette', 'neutrals', 'fontMain', 'fontMono', 'fontDisplay', 'displayReach', 'weights', 'typescale', 'radius', 'size', 'spacing', 'borderW', 'borderC', 'shadows', 'effects', 'motion', 'reveal', 'pagefx', 'buttons', 'density', 'stripe', 'chips', 'icons', 'iconStyle', 'bgPage', 'bgBar', 'bgPanels', 'bgOverlay', 'brand'];
  const S = { pins, togglePin, t, expert, secs, toggleSec, drag: query.trim() ? null : reorder };
  const presetOn = (id) => {
    const P = TL().PRESETS[id];
    if (!P) return false;
    const want = { ...TL().DEFAULTS, ...P.vals };
    return GROUPS.every((k) => s[k] === want[k]);
  };
  const themeOn = (id) => {
    const T = TL().THEMES[id];
    return T && s.palette === T.vals.palette && s.fontMain === T.vals.fontMain && s.brand === T.vals.brand;
  };

  const P = TL().PALETTES;
  const btnPresets = (window.FP_TWEAKS_CSS && window.FP_TWEAKS_CSS.BTN_PRESETS) || [];

  const copyCSS = () => {
    const css = TL().buildCSS();
    const done = () => window.Toast && window.Toast.success(t.copied, { sub: t.copiedSub });
    if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(css).then(done, done);
    else done();
  };
  const showCSS = () => {
    if (!window.Modal) return;
    const esc = (x) => x.replace(/&/g, '&amp;').replace(/</g, '&lt;');
    window.Modal.open({
      eyebrow: t.cssEyebrow, title: t.cssTitle, size: 'lg',
      body: '<pre style="margin:0;max-height:52vh;overflow:auto;font-family:var(--font-mono);font-size:12px;line-height:1.55;background:var(--color-bg-subtle);border:1px solid var(--color-border-default);border-radius:var(--radius-md);padding:14px">' + esc(TL().buildCSS()) + '</pre>',
      footer: '<button class="btn btn--ghost" type="button" data-modal-close>' + t.close + '</button>' +
        '<button class="btn btn--primary" type="button" id="tl-modal-copy"><i class="bi bi-clipboard-check"></i> ' + t.copy + '</button>',
    });
    setTimeout(() => {
      const b = document.getElementById('tl-modal-copy');
      if (b) b.addEventListener('click', copyCSS);
    }, 0);
  };

  const kitCss = (window.StudioKit && window.StudioKit.Panels && window.StudioKit.Panels.styleCss) || '';

  /* ── User presets (engine: theme-lab/user-presets.js → ThemeLab.user) ── */
  const user = TL().user;
  const upMatchName = () => {
    const m = (ups || []).find((p) => user && user.matches(p));
    return m ? m.name : '';
  };
  const upToast = (ok, title, sub) => window.Toast && (ok
    ? window.Toast.success(title, sub ? { sub } : undefined)
    : window.Toast.error(title, sub ? { sub } : undefined));
  const upDoSave = () => {
    if (!user) return;
    const name = upName.trim() || upMatchName();
    if (!name) { upToast(false, t.upNameNeedT); return; }
    user.save(name);
    setUps(user.list());
    setUpName(name);
    upToast(true, t.upSavedT, '„' + name + '“');
  };
  const upCopy = (text, title, sub) => {
    const done = () => upToast(true, title, sub);
    if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(done, done);
    else done();
  };
  const upApplyImport = (text) => {
    if (!user) return;
    try {
      const res = user.fromJSON(text);
      TL().set(user.patchOf(res.d), { anchor: tlAnchor() });
      if (res.name) setUpName(res.name);
      setUpImport('');
      upToast(true, t.upImportOkT, t.upImportOkSub);
    } catch (e) {
      upToast(false, t.upImportErrT, t.upImportErrSub);
    }
  };
  const upOnFile = (e) => {
    const f = e.target.files && e.target.files[0];
    e.target.value = '';
    if (!f) return;
    const r = new FileReader();
    r.onload = () => upApplyImport(String(r.result || ''));
    r.readAsText(f);
  };

  return (
    <div className="twk-panel" data-show="1" ref={panelRef}>
      <style>{kitCss}</style>
      <style>{tlPanelExtraStyle}</style>
      <div className="twk-hd" style={{ cursor: 'default' }}>
        <b>{t.panel}</b>
        <div className="tlp-hd-tools">
          {expert ? (
            <button type="button" className="tlp-hbtn" data-tip={anyCollapsed ? t.secAllExpand : t.secAllCollapse}
              aria-label={anyCollapsed ? t.secAllExpand : t.secAllCollapse}
              onClick={() => setAllSecs(!anyCollapsed)}>
              <i className={anyCollapsed ? 'bi bi-chevron-expand' : 'bi bi-chevron-contract'}></i>
            </button>
          ) : null}
          <label className="tlp-expert" data-tip={t.expertTip}>
            <input type="checkbox" checked={expert} onChange={(e) => setExpertMode(e.target.checked)} />
            <span>{t.expert}</span>
          </label>
        </div>
      </div>
      <div className="twk-body" ref={reorder.containerRef}>

        {expert ? (
          <div className="tlp-search">
            <div className="tlp-search-box">
              <i className="bi bi-search tlp-search-ic" aria-hidden="true"></i>
              <input className="twk-field" type="text" value={query} placeholder={t.searchPh} aria-label={t.searchPh}
                onChange={(e) => setQuery(e.target.value)}
                onKeyDown={onSearchKey} />
              {query ? (
                <span className="tlp-search-ct" aria-live="polite">{hitCount ? (hitIdx >= 0 ? (hitIdx + 1) + '/' + hitCount : String(hitCount)) : '0'}</span>
              ) : (
                <span className="tlp-kbd" aria-hidden="true">Ctrl K</span>
              )}
              {query ? (
                <button type="button" className="tlp-hbtn tlp-search-x" data-tip={t.searchClear} aria-label={t.searchClear}
                  onClick={() => setQuery('')}><i className="bi bi-x"></i></button>
              ) : null}
            </div>
            <button type="button" className="tlp-hbtn tlp-search-nav" data-tip={t.searchPrev} aria-label={t.searchPrev}
              disabled={!query || !hitCount} onClick={() => navHit(-1)}><i className="bi bi-chevron-up"></i></button>
            <button type="button" className="tlp-hbtn tlp-search-nav" data-tip={t.searchNext} aria-label={t.searchNext}
              disabled={!query || !hitCount} onClick={() => navHit(1)}><i className="bi bi-chevron-down"></i></button>
            <button type="button" className="tlp-hbtn tlp-search-nav" aria-pressed={hideNonMatch}
              data-tip={hideNonMatch ? t.searchShowAll : t.searchHideAll}
              aria-label={hideNonMatch ? t.searchShowAll : t.searchHideAll}
              onClick={toggleHideMode}><i className={hideNonMatch ? 'bi bi-eye-slash' : 'bi bi-eye'}></i></button>
          </div>
        ) : null}

        <TweakRadio label={t.lang} value={lang}
          options={[{ value: 'cs', label: 'Čeština' }, { value: 'en', label: 'English' }]}
          onChange={(v) => setLangState(v)} />

        <TlOrdered order={reorder.orderedIds}>

        <TlSection id="presets" label={t.presets} {...S}>
        <div className="tlp-pills">
          {Object.keys(TL().PRESETS).map((id) => (
            <button key={id} type="button" className="tlp-pill" data-on={presetOn(id) ? '1' : '0'}
              onClick={(e) => TL().applyPreset(id, { anchor: e.currentTarget })}>
              {TL().PRESETS[id].name}
            </button>
          ))}
          <button type="button" className="tlp-pill" data-tip={t.randomize} aria-label={t.randomize}
            onClick={(e) => TL().randomize(pins, { anchor: e.currentTarget })}>
            <i className="bi bi-dice-5"></i>
          </button>
          <button type="button" className="tlp-pill" data-tip={t.reset} aria-label={t.reset}
            onClick={(e) => TL().applyPreset('dsgcz', { anchor: e.currentTarget })}>
            <i className="bi bi-arrow-counterclockwise"></i>
          </button>
          <button type="button" className="tlp-pill" data-on={pinState === 'all' ? '1' : '0'} data-pinst={pinState}
            data-tip={pinState === 'all' ? t.pinAllOff : (pinState === 'some' ? t.pinSomeTip.replace('{n}', String(pinnedCount)).replace('{m}', String(pinGroups.length)) : t.pinAllOn)}
            aria-label={pinState === 'all' ? t.pinAllOff : t.pinAllOn} aria-pressed={pinState === 'all'}
            onClick={setAllPins}>
            <i className={pinState === 'none' ? 'bi bi-pin-angle' : 'bi bi-pin-fill'}></i>
          </button>
        </div>
        </TlSection>

        <TlSection id="themes" label={t.themes} {...S}>
        <div className="tlp-pills">
          {Object.keys(TL().THEMES).map((id) => (
            <button key={id} type="button" className="tlp-pill" data-on={themeOn(id) ? '1' : '0'}
              onClick={(e) => TL().set({ ...TL().THEMES[id].vals, preset: 'custom' }, { anchor: e.currentTarget })}>
              <span style={{ display: 'inline-block', width: 9, height: 9, borderRadius: 3, background: P[TL().THEMES[id].vals.palette].sw, marginRight: 5, verticalAlign: '-1px' }}></span>
              {TL().THEMES[id].name}
            </button>
          ))}
        </div>
        </TlSection>

        {expert ? (
        <TlSection id="user" label={t.userS} {...S}>
        {ups && ups.length ? (
          <div className="tlp-pills">
            {ups.map((p) => (
              <span key={p.id} role="button" tabIndex={0} className="tlp-pill tlp-pill--user"
                data-on={user && user.matches(p) ? '1' : '0'}
                onClick={() => user && user.apply(p, { anchor: tlAnchor() })}
                onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); user && user.apply(p, { anchor: tlAnchor() }); } }}>
                {p.name}
                <button type="button" className="tlp-x" aria-label={t.upDel} data-tip={t.upDel}
                  onClick={(e) => { e.stopPropagation(); if (user) { user.remove(p.id); setUps(user.list()); } }}>
                  <i className="bi bi-x"></i>
                </button>
              </span>
            ))}
          </div>
        ) : <div className="tlp-note">{t.upEmpty}</div>}
        <TweakText label={t.upNameL} value={upName} placeholder={t.upNamePh} onChange={(v) => setUpName(v)} />
        <div className="tlp-actions">
          <TweakButton label={t.upSave} onClick={upDoSave} />
          <TweakButton label={t.upDl} secondary onClick={() => user && user.download(upName.trim() || upMatchName())} />
        </div>
        <div className="tlp-actions">
          <TweakButton label={t.upLink} secondary onClick={() => user && upCopy(user.shareLink(upName.trim() || upMatchName()), t.upLinkT, t.upLinkSub)} />
          <TweakButton label={t.upJson} secondary onClick={() => user && upCopy(user.toJSON(upName.trim() || upMatchName()), t.upJsonT)} />
        </div>
        <TweakText label={t.upImportL} value={upImport} placeholder={t.upImportPh} onChange={(v) => setUpImport(v)} />
        <div className="tlp-actions">
          <TweakButton label={t.upImport} onClick={() => upApplyImport(upImport)} />
          <TweakButton label={t.upFile} secondary onClick={() => upFileRef.current && upFileRef.current.click()} />
        </div>
        <input type="file" accept=".json,application/json" style={{ display: 'none' }} ref={upFileRef} onChange={upOnFile} />
        </TlSection>
        ) : null}

        <TlSection id="mode" label={t.mode} {...S}>
        <TweakRadio label={t.mode} value={s.mode}
          options={[{ value: 'light', label: t.light }, { value: 'dark', label: t.dark }]}
          onChange={(v) => TL().set({ mode: v }, { anchor: tlAnchor() })} />
        <TweakRadio label={t.transition} value={s.transition}
          options={[{ value: 'wipe', label: t.wipe }, { value: 'fade', label: t.fade }, { value: 'none', label: t.instant }]}
          onChange={(v) => TL().set({ transition: v }, { how: 'none' })} />
        </TlSection>

        <TlSection id="colors" label={t.colors} group="palette" {...S}>
        <div className="twk-row">
          <div className="twk-lbl"><span>{t.palette}</span><span className="twk-val">{P[s.palette] ? P[s.palette].name : s.palette}</span></div>
          <div className="tlp-sw-row">
            {Object.keys(P).map((id) => (
              <button key={id} type="button" className="tlp-sw" data-on={s.palette === id ? '1' : '0'}
                style={{ background: P[id].sw }} data-tip={P[id].name} aria-label={P[id].name}
                onClick={() => set({ palette: id })}></button>
            ))}
          </div>
        </div>
        <TweakSelect label={t.neutrals} value={s.neutrals}
          options={[
            { value: 'cool', label: t.nCool },
            { value: 'pure', label: t.nPure },
            { value: 'warm', label: t.nWarm },
            { value: 'slate', label: t.nSlate },
            { value: 'stone', label: t.nStone },
            { value: 'sage', label: t.nSage },
            { value: 'mauve', label: t.nMauve },
          ]}
          onChange={(v) => set({ neutrals: v })} />
        </TlSection>

        <TlSection id="bg" label={t.bg} group="bg" {...S}>
        <TweakSelect label={t.bgPageL} value={s.bgPage}
          options={[
            { value: 'none', label: t.bgNone },
            { value: 'wash', label: t.bgWash },
            { value: 'aurora', label: t.bgAurora },
            { value: 'dots', label: t.bgDots },
            { value: 'grid', label: t.bgGrid },
            { value: 'hatch', label: t.bgHatch },
            { value: 'paper', label: t.bgPaper },
            { value: 'mist', label: t.bgMist },
            { value: 'image', label: t.bgImage },
          ]}
          onChange={(v) => set({ bgPage: v })} />
        {s.bgPage === 'image' ? (
          <TweakText label={t.bgUrlL} value={s.bgImageUrl} placeholder="https://…"
            onChange={(v) => TL().set({ bgImageUrl: v, preset: 'custom' }, { how: 'none' })} />
        ) : null}
        {expert ? (<>
        <TweakSelect label={t.bgBarL} value={s.bgBar}
          options={[
            { value: 'base', label: t.bgBarBase },
            { value: 'tint', label: t.bgBarTint },
            { value: 'gradient', label: t.bgBarGrad },
            { value: 'aurora', label: t.bgBarAurora },
          ]}
          onChange={(v) => set({ bgBar: v })} />
        <TweakSelect label={t.bgPanelsL} value={s.bgPanels}
          options={[
            { value: 'base', label: t.bgPanBase },
            { value: 'tint', label: t.bgPanTint },
            { value: 'wash', label: t.bgPanWash },
          ]}
          onChange={(v) => set({ bgPanels: v })} />
        <TweakSelect label={t.bgOverlayL} value={s.bgOverlay}
          options={[
            { value: 'base', label: t.bgOvBase },
            { value: 'wash', label: t.bgOvWash },
            { value: 'aurora', label: t.bgOvAurora },
          ]}
          onChange={(v) => set({ bgOverlay: v })} />
        </>) : null}
        </TlSection>

        <TlSection id="type" label={t.type} group="fonts" {...S}>
        <TweakSelect label={t.fontMainL} value={s.fontMain}
          options={Object.keys(TL().MAIN_FONTS).map((id) => ({ value: id, label: TL().MAIN_FONTS[id].name }))}
          onChange={(v) => set({ fontMain: v })} />
        {s.fontMain === 'custom' ? (
          <TweakText label={t.fontCustomL} value={s.fontCustomMain} placeholder={t.fontCustomPh}
            onChange={(v) => TL().set({ fontCustomMain: v, preset: 'custom' }, { how: 'none' })} />
        ) : null}
        <TweakSelect label={t.fontMonoL} value={s.fontMono}
          options={Object.keys(TL().MONO_FONTS).map((id) => ({ value: id, label: TL().MONO_FONTS[id].name }))}
          onChange={(v) => set({ fontMono: v })} />
        {s.fontMono === 'custom' ? (
          <TweakText label={t.fontCustomL + ' (mono)'} value={s.fontCustomMono} placeholder={t.fontCustomPh}
            onChange={(v) => TL().set({ fontCustomMono: v, preset: 'custom' }, { how: 'none' })} />
        ) : null}
        {expert ? (<>
        <TweakSelect label={t.fontDispL} value={s.fontDisplay}
          options={Object.keys(TL().DISPLAY_FONTS).map((id) => ({ value: id, label: TL().DISPLAY_FONTS[id].name }))}
          onChange={(v) => set({ fontDisplay: v })} />
        {s.fontDisplay === 'custom' ? (
          <TweakText label={t.fontCustomL + ' (display)'} value={s.fontCustomDisplay} placeholder={t.fontCustomPh}
            onChange={(v) => TL().set({ fontCustomDisplay: v, preset: 'custom' }, { how: 'none' })} />
        ) : null}
        {s.fontDisplay !== 'same' ? (
          <TweakSelect label={t.dispReachL} value={s.displayReach}
            options={[
              { value: 'h1', label: t.reachH1 },
              { value: 'h2', label: t.reachH2 },
              { value: 'sub', label: t.reachSub },
            ]}
            onChange={(v) => set({ displayReach: v })} />
        ) : null}
        <TweakRadio label={t.weightsL} value={s.weights}
          options={[
            { value: 'light', label: t.wLight },
            { value: 'base', label: t.wBase },
            { value: 'heavy', label: t.wHeavy },
          ]}
          onChange={(v) => set({ weights: v })} />
        </>) : null}
        <TweakRadio label={t.scale} value={s.typescale}
          options={[
            { value: 'compact', label: t.sCompact },
            { value: 'base', label: t.sBase },
            { value: 'large', label: t.sLarge },
          ]}
          onChange={(v) => set({ typescale: v })} />
        </TlSection>

        <TlSection id="shape" label={t.shape} group="shape" {...S}>
        <TweakSelect label={t.radius} value={s.radius}
          options={[
            { value: 'sharp', label: t.rSharp },
            { value: 'soft', label: t.rSoft },
            { value: 'base', label: t.rBase },
            { value: 'round', label: t.rRound },
            { value: 'pill', label: t.rPill },
          ]}
          onChange={(v) => set({ radius: v })} />
        {expert ? (<>
        <TweakSelect label={t.rRoleBtn} value={s.radiusBtn} options={tlRadiusRoleOpts(t)}
          onChange={(v) => set({ radiusBtn: v })} />
        <TweakSelect label={t.rRoleCtl} value={s.radiusCtl} options={tlRadiusRoleOpts(t)}
          onChange={(v) => set({ radiusCtl: v })} />
        <TweakSelect label={t.rRolePanel} value={s.radiusPanel} options={tlRadiusRoleOpts(t)}
          onChange={(v) => set({ radiusPanel: v })} />
        <TweakSelect label={t.rRoleOverlay} value={s.radiusOverlay} options={tlRadiusRoleOpts(t)}
          onChange={(v) => set({ radiusOverlay: v })} />
        <TweakSelect label={t.rRoleChip} value={s.radiusChip} options={tlRadiusRoleOpts(t)}
          onChange={(v) => set({ radiusChip: v })} />
        <TweakRadio label={t.sizeL} value={s.size}
          options={[
            { value: 'compact', label: t.szCompact },
            { value: 'base', label: t.szBase },
            { value: 'large', label: t.szLarge },
          ]}
          onChange={(v) => set({ size: v })} />
        </>) : null}
        </TlSection>

        <TlSection id="space" label={t.space} group="spacing" {...S}>
        <TweakSelect label={t.space} value={s.spacing}
          options={[
            { value: 'xtight', label: t.spXTight + ' — 60 %' },
            { value: 'tight', label: t.spTight + ' — 78 %' },
            { value: 'base', label: t.spBase + ' — 100 %' },
            { value: 'airy', label: t.spAiry + ' — 130 %' },
            { value: 'xairy', label: t.spXAiry + ' — 160 %' },
          ]}
          onChange={(v) => set({ spacing: v })} />
        </TlSection>

        {expert ? (
        <TlSection id="borders" label={t.borders} group="borders" {...S}>
        <TweakSelect label={t.borderWL} value={s.borderW}
          options={[
            { value: 'none', label: t.bwNone },
            { value: 'hairline', label: t.bwHairline },
            { value: 'base', label: t.bwBase },
            { value: 'thick', label: t.bwThick },
            { value: 'heavy', label: t.bwHeavy },
          ]}
          onChange={(v) => set({ borderW: v })} />
        <TweakSelect label={t.borderCL} value={s.borderC}
          options={[
            { value: 'base', label: t.bcBase },
            { value: 'strong', label: t.bcStrong },
            { value: 'primary', label: t.bcPrimary },
            { value: 'contrast', label: t.bcContrast },
          ]}
          onChange={(v) => set({ borderC: v })} />
        <TweakSelect label={t.bwRolePanel} value={s.bwPanel} options={tlBwRoleOpts(t)}
          onChange={(v) => set({ bwPanel: v })} />
        <TweakSelect label={t.bwRoleCtl} value={s.bwCtl} options={tlBwRoleOpts(t)}
          onChange={(v) => set({ bwCtl: v })} />
        <TweakSelect label={t.bwRoleLine} value={s.bwLine} options={tlBwRoleOpts(t)}
          onChange={(v) => set({ bwLine: v })} />
        <TweakSelect label={t.fgStyleL} value={s.fgStyle}
          options={[
            { value: 'base', label: t.fgBase },
            { value: 'outline', label: t.fgOutline },
            { value: 'flat', label: t.fgFlat },
            { value: 'bare', label: t.fgBare },
            { value: 'tree', label: t.fgTree },
          ]}
          onChange={(v) => set({ fgStyle: v })} />
        </TlSection>
        ) : null}

        {expert ? (
        <TlSection id="variants" label={t.variants} group="variants" {...S}>
        <TweakSelect label={t.segStyleL} value={s.segStyle}
          options={[
            { value: 'base', label: t.segFill },
            { value: 'underline', label: t.segUnderline },
          ]}
          onChange={(v) => set({ segStyle: v })} />
        <TweakSelect label={t.pagStyleL} value={s.pagStyle}
          options={[
            { value: 'base', label: t.pagFrame },
            { value: 'bare', label: t.pagBare },
          ]}
          onChange={(v) => set({ pagStyle: v })} />
        <TweakSelect label={t.comboHlL} value={s.comboHl}
          options={[
            { value: 'base', label: t.cbTint },
            { value: 'rail', label: t.cbRail },
            { value: 'rounded', label: t.cbRounded },
          ]}
          onChange={(v) => set({ comboHl: v })} />
        <TweakSelect label={t.checkAnimL} value={s.checkAnim}
          options={[
            { value: 'base', label: t.caBase },
            { value: 'fade', label: t.caFade },
            { value: 'pop', label: t.caPop },
            { value: 'wipe', label: t.caWipe },
            { value: 'draw', label: t.caDraw },
          ]}
          onChange={(v) => set({ checkAnim: v })} />
        <TweakSelect label={t.linkStyleL} value={s.linkStyle}
          options={[
            { value: 'base', label: t.lkBase },
            { value: 'dashed', label: t.lkDashed },
            { value: 'dotted', label: t.lkDotted },
            { value: 'thick', label: t.lkThick },
            { value: 'tonal', label: t.lkTonal },
          ]}
          onChange={(v) => set({ linkStyle: v })} />
        <TweakSelect label={t.tabStyleL} value={s.tabStyle}
          options={[
            { value: 'base', label: t.tabBase },
            { value: 'pills', label: t.tabPills },
            { value: 'block', label: t.tabBlock },
          ]}
          onChange={(v) => set({ tabStyle: v })} />
        </TlSection>
        ) : null}

        {expert ? (
        <TlSection id="focus" label={t.focus} group="focus" {...S}>
        <TweakRadio label={t.focusWL} value={s.focusW}
          options={[{ value: 'thin', label: t.fwThin }, { value: 'base', label: t.fwBase }, { value: 'bold', label: t.fwBold }]}
          onChange={(v) => set({ focusW: v })} />
        <TweakRadio label={t.focusGapL} value={s.focusGap}
          options={[{ value: 'none', label: t.fgapNone }, { value: 'base', label: t.fgapBase }, { value: 'wide', label: t.fgapWide }]}
          onChange={(v) => set({ focusGap: v })} />
        <TweakRadio label={t.focusGlowL} value={s.focusGlow}
          options={[{ value: 'none', label: t.fglNone }, { value: 'soft', label: t.fglSoft }, { value: 'strong', label: t.fglStrong }]}
          onChange={(v) => set({ focusGlow: v })} />
        </TlSection>
        ) : null}

        {expert ? (
        <TlSection id="table" label={t.table} group="stripe" {...S}>
        <TweakSelect label={t.stripe} value={s.stripe}
          options={[
            { value: 'zebra', label: t.stZebra },
            { value: 'lines', label: t.stLines },
            { value: 'bare', label: t.stBare },
          ]}
          onChange={(v) => set({ stripe: v })} />
        <TweakSelect label={t.density} value={s.density}
          options={[
            { value: 'compact', label: t.deCompact },
            { value: 'comfortable', label: t.deComfort },
            { value: 'roomy', label: t.deRoomy },
          ]}
          onChange={(v) => set({ density: v })} />
        <TweakSelect label={t.chips} value={s.chips}
          options={[
            { value: 'filled', label: t.chFilled },
            { value: 'outlined', label: t.chOutlined },
            { value: 'letter', label: t.chLetter },
          ]}
          onChange={(v) => set({ chips: v })} />
        </TlSection>
        ) : null}

        <TlSection id="fx" label={t.fx} group="fx" {...S}>
        <TweakSelect label={t.shadows} value={s.shadows}
          options={[
            { value: 'none', label: t.shNone },
            { value: 'base', label: t.shBase },
            { value: 'soft', label: t.shSoft },
            { value: 'dramatic', label: t.shDrama },
            { value: 'glow', label: t.shGlow },
          ]}
          onChange={(v) => set({ shadows: v })} />
        <TweakSelect label={t.fxSelL} value={s.effects}
          options={[
            { value: 'none', label: t.fxNone },
            { value: 'glass', label: t.fxGlass },
            { value: 'heavy', label: t.fxHeavy },
          ]}
          onChange={(v) => set({ effects: v })} />
        {expert ? (<>
        <TweakSelect label={t.fxRoleSurf} value={s.glassSurf} options={tlGlassRoleOpts(t)}
          onChange={(v) => set({ glassSurf: v })} />
        <TweakSelect label={t.fxRoleOver} value={s.glassOver} options={tlGlassRoleOpts(t)}
          onChange={(v) => set({ glassOver: v })} />
        <TweakSelect label={t.fxRoleCtl} value={s.glassCtl} options={tlGlassRoleOpts(t)}
          onChange={(v) => set({ glassCtl: v })} />
        </>) : null}
        </TlSection>

        <TlSection id="motion" label={t.motion} group="motion" {...S}>
        <TweakSelect label={t.motion} value={s.motion}
          options={[
            { value: 'none', label: t.mNone },
            { value: 'fast', label: t.mFast },
            { value: 'smooth', label: t.mSmooth },
            { value: 'calm', label: t.mCalm },
            { value: 'ct', label: t.mCt },
            { value: 'springy', label: t.mSpring },
          ]}
          onChange={(v) => TL().set({ motion: v, preset: 'custom' }, { how: 'none' })} />
        {expert ? (<>
        <TweakSelect label={t.revealL} value={s.reveal}
          options={[
            { value: 'up', label: t.rvUp },
            { value: 'left', label: t.rvLeft },
            { value: 'zoom', label: t.rvZoom },
            { value: 'fade', label: t.rvFade },
            { value: 'none', label: t.rvNone },
          ]}
          onChange={(v) => TL().set({ reveal: v, preset: 'custom' }, { how: 'none' })} />
        <TweakSelect label={t.pagefxL} value={s.pagefx}
          options={[
            { value: 'rise', label: t.pfRise },
            { value: 'fall', label: t.pfFall },
            { value: 'slip-left', label: t.pfSlip },
            { value: 'fade', label: t.pfFade },
            { value: 'scale', label: t.pfScale },
            { value: 'none', label: t.pfNone },
          ]}
          onChange={(v) => TL().set({ pagefx: v, preset: 'custom' }, { how: 'none' })} />
        </>) : null}
        </TlSection>

        <TlSection id="buttons" label={t.buttons} group="buttons" {...S}>
        <TweakSelect label={t.btnStyle} value={s.buttons}
          options={[{ value: 'dsgcz', label: 'DSGCZ (kanonické)' }].concat(
            btnPresets.filter((p) => p.id !== 'DSGCZ').map((p) => ({
              value: p.id,
              label: ({ 1: '1 — přízemní stín', 2: '2 — jemný gradient', 3: '3 — lesk', 4: '4 — vystouplé', 5: '5 — vystouplé · gradient', 6: '6 — lustr · záře' })[p.id] || p.id,
            }))
          )}
          onChange={(v) => set({ buttons: v })} />
        </TlSection>

        <TlSection id="icons" label={t.icons} group="icons" {...S}>
        <TweakSelect label={t.iconSet} value={s.icons}
          options={[
            { value: 'bootstrap', label: t.iSetB },
            { value: 'material', label: t.iSetM },
            { value: 'fa', label: t.iSetF },
            { value: 'emoji', label: t.iSetE },
          ]}
          onChange={(v) => set({ icons: v })} />
        {expert ? (<>
        <TweakSelect label={t.iconStyle} value={s.iconStyle}
          options={[
            { value: 'outlined', label: t.iStOut },
            { value: 'filled', label: t.iStFill },
            { value: 'rounded', label: t.iStRound },
            { value: 'sharp', label: t.iStSharp },
          ]}
          onChange={(v) => set({ iconStyle: v })} />
        <TweakRadio label={t.iconW} value={s.iconWeight}
          options={[
            { value: 'thin', label: t.iwThin },
            { value: 'base', label: t.iwBase },
            { value: 'heavy', label: t.iwHeavy },
          ]}
          onChange={(v) => set({ iconWeight: v })} />
        </>) : null}
        </TlSection>

        <TlSection id="brand" label={t.brand} {...S}>
        <TweakText label={t.brandText} value={s.brandText} placeholder={t.brandPh}
          onChange={(v) => TL().set({ brandText: v }, { how: 'none' })} />
        </TlSection>

        <TlSection id="export" label={t.exportS} {...S}>
        <div className="tlp-actions">
          <TweakButton label={t.copy} onClick={copyCSS} />
          <TweakButton label={t.show} secondary onClick={showCSS} />
        </div>
        </TlSection>

        </TlOrdered>

      </div>
    </div>
  );
}

(function mountThemeLabPanel() {
  const host = document.getElementById('tl-panel-root');
  if (!host) return;
  ReactDOM.createRoot(host).render(<ThemeLabPanel />);
})();
